From 6ebbfe519069c755262c839bb8959bb39c7e8534 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Wed, 22 Apr 2026 22:06:07 +0530 Subject: [PATCH] fix(anthropic): allow output_config effort max for Opus 4.7 and model map - Validate max effort like xhigh: Opus 4.6/4.7 id patterns or supports_max_reasoning_effort - Set supports_max_reasoning_effort on claude-opus-4-7 entries in model cost JSON - Update tests and add test_max_effort_accepted_for_opus_47 Made-with: Cursor --- litellm/llms/anthropic/chat/transformation.py | 14 +++++++----- ...odel_prices_and_context_window_backup.json | 2 ++ model_prices_and_context_window.json | 2 ++ .../test_anthropic_chat_transformation.py | 22 ++++++++++++++++--- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index cd5bb731717..31b9bad3395 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -1534,12 +1534,16 @@ 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`` is for Opus 4.6+ output effort (not Sonnet 4.6, not Opus 4.5). + # Accept known Opus 4.6/4.7 id patterns and/or ``supports_max_reasoning_effort`` + # in the model map (same pattern as ``xhigh`` below). + if effort == "max" and not ( + self._is_opus_4_6_model(model) + or self._is_opus_4_7_model(model) + or self._supports_effort_level(model, "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 640607c0748..1df1ea6eb8d 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -9163,6 +9163,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 +9196,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 4e629bbd947..6295f71ba02 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -9177,6 +9177,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, @@ -9209,6 +9210,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..e1fe4befb57 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 @@ -1654,7 +1654,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" + ValueError, match="effort='max' is not supported by this model" ): optional_params = {"output_config": {"effort": "max"}} config.transform_request( @@ -2213,12 +2213,12 @@ 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 (Opus-only effort level).""" config = AnthropicConfig() messages = [{"role": "user", "content": "Test"}] with pytest.raises( - ValueError, match="effort='max' is only supported by Claude Opus 4.6" + ValueError, match="effort='max' is not supported by this model" ): config.transform_request( model="claude-sonnet-4-6-20260219", @@ -2245,6 +2245,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.