From ffc6d84f2762939d11cec4b0ee478e925134f441 Mon Sep 17 00:00:00 2001 From: netbrah <162479981+netbrah@users.noreply.github.com> Date: Sun, 8 Mar 2026 08:16:22 -0400 Subject: [PATCH] fix: shallow copy input_schema to avoid caller mutation + add mutation guard test Addresses Greptile review: - dict(_input_schema) before mutation prevents cross-provider state leakage - Test asserts original tool parameters dict is unchanged after call --- litellm/llms/anthropic/chat/transformation.py | 7 +++++++ .../chat/test_anthropic_chat_transformation.py | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 41fcd3e752a..fd1859f7d17 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -399,6 +399,13 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): # schemas from external sources (MCP servers, OpenAI callers) that # may omit the type field or use a non-object type. if _input_schema.get("type") != "object": + litellm.verbose_logger.debug( + "_map_tool_helper: coercing input_schema type from %r to " + "'object' for Anthropic compatibility (tool: %s)", + _input_schema.get("type"), + tool["function"].get("name"), + ) + _input_schema = dict(_input_schema) # avoid mutating caller's dict _input_schema["type"] = "object" if "properties" not in _input_schema: _input_schema["properties"] = {} diff --git a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py index a5388d19f2b..6f03f630b5f 100644 --- a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -3208,11 +3208,16 @@ def test_map_tool_helper_enforces_object_type_when_missing(): }, } + original_params = tool["function"]["parameters"].copy() result, _ = config._map_tool_helper(tool) assert result is not None assert result["input_schema"]["type"] == "object" assert "properties" in result["input_schema"] assert "query" in result["input_schema"]["properties"] + # Original parameters dict must not be modified in place + assert tool["function"]["parameters"] == original_params, ( + "parameters dict was mutated; _map_tool_helper should not modify caller data" + ) def test_map_tool_helper_enforces_object_type_when_wrong_type(): @@ -3234,9 +3239,17 @@ def test_map_tool_helper_enforces_object_type_when_wrong_type(): }, } + original_params = tool["function"]["parameters"].copy() result, _ = config._map_tool_helper(tool) assert result is not None assert result["input_schema"]["type"] == "object" + assert result["input_schema"].get("properties") == {}, ( + "properties should be injected as {} when schema has non-object type and no properties key" + ) + # Original parameters dict must not be modified in place + assert tool["function"]["parameters"] == original_params, ( + "parameters dict was mutated; _map_tool_helper should not modify caller data" + ) def test_map_tool_helper_preserves_valid_object_schema():