From 1ad09f00dcfa95a57c960106609bdaf9e4b46574 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:12:44 -0500 Subject: [PATCH] register find_similar_objects chat tool definition --- frigate/api/chat.py | 70 +++++++++++++++++++ .../test/test_chat_find_similar_objects.py | 33 +++++++++ 2 files changed, 103 insertions(+) diff --git a/frigate/api/chat.py b/frigate/api/chat.py index ad797f8f4..41413048c 100644 --- a/frigate/api/chat.py +++ b/frigate/api/chat.py @@ -251,6 +251,76 @@ def get_tool_definitions() -> List[Dict[str, Any]]: "required": [], }, }, + { + "type": "function", + "function": { + "name": "find_similar_objects", + "description": ( + "Find tracked objects that are visually and semantically similar " + "to a specific past event. Use this when the user references a " + "particular object they have seen and wants to find other " + "sightings of the same or similar one ('that green car', 'the " + "person in the red jacket', 'the package that was delivered'). " + "Prefer this over search_objects whenever the user's intent is " + "'find more like this specific one.' Use search_objects first " + "only if you need to locate the anchor event. Requires semantic " + "search to be enabled." + ), + "parameters": { + "type": "object", + "properties": { + "event_id": { + "type": "string", + "description": "The id of the anchor event to find similar objects to.", + }, + "after": { + "type": "string", + "description": "Start time in ISO 8601 format (e.g., '2024-01-01T00:00:00Z').", + }, + "before": { + "type": "string", + "description": "End time in ISO 8601 format (e.g., '2024-01-01T23:59:59Z').", + }, + "cameras": { + "type": "array", + "items": {"type": "string"}, + "description": "Optional list of cameras to restrict to. Defaults to all.", + }, + "labels": { + "type": "array", + "items": {"type": "string"}, + "description": "Optional list of labels to restrict to. Defaults to the anchor event's label.", + }, + "sub_labels": { + "type": "array", + "items": {"type": "string"}, + "description": "Optional list of sub_labels (names) to restrict to.", + }, + "zones": { + "type": "array", + "items": {"type": "string"}, + "description": "Optional list of zones. An event matches if any of its zones overlap.", + }, + "similarity_mode": { + "type": "string", + "enum": ["visual", "semantic", "fused"], + "description": "Which similarity signal(s) to use. 'fused' (default) combines visual and semantic.", + "default": "fused", + }, + "min_score": { + "type": "number", + "description": "Drop matches with a similarity score below this threshold (0.0-1.0).", + }, + "limit": { + "type": "integer", + "description": "Maximum number of matches to return (default: 10).", + "default": 10, + }, + }, + "required": ["event_id"], + }, + }, + }, { "type": "function", "function": { diff --git a/frigate/test/test_chat_find_similar_objects.py b/frigate/test/test_chat_find_similar_objects.py index bb6577ac6..7987f6588 100644 --- a/frigate/test/test_chat_find_similar_objects.py +++ b/frigate/test/test_chat_find_similar_objects.py @@ -202,5 +202,38 @@ class TestBuildSimilarCandidatesQuery(unittest.TestCase): self.assertNotIn("e0000", ids) +from frigate.api.chat import get_tool_definitions + + +class TestToolDefinition(unittest.TestCase): + def test_find_similar_objects_is_registered(self): + tools = get_tool_definitions() + names = [t["function"]["name"] for t in tools] + self.assertIn("find_similar_objects", names) + + def test_find_similar_objects_schema(self): + tools = get_tool_definitions() + tool = next( + t for t in tools if t["function"]["name"] == "find_similar_objects" + ) + params = tool["function"]["parameters"]["properties"] + self.assertIn("event_id", params) + self.assertIn("after", params) + self.assertIn("before", params) + self.assertIn("cameras", params) + self.assertIn("labels", params) + self.assertIn("sub_labels", params) + self.assertIn("zones", params) + self.assertIn("similarity_mode", params) + self.assertIn("min_score", params) + self.assertIn("limit", params) + self.assertEqual( + tool["function"]["parameters"]["required"], ["event_id"] + ) + self.assertEqual( + params["similarity_mode"]["enum"], ["visual", "semantic", "fused"] + ) + + if __name__ == "__main__": unittest.main()