From 7b2ecb8a9a83ebfb6a7ab4ea598a244c588f90c8 Mon Sep 17 00:00:00 2001 From: Luca Steingen <33423679+LucaStngn@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:26:40 +0100 Subject: [PATCH] Fix: Enable custom function tools with Anthropic models This commit fixes three bugs that prevented custom function tools from working alongside the computer tool with Anthropic Claude models: 1. Tool format conversion: Convert OpenAI format to Anthropic format - Changed from {type, function: {parameters}} to {name, description, input_schema} 2. Response handling (content format): Add custom tool detection - Check tool name and handle custom tools separately from computer actions - Prevents 'Unknown action type: None' errors 3. Response handling (tool_calls format): Add custom tool handling - Parse JSON arguments and route custom tools correctly - Ensures compatibility with litellm's normalized response format These changes enable developers to use custom Python functions as tools alongside computer control tools without breaking existing functionality. Fixes: Custom function tools now work correctly with Anthropic models. --- libs/python/agent/agent/loops/anthropic.py | 44 ++++++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/libs/python/agent/agent/loops/anthropic.py b/libs/python/agent/agent/loops/anthropic.py index 2a3dffd5..42e33b5d 100644 --- a/libs/python/agent/agent/loops/anthropic.py +++ b/libs/python/agent/agent/loops/anthropic.py @@ -107,12 +107,9 @@ async def _prepare_tools_for_anthropic(tool_schemas: List[Dict[str, Any]], model function_schema = schema["function"] anthropic_tools.append( { - "type": "function", - "function": { - "name": function_schema["name"], - "description": function_schema.get("description", ""), - "parameters": function_schema.get("parameters", {}), - }, + "name": function_schema["name"], + "description": function_schema.get("description", ""), + "input_schema": function_schema.get("parameters", {}), } ) @@ -666,11 +663,24 @@ def _convert_completion_to_responses_items(response: Any) -> List[Dict[str, Any] if content_item.get("type") == "text": responses_items.append(make_output_text_item(content_item.get("text", ""))) elif content_item.get("type") == "tool_use": - # Convert tool use to computer call + # Check if this is a custom function tool or computer tool + tool_name = content_item.get("name", "computer") tool_input = content_item.get("input", {}) - action_type = tool_input.get("action") call_id = content_item.get("id") + # Handle custom function tools (not computer tools) + if tool_name != "computer": + from ..responses import make_function_call_item + responses_items.append(make_function_call_item( + function_name=tool_name, + arguments=tool_input, + call_id=call_id + )) + continue + + # Computer tool - process actions + action_type = tool_input.get("action") + # Action reference: # https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/computer-use-tool#available-actions @@ -868,6 +878,24 @@ def _convert_completion_to_responses_items(response: Any) -> List[Dict[str, Any] # Handle tool calls (alternative format) if hasattr(message, "tool_calls") and message.tool_calls: for tool_call in message.tool_calls: + tool_name = tool_call.function.name + + # Handle custom function tools + if tool_name != "computer": + from ..responses import make_function_call_item + # tool_call.function.arguments is a JSON string, need to parse it + try: + args_dict = json.loads(tool_call.function.arguments) + except json.JSONDecodeError: + args_dict = {} + responses_items.append(make_function_call_item( + function_name=tool_name, + arguments=args_dict, + call_id=tool_call.id + )) + continue + + # Handle computer tool if tool_call.function.name == "computer": try: try: