feat: createExistingContact feat: using nano instead of mini so I don't run out of money instantly
268 lines
9.0 KiB
Go
268 lines
9.0 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 = `
|
|
**You are an AI processing events from images using internal thought.**
|
|
|
|
**Task:** Extract event details (Name, Date/Time, Location). Use think before deciding actions. Check duplicates with listEvents. Handle new events via getEventLocationId (if location exists) and createEvent. Use finish if no event or duplicate found.
|
|
1. **Analyze Image & Think:** Extract details. Use think to confirm if a valid event exists. If not -> stopAgent.
|
|
2. **Event Confirmed?** -> *Must* call listEvents, to check for existing events and prevent duplicates.
|
|
3. **Detect Duplicates** -> If the input contains an event that already exists from listEvents, then you should call stopAgent.
|
|
4. **New Events**
|
|
* If you think the input contains a location, then you can use getEventLocationId to retrieve the ID of the location. Only use this IF the input contains a location.
|
|
* Call createEvent.
|
|
5. **Multiple Events:** Process sequentially using this logic.
|
|
|
|
**Tools:**
|
|
* think: Internal reasoning/planning step.
|
|
* listEvents: Check for duplicates (mandatory first step for found events).
|
|
* getEventLocationId: Get ID for location text.
|
|
* createEvent: Add new event (Name req.). Terminal action for new events.
|
|
* stopAgent: Signal completion (no event/duplicate found). Terminal action.
|
|
`
|
|
|
|
const eventTools = `
|
|
[
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "think",
|
|
"description": "Use this tool to think through the image, evaluating the event and whether or not it exists in the users listEvents.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"thought": {
|
|
"type": "string",
|
|
"description": "A singular thought about the image"
|
|
}
|
|
},
|
|
"required": ["thought"]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"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"`
|
|
}
|
|
|
|
const layout = "2006-01-02T15:04:05Z"
|
|
|
|
func getArguments(args createEventArguments) (model.Events, error) {
|
|
event := model.Events{
|
|
Name: args.Name,
|
|
}
|
|
|
|
if args.StartDateTime != nil {
|
|
startTime, err := time.Parse(layout, *args.StartDateTime)
|
|
if err != nil {
|
|
return event, err
|
|
}
|
|
|
|
event.StartDateTime = &startTime
|
|
}
|
|
|
|
if args.EndDateTime != nil {
|
|
endTime, err := time.Parse(layout, *args.EndDateTime)
|
|
if err != nil {
|
|
return event, err
|
|
}
|
|
|
|
event.EndDateTime = &endTime
|
|
}
|
|
|
|
if args.LocationID != nil {
|
|
locationId, err := uuid.Parse(*args.LocationID)
|
|
if err != nil {
|
|
return model.Events{}, err
|
|
}
|
|
|
|
event.LocationID = &locationId
|
|
}
|
|
|
|
return event, nil
|
|
}
|
|
|
|
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("think", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) {
|
|
return "Thought", nil
|
|
})
|
|
|
|
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()
|
|
event, err := getArguments(args)
|
|
if err != nil {
|
|
return model.Events{}, err
|
|
}
|
|
|
|
events, err := eventsModel.Save(ctx, info.UserId, event)
|
|
|
|
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
|
|
}
|