From 78b773f5aee772d153ca24e5ee2c36cfc52f7967 Mon Sep 17 00:00:00 2001 From: oss-agent-shin <279349115+oss-agent-shin@users.noreply.github.com> Date: Wed, 6 May 2026 04:40:42 +0000 Subject: [PATCH] Fix responses tool conversion for chat bridge Co-authored-by: ishaan-berri --- .../transformation.py | 24 +++++++-- .../test_litellm_completion_responses.py | 51 +++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/litellm/responses/litellm_completion_transformation/transformation.py b/litellm/responses/litellm_completion_transformation/transformation.py index 48b12a5fba9..8f7bcb9e49f 100644 --- a/litellm/responses/litellm_completion_transformation/transformation.py +++ b/litellm/responses/litellm_completion_transformation/transformation.py @@ -1384,17 +1384,31 @@ class LiteLLMCompletionResponsesConfig: ) elif tool.get("type") == "function": typed_tool = cast(FunctionToolParam, tool) + function_dict = cast(Dict[str, Any], tool.get("function") or {}) # Ensure parameters has "type": "object" as required by providers like Anthropic - parameters = dict(typed_tool.get("parameters", {}) or {}) + parameters = dict( + function_dict.get("parameters") + or typed_tool.get("parameters", {}) + or {} + ) if not parameters or "type" not in parameters: parameters["type"] = "object" + strict = ( + function_dict.get("strict") + if function_dict.get("strict") is not None + else typed_tool.get("strict", False) + ) chat_completion_tool: Dict[str, Any] = { "type": "function", "function": { - "name": typed_tool.get("name") or "", - "description": typed_tool.get("description") or "", + "name": function_dict.get("name") + or typed_tool.get("name") + or "", + "description": function_dict.get("description") + or typed_tool.get("description") + or "", "parameters": parameters, - "strict": typed_tool.get("strict", False) or False, + "strict": strict or False, }, } if tool.get("cache_control"): @@ -1408,6 +1422,8 @@ class LiteLLMCompletionResponsesConfig: chat_completion_tools.append( cast(ChatCompletionToolParam, chat_completion_tool) ) + elif tool.get("type") in {"custom", "shell"}: + continue else: chat_completion_tools.append( cast(Union[ChatCompletionToolParam, OpenAIMcpServerTool], tool) diff --git a/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py b/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py index 5d8ff8022e5..9b94563403b 100644 --- a/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py +++ b/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py @@ -1189,6 +1189,57 @@ class TestToolTransformation: assert "allowed_callers" not in result_tool assert "input_examples" not in result_tool + def test_transform_function_tools_reads_chat_completion_style_function_fields(self): + """Chat Completion-style nested function tools should keep their metadata.""" + function_tool = { + "type": "function", + "function": { + "name": "read", + "description": "Read a file", + "parameters": { + "type": "object", + "properties": {"path": {"type": "string"}}, + }, + "strict": True, + }, + } + + ( + result_tools, + _, + ) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools( + tools=[function_tool] + ) + + assert len(result_tools) == 1 + result_tool = result_tools[0] + assert result_tool["type"] == "function" + assert result_tool["function"] == function_tool["function"] + + def test_transform_unsupported_responses_api_tool_types_are_dropped(self): + """Responses-only tool types should not be forwarded to Chat Completions APIs.""" + tools = [ + {"type": "custom", "name": "shell", "description": "Run shell commands"}, + {"type": "shell", "name": "shell"}, + { + "type": "function", + "name": "get_weather", + "description": "Get weather", + "parameters": {"type": "object"}, + }, + ] + + ( + result_tools, + _, + ) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools( + tools=tools + ) + + assert len(result_tools) == 1 + assert result_tools[0]["type"] == "function" + assert result_tools[0]["function"]["name"] == "get_weather" + def test_transform_code_execution_tools(self): """Test that code_execution tools are passed through as-is""" code_execution_tool = {