From 6bd579c84e3b5681e97ed4facbe08e2ccad75192 Mon Sep 17 00:00:00 2001 From: kimnamu Date: Thu, 24 Sep 2026 16:30:53 +0900 Subject: [PATCH] fix(bedrock): rebuild the unforced json_tool_call tool instead of writing into it Greptile flagged the Invoke directive for mutating tool dicts in place. Anthropic-format tools reach map_openai_params as the caller's own dicts, so a caller tool that reused the reserved json_tool_call name would have carried the directive back into its later requests, and AGENTS.md rules out in-place mutation. Build a new dict for the json_tool_call tool and store the rebuilt list on optional_params, the way _add_tools_to_optional_params already does. Request bodies are unchanged. --- .../anthropic_claude3_transformation.py | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py index d585a943eef..dc3061009b5 100644 --- a/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py +++ b/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py @@ -124,13 +124,22 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig): and AnthropicModelInfo.forced_tool_use_unsupported(original_model) ): optional_params.pop("tool_choice") - if "response_format" in non_default_params and ( - self.is_thinking_enabled(non_default_params) - or AnthropicModelInfo.forced_tool_use_unsupported(original_model) + if ( + "response_format" in non_default_params + and "tools" in optional_params + and ( + self.is_thinking_enabled(non_default_params) + or AnthropicModelInfo.forced_tool_use_unsupported(original_model) + ) ): - for tool in optional_params.get("tools", ()): - if tool.get("name") == RESPONSE_FORMAT_TOOL_NAME: - tool["description"] = RESPONSE_FORMAT_UNFORCED_TOOL_DESCRIPTION + # Rebuild the tool instead of writing into it: Anthropic-format tools reach + # here as the caller's own dicts, and the repo rules out in-place mutation + optional_params["tools"] = [ # rebind-ok: out-param store like siblings + {**tool, "description": RESPONSE_FORMAT_UNFORCED_TOOL_DESCRIPTION} + if tool.get("name") == RESPONSE_FORMAT_TOOL_NAME + else tool + for tool in optional_params["tools"] + ] return optional_params