diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index db9c8a5cedd..395d99a4caa 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -1631,6 +1631,11 @@ class AmazonConverseConfig(BaseConfig): bedrock_tool_config["toolChoice"] = tool_choice_values self._drop_tool_choice_type_conflicting_with_tool_config(additional_request_params) + config_block_entries: Final = tuple( + (config_name, config_class, inference_params.pop(config_name, None)) + for config_name, config_class in self.get_config_blocks().items() + ) + data: Final[CommonRequestObject] = { "inferenceConfig": self._transform_inference_params(inference_params=inference_params), } @@ -1641,9 +1646,7 @@ class AmazonConverseConfig(BaseConfig): if system_content_blocks: data["system"] = system_content_blocks - # Handle all config blocks - for config_name, config_class in self.get_config_blocks().items(): - config_value = inference_params.pop(config_name, None) + for config_name, config_class, config_value in config_block_entries: if config_value is not None: data[config_name] = config_class(**config_value) 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 226bba6826a..63f895e1819 100644 --- a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py @@ -957,6 +957,28 @@ def test_transform_request_helper_includes_anthropic_beta_and_tools(): assert fields["tools"][0]["type"] == "computer_20250124" +def test_config_blocks_do_not_leak_into_inference_config(): + """Regression: inferenceConfig was built before the config blocks were popped, so a dead + nested copy of each block (guardrailConfig, performanceConfig, serviceTier) rode inside + inferenceConfig alongside the real top-level one.""" + data = AmazonConverseConfig()._transform_request_helper( + model="anthropic.claude-haiku-4-5-20251001-v1:0", + system_content_blocks=[], + optional_params={ + "maxTokens": 100, + "guardrailConfig": {"guardrailIdentifier": "gr-id", "guardrailVersion": "DRAFT"}, + "performanceConfig": {"latency": "optimized"}, + "serviceTier": {"type": "priority"}, + }, + messages=[{"role": "user", "content": "hi"}], + ) + + assert data["inferenceConfig"] == {"maxTokens": 100} + assert data["guardrailConfig"] == {"guardrailIdentifier": "gr-id", "guardrailVersion": "DRAFT"} + assert data["performanceConfig"] == {"latency": "optimized"} + assert data["serviceTier"] == {"type": "priority"} + + def test_parallel_tool_calls_config_kept_for_sonnet_5(monkeypatch): old_env = os.environ.get("LITELLM_LOCAL_MODEL_COST_MAP") old_cost = litellm.model_cost @@ -2853,17 +2875,11 @@ def test_guarded_text_guardrail_config_preserved(): headers={}, ) - # GuardrailConfig should be present at top level assert "guardrailConfig" in result assert result["guardrailConfig"]["guardrailIdentifier"] == "gr-abc123" - # GuardrailConfig should also be in inferenceConfig assert "inferenceConfig" in result - assert "guardrailConfig" in result["inferenceConfig"] - assert ( - result["inferenceConfig"]["guardrailConfig"]["guardrailIdentifier"] - == "gr-abc123" - ) + assert "guardrailConfig" not in result["inferenceConfig"] def test_auto_convert_last_user_message_to_guarded_text():