From 24d6accc61ae8deef39937b2bdd5efcffbea7a7b Mon Sep 17 00:00:00 2001 From: Maruf Aytekin Date: Tue, 25 Feb 2025 10:30:47 -0500 Subject: [PATCH] [Bug Fix] Tool signature error for Anthropic #1091 (#1101) * [Bug Fix] Tool signature error for Anthropic #1091 * tests added * updated tests to reflect test coverage * pre-commit checks --------- Co-authored-by: Davor Runje --- .secrets.baseline | 6 ++-- autogen/oai/anthropic.py | 35 ++++++++++++++++++-- test/oai/test_anthropic.py | 68 +++++++++++++++++++++++++++++++++++++- 3 files changed, 103 insertions(+), 6 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 56d87d44a1..2ac6954fab 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -911,7 +911,7 @@ "filename": "test/oai/test_anthropic.py", "hashed_secret": "9e712c7fd95990477f7c83991f86583883fa7260", "is_verified": false, - "line_number": 51, + "line_number": 50, "is_secret": false }, { @@ -919,7 +919,7 @@ "filename": "test/oai/test_anthropic.py", "hashed_secret": "1250ccf39e681decf5b51332888b7ccfc9a05227", "is_verified": false, - "line_number": 71, + "line_number": 70, "is_secret": false } ], @@ -1596,5 +1596,5 @@ } ] }, - "generated_at": "2025-02-24T18:10:39Z" + "generated_at": "2025-02-25T12:38:52Z" } diff --git a/autogen/oai/anthropic.py b/autogen/oai/anthropic.py index a1d1e95003..6c67711a8b 100644 --- a/autogen/oai/anthropic.py +++ b/autogen/oai/anthropic.py @@ -365,11 +365,42 @@ def get_usage(response: ChatCompletion) -> dict: @staticmethod def convert_tools_to_functions(tools: list) -> list: + """ + Convert tool definitions into Anthropic-compatible functions, + updating nested $ref paths in property schemas. + + Args: + tools (list): List of tool definitions. + + Returns: + list: List of functions with updated $ref paths. + """ + + def update_refs(obj, defs_keys, prop_name): + """Recursively update $ref values that start with "#/$defs/".""" + if isinstance(obj, dict): + for key, value in obj.items(): + if key == "$ref" and isinstance(value, str) and value.startswith("#/$defs/"): + ref_key = value[len("#/$defs/") :] + if ref_key in defs_keys: + obj[key] = f"#/properties/{prop_name}/$defs/{ref_key}" + else: + update_refs(value, defs_keys, prop_name) + elif isinstance(obj, list): + for item in obj: + update_refs(item, defs_keys, prop_name) + functions = [] for tool in tools: if tool.get("type") == "function" and "function" in tool: - functions.append(tool["function"]) - + function = tool["function"] + parameters = function.get("parameters", {}) + properties = parameters.get("properties", {}) + for prop_name, prop_schema in properties.items(): + if "$defs" in prop_schema: + defs_keys = set(prop_schema["$defs"].keys()) + update_refs(prop_schema, defs_keys, prop_name) + functions.append(function) return functions def _add_response_format_to_system(self, params: dict[str, Any]): diff --git a/test/oai/test_anthropic.py b/test/oai/test_anthropic.py index 639c2f36f5..f9e22fedd1 100644 --- a/test/oai/test_anthropic.py +++ b/test/oai/test_anthropic.py @@ -6,7 +6,6 @@ # SPDX-License-Identifier: MIT # !/usr/bin/env python3 -m pytest - import pytest from autogen.import_utils import optional_import_block, skip_on_missing_imports @@ -265,3 +264,70 @@ class MathReasoning(BaseModel): with pytest.raises(ValueError, match="No valid JSON found in response for Structured Output."): anthropic_client._extract_json_response(no_json_response) + + +@skip_on_missing_imports(["anthropic"], "anthropic") +def test_convert_tools_to_functions(anthropic_client): + tools = [ + { + "type": "function", + "function": { + "description": "weather tool", + "name": "weather_tool", + "parameters": { + "type": "object", + "properties": { + "city_name": {"type": "string", "description": "city_name"}, + "city_list": { + "$defs": { + "city_list_class": { + "properties": { + "item1": {"title": "Item1", "type": "string"}, + "item2": {"title": "Item2", "type": "string"}, + }, + "required": ["item1", "item2"], + "title": "city_list_class", + "type": "object", + } + }, + "items": {"$ref": "#/$defs/city_list_class"}, + "type": "array", + "description": "city_list", + }, + }, + "required": ["city_name", "city_list"], + }, + }, + } + ] + expected = [ + { + "description": "weather tool", + "name": "weather_tool", + "parameters": { + "type": "object", + "properties": { + "city_name": {"type": "string", "description": "city_name"}, + "city_list": { + "$defs": { + "city_list_class": { + "properties": { + "item1": {"title": "Item1", "type": "string"}, + "item2": {"title": "Item2", "type": "string"}, + }, + "required": ["item1", "item2"], + "title": "city_list_class", + "type": "object", + } + }, + "items": {"$ref": "#/properties/city_list/$defs/city_list_class"}, + "type": "array", + "description": "city_list", + }, + }, + "required": ["city_name", "city_list"], + }, + } + ] + actual = anthropic_client.convert_tools_to_functions(tools=tools) + assert actual == expected