diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index ac3c92d1fed..23bf2d841dd 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -1300,10 +1300,12 @@ class AmazonConverseConfig(BaseConfig): continue filtered_tools.append(tool) + # Defensive copy so we don't mutate caller data (router fallback/retry may reuse optional_params). # Strip custom field from tools before sending to Bedrock Converse. # Claude Code sends custom: {eager_input_streaming: true} etc. which Anthropic # accepts but Bedrock rejects for some models (e.g. Haiku 4.5) with # "Extra inputs are not permitted". Ref: https://github.com/BerriAI/litellm/issues/23825 + filtered_tools = [copy.deepcopy(t) for t in filtered_tools] strip_custom_from_tools_list(filtered_tools) # Only separate tools if computer use tools are actually present diff --git a/litellm/llms/bedrock/common_utils.py b/litellm/llms/bedrock/common_utils.py index 630808aa58b..e753dbc2bf9 100644 --- a/litellm/llms/bedrock/common_utils.py +++ b/litellm/llms/bedrock/common_utils.py @@ -62,10 +62,7 @@ def remove_custom_field_from_tools(request_body: dict) -> None: Ref: https://github.com/BerriAI/litellm/issues/22847 """ - tools = request_body.get("tools") - if not tools or not isinstance(tools, list): - return - strip_custom_from_tools_list(tools) + strip_custom_from_tools_list(request_body.get("tools")) def strip_custom_from_tools_list(tools: list) -> None: diff --git a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py index cff0d20cee8..944a9ae48d4 100644 --- a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py @@ -354,8 +354,8 @@ def test_process_tools_and_beta_strips_custom_field(): assert len(bedrock_tools) == 1 assert "toolSpec" in bedrock_tools[0] assert bedrock_tools[0]["toolSpec"]["name"] == "get_weather" - # Original tools list should have been modified in-place (custom stripped) - assert "custom" not in tools_with_custom[0] + # Caller data should not be mutated (defensive copy used for router fallback safety) + assert "custom" in tools_with_custom[0] def test_transform_request_helper_includes_anthropic_beta_and_tools():