From 5ac581087572f84518d47583503f9c0ad56161db Mon Sep 17 00:00:00 2001 From: VectorPeak Date: Fri, 3 Jul 2026 20:13:06 +0800 Subject: [PATCH 1/3] Python: Normalize Anthropic single tools --- .../agent_framework_anthropic/_chat_client.py | 4 +-- .../anthropic/tests/test_anthropic_client.py | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py index 4cab69446e..936d52e04d 100644 --- a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py +++ b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py @@ -28,7 +28,7 @@ ) from agent_framework._settings import SecretString, load_settings from agent_framework._telemetry import get_user_agent -from agent_framework._tools import SHELL_TOOL_KIND_VALUE +from agent_framework._tools import SHELL_TOOL_KIND_VALUE, normalize_tools from agent_framework._types import _get_data_bytes_as_str # type: ignore from agent_framework.observability import ChatTelemetryLayer from anthropic import AsyncAnthropic, AsyncAnthropicFoundry @@ -950,7 +950,7 @@ def _prepare_tools_for_anthropic(self, options: Mapping[str, Any]) -> dict[str, tool_list: list[Any] = [] mcp_server_list: list[Any] = [] tool_name_aliases: dict[str, str] = {} - for tool in tools: + for tool in normalize_tools(tools): if isinstance(tool, FunctionTool) and tool.kind == SHELL_TOOL_KIND_VALUE: api_type = (tool.additional_properties or {}).get("type", "bash_20250124") tool_name_aliases["bash"] = tool.name diff --git a/python/packages/anthropic/tests/test_anthropic_client.py b/python/packages/anthropic/tests/test_anthropic_client.py index 48d2cb6a81..2e629c6fb4 100644 --- a/python/packages/anthropic/tests/test_anthropic_client.py +++ b/python/packages/anthropic/tests/test_anthropic_client.py @@ -745,6 +745,27 @@ def get_weather( assert "Get weather for a location" in result["tools"][0]["description"] +def test_prepare_tools_for_anthropic_single_tool(mock_anthropic_client: MagicMock) -> None: + """Test converting a single FunctionTool to Anthropic format.""" + client = create_test_anthropic_client(mock_anthropic_client) + + @tool(approval_mode="never_require") + def get_weather( + location: Annotated[str, Field(description="Location to get weather for")], + ) -> str: + """Get weather for a location.""" + return f"Weather for {location}" + + chat_options = ChatOptions(tools=get_weather) # type: ignore[arg-type] + result = client._prepare_tools_for_anthropic(chat_options) + + assert result is not None + assert "tools" in result + assert len(result["tools"]) == 1 + assert result["tools"][0]["type"] == "custom" + assert result["tools"][0]["name"] == "get_weather" + + def test_prepare_tools_for_anthropic_web_search( mock_anthropic_client: MagicMock, ) -> None: @@ -918,6 +939,21 @@ def test_prepare_tools_for_anthropic_dict_tool( assert result["tools"][0]["name"] == "custom_tool" +def test_prepare_tools_for_anthropic_single_dict_tool( + mock_anthropic_client: MagicMock, +) -> None: + """Test passing through a single dict tool.""" + client = create_test_anthropic_client(mock_anthropic_client) + chat_options = ChatOptions(tools={"type": "custom", "name": "custom_tool", "description": "A custom tool"}) # type: ignore[arg-type] + + result = client._prepare_tools_for_anthropic(chat_options) + + assert result is not None + assert "tools" in result + assert len(result["tools"]) == 1 + assert result["tools"][0]["name"] == "custom_tool" + + def test_prepare_tools_for_anthropic_none(mock_anthropic_client: MagicMock) -> None: """Test converting None tools.""" client = create_test_anthropic_client(mock_anthropic_client) From c4ed207b9407585a4776306263dac8b26ee1de37 Mon Sep 17 00:00:00 2001 From: VectorPeak Date: Fri, 3 Jul 2026 20:19:27 +0800 Subject: [PATCH 2/3] Potential fix for pull request finding Thanks Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- python/packages/anthropic/tests/test_anthropic_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/packages/anthropic/tests/test_anthropic_client.py b/python/packages/anthropic/tests/test_anthropic_client.py index 2e629c6fb4..8afab4e942 100644 --- a/python/packages/anthropic/tests/test_anthropic_client.py +++ b/python/packages/anthropic/tests/test_anthropic_client.py @@ -756,7 +756,7 @@ def get_weather( """Get weather for a location.""" return f"Weather for {location}" - chat_options = ChatOptions(tools=get_weather) # type: ignore[arg-type] + chat_options = ChatOptions(tools=get_weather) result = client._prepare_tools_for_anthropic(chat_options) assert result is not None From d9e11e647199fdb6ab455e304a6980258f7a85a2 Mon Sep 17 00:00:00 2001 From: VectorPeak Date: Fri, 3 Jul 2026 20:36:47 +0800 Subject: [PATCH 3/3] Python: Address Anthropic review feedback --- python/packages/anthropic/tests/test_anthropic_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/packages/anthropic/tests/test_anthropic_client.py b/python/packages/anthropic/tests/test_anthropic_client.py index 8afab4e942..caddbc4303 100644 --- a/python/packages/anthropic/tests/test_anthropic_client.py +++ b/python/packages/anthropic/tests/test_anthropic_client.py @@ -944,7 +944,7 @@ def test_prepare_tools_for_anthropic_single_dict_tool( ) -> None: """Test passing through a single dict tool.""" client = create_test_anthropic_client(mock_anthropic_client) - chat_options = ChatOptions(tools={"type": "custom", "name": "custom_tool", "description": "A custom tool"}) # type: ignore[arg-type] + chat_options = ChatOptions(tools={"type": "custom", "name": "custom_tool", "description": "A custom tool"}) result = client._prepare_tools_for_anthropic(chat_options)