From 2d9d2f19c9411a73ce1f43273a7c12dae6283f43 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 22 Apr 2026 20:14:36 -0700 Subject: [PATCH] fix: support converse and 4.7 --- .../bedrock/chat/converse_transformation.py | 11 ++- ...odel_prices_and_context_window_backup.json | 33 ++++++--- model_prices_and_context_window.json | 33 ++++++--- .../chat/test_converse_transformation.py | 73 ++++++++++++++++++- 4 files changed, 121 insertions(+), 29 deletions(-) diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index db6784d042b..1fe64c3610e 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -59,6 +59,7 @@ from litellm.types.utils import ( Usage, ) from litellm.utils import ( + _supports_factory, add_dummy_tool, any_assistant_message_has_thinking_blocks, has_tool_call_blocks, @@ -1192,9 +1193,6 @@ class AmazonConverseConfig(BaseConfig): + supported_config_params ) inference_params.pop("json_mode", None) # used for handling json_schema - # Anthropic-only key. Bedrock expects `outputConfig` (camelCase) and - # will reject `output_config` if it leaks through pass-through routes. - inference_params.pop("output_config", None) # Extract requestMetadata before processing other parameters request_metadata = inference_params.pop("requestMetadata", None) @@ -1204,9 +1202,10 @@ class AmazonConverseConfig(BaseConfig): output_config: Optional[OutputConfigBlock] = inference_params.pop( "outputConfig", None ) - inference_params.pop( - "output_config", None - ) # Bedrock Converse doesn't support it + if not _supports_factory( + model=model, custom_llm_provider=None, key="supports_output_config" + ): + inference_params.pop("output_config", None) # keep supported params in 'inference_params', and set all model-specific params in 'additional_request_params' additional_request_params = { diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 26ccb5adf69..744d526cc1c 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1151,7 +1151,8 @@ "supports_vision": true, "supports_xhigh_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_output_config": true }, "global.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.25e-06, @@ -1179,7 +1180,8 @@ "supports_vision": true, "supports_xhigh_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_output_config": true }, "us.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.875e-06, @@ -1207,7 +1209,8 @@ "supports_vision": true, "supports_xhigh_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_output_config": true }, "eu.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.875e-06, @@ -1235,7 +1238,8 @@ "supports_vision": true, "supports_xhigh_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_output_config": true }, "au.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.875e-06, @@ -1263,7 +1267,8 @@ "supports_vision": true, "supports_xhigh_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_output_config": true }, "anthropic.claude-sonnet-4-6": { "cache_creation_input_token_cost": 3.75e-06, @@ -1950,7 +1955,8 @@ "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, - "tool_use_system_prompt_tokens": 159 + "tool_use_system_prompt_tokens": 159, + "supports_output_config": true }, "azure_ai/claude-opus-4-1": { "cache_creation_input_token_cost": 1.875e-05, @@ -9182,7 +9188,8 @@ "provider_specific_entry": { "us": 1.1, "fast": 6.0 - } + }, + "supports_output_config": true }, "claude-opus-4-7-20260416": { "cache_creation_input_token_cost": 6.25e-06, @@ -9214,7 +9221,8 @@ "provider_specific_entry": { "us": 1.1, "fast": 6.0 - } + }, + "supports_output_config": true }, "claude-sonnet-4-20250514": { "deprecation_date": "2026-05-14", @@ -27026,7 +27034,8 @@ "mode": "responses", "supports_web_search": true, "supports_reasoning": false, - "supports_function_calling": true + "supports_function_calling": true, + "supports_output_config": true }, "perplexity/anthropic/claude-opus-4-5": { "litellm_provider": "perplexity", @@ -31447,7 +31456,8 @@ "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, - "tool_use_system_prompt_tokens": 346 + "tool_use_system_prompt_tokens": 346, + "supports_output_config": true }, "vertex_ai/claude-opus-4-7@default": { "cache_creation_input_token_cost": 6.25e-06, @@ -31474,7 +31484,8 @@ "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, - "tool_use_system_prompt_tokens": 346 + "tool_use_system_prompt_tokens": 346, + "supports_output_config": true }, "vertex_ai/claude-sonnet-4-5": { "cache_creation_input_token_cost": 3.75e-06, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 26ccb5adf69..744d526cc1c 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1151,7 +1151,8 @@ "supports_vision": true, "supports_xhigh_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_output_config": true }, "global.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.25e-06, @@ -1179,7 +1180,8 @@ "supports_vision": true, "supports_xhigh_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_output_config": true }, "us.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.875e-06, @@ -1207,7 +1209,8 @@ "supports_vision": true, "supports_xhigh_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_output_config": true }, "eu.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.875e-06, @@ -1235,7 +1238,8 @@ "supports_vision": true, "supports_xhigh_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_output_config": true }, "au.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.875e-06, @@ -1263,7 +1267,8 @@ "supports_vision": true, "supports_xhigh_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, - "supports_native_structured_output": true + "supports_native_structured_output": true, + "supports_output_config": true }, "anthropic.claude-sonnet-4-6": { "cache_creation_input_token_cost": 3.75e-06, @@ -1950,7 +1955,8 @@ "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, - "tool_use_system_prompt_tokens": 159 + "tool_use_system_prompt_tokens": 159, + "supports_output_config": true }, "azure_ai/claude-opus-4-1": { "cache_creation_input_token_cost": 1.875e-05, @@ -9182,7 +9188,8 @@ "provider_specific_entry": { "us": 1.1, "fast": 6.0 - } + }, + "supports_output_config": true }, "claude-opus-4-7-20260416": { "cache_creation_input_token_cost": 6.25e-06, @@ -9214,7 +9221,8 @@ "provider_specific_entry": { "us": 1.1, "fast": 6.0 - } + }, + "supports_output_config": true }, "claude-sonnet-4-20250514": { "deprecation_date": "2026-05-14", @@ -27026,7 +27034,8 @@ "mode": "responses", "supports_web_search": true, "supports_reasoning": false, - "supports_function_calling": true + "supports_function_calling": true, + "supports_output_config": true }, "perplexity/anthropic/claude-opus-4-5": { "litellm_provider": "perplexity", @@ -31447,7 +31456,8 @@ "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, - "tool_use_system_prompt_tokens": 346 + "tool_use_system_prompt_tokens": 346, + "supports_output_config": true }, "vertex_ai/claude-opus-4-7@default": { "cache_creation_input_token_cost": 6.25e-06, @@ -31474,7 +31484,8 @@ "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, - "tool_use_system_prompt_tokens": 346 + "tool_use_system_prompt_tokens": 346, + "supports_output_config": true }, "vertex_ai/claude-sonnet-4-5": { "cache_creation_input_token_cost": 3.75e-06, 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 38a59c694e7..a5f2e26aa1f 100644 --- a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py @@ -3286,7 +3286,8 @@ def test_transform_request_with_output_config(): def test_transform_request_strips_anthropic_output_config(): """ - output_config is Anthropic-specific and must never be forwarded to Bedrock. + output_config is Anthropic-specific and must be stripped for models that + don't declare supports_output_config (e.g. Nova). """ config = AmazonConverseConfig() messages = [{"role": "user", "content": "hello"}] @@ -3307,6 +3308,76 @@ def test_transform_request_strips_anthropic_output_config(): assert "output_config" not in additional_fields +def test_transform_request_forwards_output_config_for_claude_4_6(): + """ + For Bedrock Converse models that declare supports_output_config=true + (Claude 4.6+), the Anthropic-style `output_config` must flow through + `additionalModelRequestFields` so Bedrock forwards the `effort` setting + to the Anthropic backend. + """ + from unittest.mock import patch + + config = AmazonConverseConfig() + messages = [{"role": "user", "content": "hello"}] + + with patch( + "litellm.llms.bedrock.chat.converse_transformation._supports_factory", + return_value=True, + ): + result = config._transform_request( + model="us.anthropic.claude-opus-4-6-v1", + messages=messages, + optional_params={ + "maxTokens": 64, + "output_config": {"effort": "high"}, + }, + litellm_params={}, + headers={}, + ) + + additional_fields = result.get("additionalModelRequestFields", {}) + assert additional_fields.get("output_config") == {"effort": "high"} + # Native Bedrock `outputConfig` (structured outputs) should not be set. + assert "outputConfig" not in result + + +def test_transform_request_forwards_output_config_for_claude_4_7(): + """ + Claude Opus 4.7 on Bedrock Converse must preserve output_config via + additionalModelRequestFields. Reads the real flag from the bundled cost + JSON so a regression in model_prices_and_context_window.json would trip + this test. + """ + old_env = os.environ.get("LITELLM_LOCAL_MODEL_COST_MAP") + old_cost = litellm.model_cost + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + try: + config = AmazonConverseConfig() + messages = [{"role": "user", "content": "hello"}] + + result = config._transform_request( + model="us.anthropic.claude-opus-4-7", + messages=messages, + optional_params={ + "maxTokens": 64, + "output_config": {"effort": "max"}, + }, + litellm_params={}, + headers={}, + ) + + additional_fields = result.get("additionalModelRequestFields", {}) + assert additional_fields.get("output_config") == {"effort": "max"} + assert "outputConfig" not in result + finally: + litellm.model_cost = old_cost + if old_env is None: + os.environ.pop("LITELLM_LOCAL_MODEL_COST_MAP", None) + else: + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = old_env + + def test_transform_response_native_structured_output(): """Test response handling when model returns JSON as text content (native structured output).""" response_json = {