248 lines
9.9 KiB
Go
248 lines
9.9 KiB
Go
package agents
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"screenmark/screenmark/.gen/haystack/haystack/model"
|
|
"screenmark/screenmark/agents/client"
|
|
"screenmark/screenmark/models"
|
|
"time"
|
|
|
|
"github.com/charmbracelet/log"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const eventPrompt = `
|
|
**Role:** You are an Event Processing AI Assistant specialized in extracting event information from images, managing event data using provided tools, and ensuring accuracy and avoiding duplicates.
|
|
|
|
**Primary Goal:** To analyze images, identify potential events (like meetings, appointments, conferences, invitations), extract key details (name, date/time, location description), check against existing events, retrieve location identifiers if applicable, create new event entries when necessary, and signal completion using the 'finish' tool.
|
|
|
|
**Core Workflow:**
|
|
|
|
**Duplicate Check (Mandatory if Event Found):**
|
|
* If potential event details were found, you **must** call the listEvents tool first to check for duplicates. **Generate only the listEvents tool call structure.**
|
|
* Once you receive the list, compare the extracted event details (Name, Start Date/Time primarily) against the existing events.
|
|
* **If a matching event already exists, proceed directly to Step 6 (call finish).**
|
|
|
|
**Location ID Retrieval (Conditional):**
|
|
* If the event is identified as *new* AND a *location description* was extracted.
|
|
* Call the getEventLocationId tool, providing the extracted location description. **Generate only the getEventLocationId tool call structure.**
|
|
|
|
**Create Event:**
|
|
* If the event was identified as *new*:
|
|
* Prepare the parameters for the createEvent tool using the extracted details (Name, Start Date/Time, End Date/Time).
|
|
* If you identify the event as *duplicate*, meaning you think an event in listEvents is the same as the event on this image.
|
|
* Call the updateEvent tool so this image is also linked to that event. If you find any new information you can update it using this tool too.
|
|
|
|
**Handling Multiple Events:**
|
|
* If the image contains multiple distinct events, ideally process them one by one.
|
|
* Do this until there are no more events on this image
|
|
|
|
**Task Completion / No Action Needed:**
|
|
* Call the finish tool **only** when one of the following conditions is met:
|
|
* No identifiable event information was found in the initial image analysis.
|
|
* The listEvents check confirmed the identified event already exists.
|
|
* You have successfully called createEvent for a new event.
|
|
|
|
**Available Tools:**
|
|
|
|
* **listEvents**: Retrieves the user's existing events. **Must** be called first if potential event details are found in the image, to enable duplicate checking.
|
|
* **getEventLocationId**: Takes a location description (text) and retrieves a unique ID (locationId) for it. Use this *before* createEvent *only* if a new event has a specific location mentioned.
|
|
* **createEvent**: Adds a *new*, non-duplicate event to the user's calendar/list. Only call *after* listEvents confirms the event is new. Requires name. Include startDateTime, endDateTime, and locationId (if available and retrieved).
|
|
* **stopAgent**: Signals that processing for the current image is complete (either action was taken, no action was needed because the event already existed, or no event was found). Call this as the final step.
|
|
`
|
|
|
|
const eventTools = `
|
|
[
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "listEvents",
|
|
"description": "Retrieves the list of the user's currently scheduled events. Essential for checking if an event identified in the image already exists to prevent duplicates. Must be called before potentially creating an event.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {},
|
|
"required": []
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "createEvent",
|
|
"description": "Creates a new event in the user's calendar or list. Use only after listEvents confirms the event is new. Provide all extracted details.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"description": "The name or title of the event. This field is mandatory."
|
|
},
|
|
"startDateTime": {
|
|
"type": "string",
|
|
"description": "The event's start date and time in ISO 8601 format (e.g., '2025-04-18T10:00:00Z'). Include if available."
|
|
},
|
|
"endDateTime": {
|
|
"type": "string",
|
|
"description": "The event's end date and time in ISO 8601 format. Optional, include if available and different from startDateTime."
|
|
},
|
|
"locationId": {
|
|
"type": "string",
|
|
"description": "The unique identifier (UUID or similar) for the event's location. Use this if available, do not invent it."
|
|
}
|
|
},
|
|
"required": ["name"]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "updateEvent",
|
|
"description": "Updates an existing event record identified by its eventId. Use this tool when listEvents indicates a match for the event details found in the current input.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"eventId": {
|
|
"type": "string",
|
|
"description": "The UUID of the existing event"
|
|
}
|
|
},
|
|
"required": ["eventId"]
|
|
}
|
|
|
|
}
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "getEventLocationId",
|
|
"description": "Retrieves a unique identifier for a location description associated with an event. Use this before createEvent if a new event specifies a location.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"locationDescription": {
|
|
"type": "string",
|
|
"description": "The text describing the location extracted from the image (e.g., 'Conference Room B', '123 Main St, Anytown', 'Zoom Link details')."
|
|
}
|
|
},
|
|
"required": ["locationDescription"]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "stopAgent",
|
|
"description": "Call this tool only when event processing for the current image is fully complete. This occurs if: 1) No event info was found, OR 2) The found event already exists, OR 3) A new event has been successfully created.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {},
|
|
"required": []
|
|
}
|
|
}
|
|
}
|
|
]`
|
|
|
|
type listEventArguments struct{}
|
|
type createEventArguments struct {
|
|
Name string `json:"name"`
|
|
StartDateTime *string `json:"startDateTime"`
|
|
EndDateTime *string `json:"endDateTime"`
|
|
OrganizerName *string `json:"organizerName"`
|
|
LocationID *string `json:"locationId"`
|
|
}
|
|
type updateEventArguments struct {
|
|
EventID string `json:"eventId"`
|
|
}
|
|
|
|
func NewEventAgent(log *log.Logger, eventsModel models.EventModel, locationModel models.LocationModel) client.AgentClient {
|
|
agentClient := client.CreateAgentClient(client.CreateAgentClientOptions{
|
|
SystemPrompt: eventPrompt,
|
|
JsonTools: eventTools,
|
|
Log: log,
|
|
EndToolCall: "stopAgent",
|
|
})
|
|
|
|
locationAgent := NewLocationAgentWithComm(log.WithPrefix("Events 📅 > Locations 📍"), locationModel)
|
|
locationQuery := "Can you get me the ID of the location present in this image?"
|
|
locationAgent.Options.Query = &locationQuery
|
|
|
|
agentClient.ToolHandler.AddTool("listEvents", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
|
return eventsModel.List(context.Background(), info.UserId)
|
|
})
|
|
|
|
agentClient.ToolHandler.AddTool("createEvent", func(info client.ToolHandlerInfo, _args string, call client.ToolCall) (any, error) {
|
|
args := createEventArguments{}
|
|
err := json.Unmarshal([]byte(_args), &args)
|
|
if err != nil {
|
|
return model.Events{}, err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
layout := "2006-01-02T15:04:05Z"
|
|
|
|
startTime, err := time.Parse(layout, *args.StartDateTime)
|
|
if err != nil {
|
|
return model.Events{}, err
|
|
}
|
|
|
|
endTime, err := time.Parse(layout, *args.EndDateTime)
|
|
if err != nil {
|
|
return model.Events{}, err
|
|
}
|
|
|
|
locationId, err := uuid.Parse(*args.LocationID)
|
|
if err != nil {
|
|
return model.Events{}, err
|
|
}
|
|
|
|
events, err := eventsModel.Save(ctx, info.UserId, model.Events{
|
|
Name: args.Name,
|
|
StartDateTime: &startTime,
|
|
EndDateTime: &endTime,
|
|
LocationID: &locationId,
|
|
})
|
|
|
|
if err != nil {
|
|
return model.Events{}, err
|
|
}
|
|
|
|
_, err = eventsModel.SaveToImage(ctx, info.ImageId, events.ID)
|
|
if err != nil {
|
|
return model.Events{}, err
|
|
}
|
|
|
|
return events, nil
|
|
})
|
|
|
|
agentClient.ToolHandler.AddTool("updateEvent", func(info client.ToolHandlerInfo, _args string, call client.ToolCall) (any, error) {
|
|
args := updateEventArguments{}
|
|
err := json.Unmarshal([]byte(_args), &args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
contactUuid, err := uuid.Parse(args.EventID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
eventsModel.SaveToImage(ctx, info.ImageId, contactUuid)
|
|
return "Saved", nil
|
|
})
|
|
|
|
agentClient.ToolHandler.AddTool("getEventLocationId", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
|
// TODO: reenable this when I'm creating the agent locally instead of getting it from above.
|
|
locationAgent.RunAgent(info.UserId, info.ImageId, info.ImageName, *info.Image)
|
|
|
|
log.Debugf("Reply from location %s\n", locationAgent.Reply)
|
|
return locationAgent.Reply, nil
|
|
})
|
|
|
|
return agentClient
|
|
}
|