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 53e08229799..c936b2cd23c 100644 --- a/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py +++ b/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py @@ -53,13 +53,26 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig): model: str, drop_params: bool, ) -> dict: - return AnthropicConfig.map_openai_params( + # Force tool-based structured outputs for Bedrock Invoke + # (similar to VertexAI fix in #19201) + # Bedrock Invoke doesn't support output_format parameter + original_model = model + if "response_format" in non_default_params: + # Use a model name that forces tool-based approach + model = "claude-3-sonnet-20240229" + + optional_params = AnthropicConfig.map_openai_params( self, non_default_params, optional_params, model, drop_params, ) + + # Restore original model name + model = original_model + + return optional_params def transform_request( @@ -90,6 +103,8 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig): _anthropic_request.pop("model", None) _anthropic_request.pop("stream", None) + # Bedrock Invoke doesn't support output_format parameter + _anthropic_request.pop("output_format", None) if "anthropic_version" not in _anthropic_request: _anthropic_request["anthropic_version"] = self.anthropic_version @@ -117,6 +132,26 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig): if "opus-4" in model.lower() or "opus_4" in model.lower(): beta_set.add("tool-search-tool-2025-10-19") + # Filter out beta headers that Bedrock Invoke doesn't support + # AWS Bedrock only supports a specific whitelist of beta flags + # Reference: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages-request-response.html + BEDROCK_SUPPORTED_BETAS = { + "computer-use-2024-10-22", # Legacy computer use + "computer-use-2025-01-24", # Current computer use (Claude 3.7 Sonnet) + "token-efficient-tools-2025-02-19", # Tool use (Claude 3.7+ and Claude 4+) + "interleaved-thinking-2025-05-14", # Interleaved thinking (Claude 4+) + "output-128k-2025-02-19", # 128K output tokens (Claude 3.7 Sonnet) + "dev-full-thinking-2025-05-14", # Developer mode for raw thinking (Claude 4+) + "context-1m-2025-08-07", # 1 million tokens (Claude Sonnet 4) + "context-management-2025-06-27", # Context management (Claude Sonnet/Haiku 4.5) + "effort-2025-11-24", # Effort parameter (Claude Opus 4.5) + "tool-search-tool-2025-10-19", # Tool search (Claude Opus 4.5) + "tool-examples-2025-10-29", # Tool use examples (Claude Opus 4.5) + } + + # Only keep beta headers that Bedrock supports + beta_set = {beta for beta in beta_set if beta in BEDROCK_SUPPORTED_BETAS} + if beta_set: _anthropic_request["anthropic_beta"] = list(beta_set) diff --git a/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py b/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py index 98b392a353d..5c1b4cbd38e 100644 --- a/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py @@ -464,3 +464,108 @@ def test_opus_4_5_model_detection(): for model in non_opus_4_5_models: assert not config._is_claude_opus_4_5(model), \ f"Should not detect {model} as Opus 4.5" + + +def test_structured_outputs_beta_header_filtered_for_bedrock_invoke(): + """ + Test that unsupported beta headers are filtered out for Bedrock Invoke API. + + Bedrock Invoke API only supports a specific whitelist of beta flags and returns + "invalid beta flag" error for others (e.g., structured-outputs, mcp-servers). + This test ensures unsupported headers are filtered while keeping supported ones. + + Fixes: https://github.com/BerriAI/litellm/issues/16726 + """ + config = AmazonAnthropicClaudeConfig() + + messages = [{"role": "user", "content": "test"}] + + # Test 1: structured-outputs beta header (unsupported) + headers = {"anthropic-beta": "structured-outputs-2025-11-13"} + + result = config.transform_request( + model="anthropic.claude-4-0-sonnet-20250514-v1:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers=headers, + ) + + # Verify structured-outputs beta is filtered out + anthropic_beta = result.get("anthropic_beta", []) + assert not any("structured-outputs" in beta for beta in anthropic_beta), \ + f"structured-outputs beta should be filtered, got: {anthropic_beta}" + + # Test 2: mcp-servers beta header (unsupported - the main issue from #16726) + headers = {"anthropic-beta": "mcp-servers-2025-12-04"} + + result = config.transform_request( + model="anthropic.claude-4-0-sonnet-20250514-v1:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers=headers, + ) + + # Verify mcp-servers beta is filtered out + anthropic_beta = result.get("anthropic_beta", []) + assert not any("mcp-servers" in beta for beta in anthropic_beta), \ + f"mcp-servers beta should be filtered, got: {anthropic_beta}" + + # Test 3: Mix of supported and unsupported beta headers + headers = {"anthropic-beta": "computer-use-2024-10-22,mcp-servers-2025-12-04,structured-outputs-2025-11-13"} + + result = config.transform_request( + model="anthropic.claude-4-0-sonnet-20250514-v1:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers=headers, + ) + + # Verify only supported betas are kept + anthropic_beta = result.get("anthropic_beta", []) + assert not any("structured-outputs" in beta for beta in anthropic_beta), \ + f"structured-outputs beta should be filtered, got: {anthropic_beta}" + assert not any("mcp-servers" in beta for beta in anthropic_beta), \ + f"mcp-servers beta should be filtered, got: {anthropic_beta}" + assert any("computer-use" in beta for beta in anthropic_beta), \ + f"computer-use beta should be kept, got: {anthropic_beta}" + + +def test_output_format_removed_from_bedrock_invoke_request(): + """ + Test that output_format parameter is removed from Bedrock Invoke requests. + + Bedrock Invoke API doesn't support the output_format parameter (only supported + in Anthropic Messages API). This test ensures it's removed to prevent errors. + """ + config = AmazonAnthropicClaudeConfig() + + messages = [{"role": "user", "content": "test"}] + + # Create a request with output_format via map_openai_params + non_default_params = { + "response_format": {"type": "json_object"} + } + optional_params = {} + + # This should trigger tool-based structured outputs + optional_params = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model="anthropic.claude-4-0-sonnet-20250514-v1:0", + drop_params=False, + ) + + result = config.transform_request( + model="anthropic.claude-4-0-sonnet-20250514-v1:0", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={}, + ) + + # Verify output_format is not in the request + assert "output_format" not in result, \ + f"output_format should be removed for Bedrock Invoke, got keys: {result.keys()}"