From 49969b0608d4be95f9bfba5d55f6e7fe13277c60 Mon Sep 17 00:00:00 2001 From: John Costa Date: Fri, 18 Apr 2025 13:26:42 +0100 Subject: [PATCH] feat(location-agent): using createLocation instead of updateLocation to simplify --- backend/agents/contact_agent.go | 41 ++--------------- backend/agents/location_agent.go | 75 +++++++++----------------------- backend/models/locations.go | 43 ++++++++++++++++++ 3 files changed, 67 insertions(+), 92 deletions(-) diff --git a/backend/agents/contact_agent.go b/backend/agents/contact_agent.go index 630e763..1a969c0 100644 --- a/backend/agents/contact_agent.go +++ b/backend/agents/contact_agent.go @@ -53,14 +53,6 @@ const contactPrompt = ` * **listContacts**: Retrieves the existing contact list. **Must** be called first if potential contacts are found in the image, to enable duplicate checking. * **createContact**: Adds a *new*, non-duplicate contact. Only call *after* listContacts confirms the person is new. name is mandatory. * **stopAgent**: Signals that processing for the current image is complete (either action was taken, no action was needed, or all identified contacts already existed). Call this as the final step or when no other action is applicable based on the workflow. - -**Key Instructions & Constraints:** - -* **Strict Output Separation:** **Never mix text content and tool call structures in the same response content. Generate ONLY the tool call when using a tool.** -* **Tool Order:** listContacts (if contacts found) -> [createContact (if new contacts exist, potentially multiple calls)] -> stopAgent. OR stopAgent directly if no contacts found or all contacts exist. -* **Accuracy & Completeness:** Strive for accurate extraction and provide all available details when calling createContact. -* **No Duplicates:** The core purpose of the listContacts check is to prevent duplicate entries. -* **Use Provided Tools Only:** Only use listContacts, createContact, and stopAgent. ` const contactTools = ` @@ -85,10 +77,10 @@ const contactTools = ` "parameters": { "type": "object", "properties": { - "contactId": { - "type": "string", - "description": "The UUID of the contact. You should only provide this IF you believe the contact already exists, from listContacts." - }, + "contactId": { + "type": "string", + "description": "The UUID of the contact. You should only provide this IF you believe the contact already exists, from listContacts." + }, "name": { "type": "string", "description": "The full name of the person being added as a contact. This field is mandatory." @@ -133,9 +125,6 @@ type createContactsArguments struct { Address *string `json:"address"` Email *string `json:"email"` } -type linkContactArguments struct { - ContactID string `json:"contactId"` -} func NewContactAgent(log *log.Logger, contactModel models.ContactModel) client.AgentClient { agentClient := client.CreateAgentClient(client.CreateAgentClientOptions{ @@ -187,27 +176,5 @@ func NewContactAgent(log *log.Logger, contactModel models.ContactModel) client.A return contact, nil }) - agentClient.ToolHandler.AddTool("linkContact", func(info client.ToolHandlerInfo, _args string, call client.ToolCall) (any, error) { - args := linkContactArguments{} - err := json.Unmarshal([]byte(_args), &args) - if err != nil { - return "", err - } - - ctx := context.Background() - - contactUuid, err := uuid.Parse(args.ContactID) - if err != nil { - return "", err - } - - _, err = contactModel.SaveToImage(ctx, info.ImageId, contactUuid) - if err != nil { - return "", err - } - - return "Saved", nil - }) - return agentClient } diff --git a/backend/agents/location_agent.go b/backend/agents/location_agent.go index 7069ff7..f5f1656 100644 --- a/backend/agents/location_agent.go +++ b/backend/agents/location_agent.go @@ -24,27 +24,11 @@ Core Logic: * Use listLocations to check if a location matching InputName or InputAddress already exists in the saved list. * **If *no match*** is found: * Use createLocation, providing the extracted InputName (required) and any other details like InputAddress. - * *(Proceed to step 5)* - * **If a *match*** is found (meaning it's a potential duplicate/update candidate - let the matching saved location have ExistingLocationId): - * Use updateLocation. Provide the locationId = ExistingLocationId. - * Also provide any *new or potentially refined* details extracted from the current input (e.g., name = InputName, address = InputAddress). The updateLocation tool should handle updating the record with these details and/or linking the new input context (like the image) to this existing location. - * *(Proceed to step 5)* - * If no location details could be extracted from the input in Step 1. - * *(Proceed to step 5)* - -**Summarize & Stop:** Always finish by writing a concise message explaining the outcome of the turn. Examples: - * "Okay, I've answered your query about [Location Name]." (after calling reply) - * "I couldn't find [Queried Location Name] in my saved list." (after failing to find a match for a query) - * "I've saved '[InputName]' as a new location." (after calling createLocation) - * "I found that '[InputName]' was already saved, so I've updated its information/context based on your latest input." (after calling updateLocation) - * "I couldn't identify a specific location from your input." (if Step 1 failed or no action was taken) - * After providing the summary message, call stopAgent to signal the end of processing for this turn. + * If no location details could be extracted from the input, use stopAgent. Tool Usage: - * listLocations: Check saved locations. Used to find matches for user queries or to detect existing entries before creating/updating. Returns matching location(s) including their locationId. -* createLocation: Save a *new* location. Requires name, can include address, etc. -* updateLocation: Update an *existing* location. Requires locationId. Can include name, address, etc., to update specific fields or simply to associate the new input context with the existing location. +* createLocation: Save a *new* location. Requires name, can include address, etc. You may also use this to update the information in a location, by providing a locationId obtained from listLocations if you believe this location already exists. * stopAgent: Signals the end of the agent's processing for the current turn. Call this *after* providing the summary message. ` @@ -93,6 +77,10 @@ const locationTools = ` "type": "string", "description": "The primary name of the location (e.g., 'Eiffel Tower', 'Mom's House', 'Acme Corp HQ'). This field is mandatory." }, + "locationId": { + "type": "string", + "description": "The UUID of the location. You should only provide this IF you believe the contact already exists, from listLocation." + }, "address": { "type": "string", "description": "The full street address of the location, if available (e.g., 'Champ de Mars, 5 Av. Anatole France, 75007 Paris, France'). Include if extracted." @@ -101,23 +89,6 @@ const locationTools = ` "required": ["name"] } } - }, - { - "type": "function", - "function": { - "name": "updateLocation", - "description": "Updates an existing saved location identified by its locationId. Use when input matches a pre-existing location. Pass locationId and any new details (name, address) to update.", - "parameters": { - "type": "object", - "properties": { - "locationId": { - "type": "string", - "description": "The UUID of the location you are trying to update" - } - }, - "required": ["locationId"] - } - } }, %s { @@ -144,8 +115,9 @@ func getLocationAgentTools(allowReply bool) string { type listLocationArguments struct{} type createLocationArguments struct { - Name string `json:"name"` - Address *string `json:"address"` + Name string `json:"name"` + LocationID *string `json:"locationId"` + Address *string `json:"address"` } type updateLocationArguments struct { LocationID string `json:"locationId"` @@ -180,7 +152,18 @@ func NewLocationAgent(log *log.Logger, locationModel models.LocationModel) clien ctx := context.Background() + locationId := uuid.Nil + if args.LocationID != nil { + locationUuid, err := uuid.Parse(*args.LocationID) + if err != nil { + return model.Locations{}, err + } + + locationId = locationUuid + } + location, err := locationModel.Save(ctx, info.UserId, model.Locations{ + ID: locationId, Name: args.Name, Address: args.Address, }) @@ -197,24 +180,6 @@ func NewLocationAgent(log *log.Logger, locationModel models.LocationModel) clien return location, nil }) - agentClient.ToolHandler.AddTool("updateLocation", func(info client.ToolHandlerInfo, _args string, call client.ToolCall) (any, error) { - args := updateLocationArguments{} - err := json.Unmarshal([]byte(_args), &args) - if err != nil { - return "", err - } - - ctx := context.Background() - - locationId, err := uuid.Parse(args.LocationID) - if err != nil { - return "", err - } - - locationModel.SaveToImage(ctx, info.ImageId, locationId) - return "Saved", nil - }) - agentClient.ToolHandler.AddTool("reply", func(info client.ToolHandlerInfo, args string, call client.ToolCall) (any, error) { return "ok", nil }) diff --git a/backend/models/locations.go b/backend/models/locations.go index b3bfd6c..efa8f94 100644 --- a/backend/models/locations.go +++ b/backend/models/locations.go @@ -30,7 +30,50 @@ func (m LocationModel) List(ctx context.Context, userId uuid.UUID) ([]model.Loca return locations, err } +func (m LocationModel) Get(ctx context.Context, locationId uuid.UUID) (model.Locations, error) { + getLocationStmt := Locations. + SELECT(Locations.AllColumns). + WHERE(Locations.ID.EQ(UUID(locationId))) + + location := model.Locations{} + err := getLocationStmt.QueryContext(ctx, m.dbPool, &location) + + return location, err +} + +func (m LocationModel) Update(ctx context.Context, location model.Locations) (model.Locations, error) { + existingLocation, err := m.Get(ctx, location.ID) + if err != nil { + return model.Locations{}, err + } + + existingLocation.Name = location.Name + + if location.Description != nil { + existingLocation.Description = location.Description + } + + if location.Address != nil { + existingLocation.Address = location.Address + } + + updateLocationStmt := Locations. + UPDATE(Locations.MutableColumns). + MODEL(existingLocation). + WHERE(Locations.ID.EQ(UUID(location.ID))). + RETURNING(Locations.AllColumns) + + updatedLocation := model.Locations{} + err = updateLocationStmt.QueryContext(ctx, m.dbPool, &updatedLocation) + + return updatedLocation, err +} + func (m LocationModel) Save(ctx context.Context, userId uuid.UUID, location model.Locations) (model.Locations, error) { + if location.ID != uuid.Nil { + return m.Update(ctx, location) + } + insertLocationStmt := Locations. INSERT(Locations.Name, Locations.Address, Locations.Description). VALUES(location.Name, location.Address, location.Description).