From 153e502e347e019c235831b17550b91663185757 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 20 Apr 2026 17:11:46 +0530 Subject: [PATCH] fix(anthropic): allow max output effort via model metadata Make Anthropic output_config max-effort validation fully model-map driven and add max support metadata for Opus 4.7 + Opus 4.6 alias coverage, with focused tests for supported/unsupported models. Made-with: Cursor --- litellm/llms/anthropic/chat/transformation.py | 25 +++--------------- ...odel_prices_and_context_window_backup.json | 20 ++++++++++---- model_prices_and_context_window.json | 20 ++++++++++---- .../test_anthropic_chat_transformation.py | 26 ++++++++++++++----- 4 files changed, 53 insertions(+), 38 deletions(-) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index cd5bb731717..8782874dbed 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -182,22 +182,6 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): tool_call["caller"] = cast(Dict[str, Any], anthropic_tool_content["caller"]) # type: ignore[typeddict-item] return tool_call - @staticmethod - def _is_opus_4_6_model(model: str) -> bool: - """Check if the model is specifically Claude Opus 4.6.""" - model_lower = model.lower() - return any( - v in model_lower for v in ("opus-4-6", "opus_4_6", "opus-4.6", "opus_4.6") - ) - - @staticmethod - def _is_opus_4_7_model(model: str) -> bool: - """Check if the model is specifically Claude Opus 4.7.""" - model_lower = model.lower() - return any( - v in model_lower for v in ("opus-4-7", "opus_4_7", "opus-4.7", "opus_4.7") - ) - @staticmethod def _supports_effort_level(model: str, level: str) -> bool: """Check ``supports_{level}_reasoning_effort`` in the model map. @@ -1534,12 +1518,11 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): f"Invalid effort value: {effort}. Must be one of: " f"'high', 'medium', 'low', 'xhigh', 'max'" ) - # ``max`` is Claude Opus 4.6 only (not Sonnet 4.6, not Opus 4.5/4.7). - # Keep this hardcoded so the error message is specific and stable. - if effort == "max" and not self._is_opus_4_6_model(model): + # ``max`` support is model-map driven via ``supports_max_reasoning_effort``. + supports_max = self._supports_effort_level(model, "max") + if effort == "max" and not supports_max: raise ValueError( - f"effort='max' is only supported by Claude Opus 4.6. " - f"Got model: {model}" + f"effort='max' is not supported by this model. Got model: {model}" ) # ``xhigh`` is data-driven via ``supports_xhigh_reasoning_effort`` so # enabling it for a new model is a pure model-map change. diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 04b68b8f4ec..14d3c1ae846 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1146,7 +1146,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_max_reasoning_effort": true }, "global.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.25e-06, @@ -1174,7 +1175,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_max_reasoning_effort": true }, "us.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.875e-06, @@ -1202,7 +1204,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_max_reasoning_effort": true }, "eu.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.875e-06, @@ -1230,7 +1233,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_max_reasoning_effort": true }, "au.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.875e-06, @@ -1258,7 +1262,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_max_reasoning_effort": true }, "anthropic.claude-sonnet-4-6": { "cache_creation_input_token_cost": 3.75e-06, @@ -9079,6 +9084,9 @@ "cache_read_input_token_cost": 5e-07, "input_cost_per_token": 5e-06, "litellm_provider": "anthropic", + "aliases": [ + "claude-opus-4-6-20250514" + ], "max_input_tokens": 1000000, "max_output_tokens": 128000, "max_tokens": 128000, @@ -9163,6 +9171,7 @@ "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, + "supports_max_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, "provider_specific_entry": { "us": 1.1, @@ -9195,6 +9204,7 @@ "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, + "supports_max_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, "provider_specific_entry": { "us": 1.1, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 04b68b8f4ec..14d3c1ae846 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1146,7 +1146,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_max_reasoning_effort": true }, "global.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.25e-06, @@ -1174,7 +1175,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_max_reasoning_effort": true }, "us.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.875e-06, @@ -1202,7 +1204,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_max_reasoning_effort": true }, "eu.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.875e-06, @@ -1230,7 +1233,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_max_reasoning_effort": true }, "au.anthropic.claude-opus-4-7": { "cache_creation_input_token_cost": 6.875e-06, @@ -1258,7 +1262,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_max_reasoning_effort": true }, "anthropic.claude-sonnet-4-6": { "cache_creation_input_token_cost": 3.75e-06, @@ -9079,6 +9084,9 @@ "cache_read_input_token_cost": 5e-07, "input_cost_per_token": 5e-06, "litellm_provider": "anthropic", + "aliases": [ + "claude-opus-4-6-20250514" + ], "max_input_tokens": 1000000, "max_output_tokens": 128000, "max_tokens": 128000, @@ -9163,6 +9171,7 @@ "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, + "supports_max_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, "provider_specific_entry": { "us": 1.1, @@ -9195,6 +9204,7 @@ "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, + "supports_max_reasoning_effort": true, "tool_use_system_prompt_tokens": 346, "provider_specific_entry": { "us": 1.1, diff --git a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py index a7f5f92ab05..c54bfe5ff6b 100644 --- a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -1653,9 +1653,7 @@ def test_max_effort_rejected_for_opus_45(): messages = [{"role": "user", "content": "Test"}] - with pytest.raises( - ValueError, match="effort='max' is only supported by Claude Opus 4.6" - ): + with pytest.raises(ValueError, match="effort='max' is not supported by this model"): optional_params = {"output_config": {"effort": "max"}} config.transform_request( model="claude-opus-4-5-20251101", @@ -2213,13 +2211,11 @@ def test_reasoning_effort_does_not_set_output_config_for_older_models(): def test_max_effort_rejected_for_sonnet_46(): - """Test that effort='max' is rejected for Sonnet 4.6 (only Opus 4.6 supports max).""" + """Test that effort='max' is rejected for Sonnet 4.6.""" config = AnthropicConfig() messages = [{"role": "user", "content": "Test"}] - with pytest.raises( - ValueError, match="effort='max' is only supported by Claude Opus 4.6" - ): + with pytest.raises(ValueError, match="effort='max' is not supported by this model"): config.transform_request( model="claude-sonnet-4-6-20260219", messages=messages, @@ -2245,6 +2241,22 @@ def test_max_effort_accepted_for_opus_46(): assert result["output_config"]["effort"] == "max" +def test_max_effort_accepted_for_opus_47(): + """Test that effort='max' works for Opus 4.7.""" + config = AnthropicConfig() + messages = [{"role": "user", "content": "Test"}] + + result = config.transform_request( + model="claude-opus-4-7", + messages=messages, + optional_params={"output_config": {"effort": "max"}}, + litellm_params={}, + headers={}, + ) + + assert result["output_config"]["effort"] == "max" + + def test_effort_beta_header_not_injected_for_46_models(): """ Test that is_effort_used returns False for Claude 4.6 models.