diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index cd5bb731717..2b3647d944c 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -1534,11 +1534,15 @@ 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 supported by Claude Opus 4.6 and Opus 4.7 (not Sonnet 4.6, + # not Opus 4.5). The upstream Anthropic API accepts effort=max on both + # Opus 4.6 and 4.7, so we keep both substring checks here for + # date-variant tolerance (e.g. claude-opus-4-7-20260408). See #25957. + if effort == "max" and not ( + self._is_opus_4_6_model(model) or self._is_opus_4_7_model(model) + ): raise ValueError( - f"effort='max' is only supported by Claude Opus 4.6. " + f"effort='max' is only supported by Claude Opus 4.6 and 4.7. " f"Got model: {model}" ) # ``xhigh`` is data-driven via ``supports_xhigh_reasoning_effort`` so 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..e44c4791726 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 only supported by Claude Opus 4.6 and 4.7" ): 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 (Sonnet 4.6 doesn't support max).""" 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 only supported by Claude Opus 4.6 and 4.7" ): config.transform_request( model="claude-sonnet-4-6-20260219", @@ -2245,6 +2245,27 @@ def test_max_effort_accepted_for_opus_46(): assert result["output_config"]["effort"] == "max" +def test_max_effort_accepted_for_opus_47(): + """Regression for #25957 - effort='max' must work for Opus 4.7. + + Anthropic's Messages API accepts ``output_config.effort='max'`` on + ``claude-opus-4-7-*`` models. The previous hardcoded ``_is_opus_4_6_model`` + guard rejected the request before it ever reached Anthropic. + """ + config = AnthropicConfig() + messages = [{"role": "user", "content": "Test"}] + + result = config.transform_request( + model="claude-opus-4-7-20260416", + 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.