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..caddbc4303 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) + 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"}) + + 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)