From 4e791e4400e1ee1b4b4a343242a5e94991805e27 Mon Sep 17 00:00:00 2001 From: milan Date: Wed, 29 Jul 2026 21:42:29 +0000 Subject: [PATCH] fix(anthropic): keep the caller's thinking budget on Claude 4.6 /v1/messages Claude 4.6 accepts both the adaptive interface and legacy thinking.type=enabled with an explicit budget_tokens ceiling; 4.7 and up reject the legacy shape. Translating every legacy request into thinking.type=adaptive plus an output_config.effort bucket therefore dropped a ceiling the model still honours, letting a request bounded at 8000 thinking tokens reason well past it and burn its whole max_tokens. Gate the translation on a new supports_legacy_thinking_budget capability, set on the 4.6 generation in the cost map plus a fallback_generalizations rule, so 4.6 requests carry their budget upstream untouched while 4.7+ keep being translated. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../messages/transformation.py | 14 +++++- ...odel_prices_and_context_window_backup.json | 33 ++++++++++++++ litellm/types/utils.py | 1 + litellm/utils.py | 1 + model_prices_and_context_window.json | 34 ++++++++++++++ model_prices_and_context_window.schema.json | 3 ++ .../test_anthropic_messages_effort.py | 45 +++++++++++++++++++ .../test_reasoning_effort_translation.py | 25 +++++------ 8 files changed, 142 insertions(+), 14 deletions(-) diff --git a/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py b/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py index b2cef62cc50..694c208b2a3 100644 --- a/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py @@ -313,7 +313,14 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): def _translate_legacy_thinking_for_adaptive_model( model: str, optional_params: Dict, custom_llm_provider: str ) -> None: - """Translate legacy ``thinking.type=enabled`` to adaptive for 4.6/4.7. + """Translate legacy ``thinking.type=enabled`` to adaptive on models that only + accept the adaptive interface (4.7 and up). + + An explicit ``budget_tokens`` is a hard ceiling on reasoning that + ``output_config.effort`` cannot express, so bucketing it into an effort lets the + model reason past the ceiling the caller asked for. Models flagged + ``supports_legacy_thinking_budget`` in the cost map (the 4.6 generation) honour the + legacy shape natively, so their requests are forwarded with the budget intact. Caller-provided ``output_config.effort`` is never overridden. """ from litellm.llms.anthropic.chat.transformation import AnthropicConfig @@ -325,6 +332,11 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): return budget = int(thinking.get("budget_tokens") or 0) + if budget > 0 and AnthropicModelInfo._supports_model_capability( + model, "supports_legacy_thinking_budget", custom_llm_provider + ): + return + if budget >= DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET and ( AnthropicConfig._supports_effort_level(model, "xhigh", custom_llm_provider) ): diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 2cef600ea32..3767950bee1 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1000,6 +1000,7 @@ }, "anthropic.claude-opus-4-6-v1": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.25e-06, "cache_creation_input_token_cost_above_1hr": 1e-05, "cache_read_input_token_cost": 5e-07, @@ -1033,6 +1034,7 @@ }, "global.anthropic.claude-opus-4-6-v1": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.25e-06, "cache_creation_input_token_cost_above_1hr": 1e-05, "cache_read_input_token_cost": 5e-07, @@ -1066,6 +1068,7 @@ }, "us.anthropic.claude-opus-4-6-v1": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_1hr": 1.1e-05, "cache_read_input_token_cost": 5.5e-07, @@ -1099,6 +1102,7 @@ }, "eu.anthropic.claude-opus-4-6-v1": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_1hr": 1.1e-05, "cache_read_input_token_cost": 5.5e-07, @@ -1132,6 +1136,7 @@ }, "au.anthropic.claude-opus-4-6-v1": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_1hr": 1.1e-05, "cache_read_input_token_cost": 5.5e-07, @@ -2192,6 +2197,7 @@ }, "anthropic.claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_1hr": 6e-06, "cache_read_input_token_cost": 3e-07, @@ -2224,6 +2230,7 @@ }, "global.anthropic.claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_1hr": 6e-06, "cache_read_input_token_cost": 3e-07, @@ -2256,6 +2263,7 @@ }, "us.anthropic.claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 4.125e-06, "cache_creation_input_token_cost_above_1hr": 6.6e-06, "cache_read_input_token_cost": 3.3e-07, @@ -2288,6 +2296,7 @@ }, "eu.anthropic.claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 4.125e-06, "cache_creation_input_token_cost_above_1hr": 6.6e-06, "cache_read_input_token_cost": 3.3e-07, @@ -2320,6 +2329,7 @@ }, "au.anthropic.claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 4.125e-06, "cache_creation_input_token_cost_above_1hr": 6.6e-06, "cache_read_input_token_cost": 3.3e-07, @@ -2352,6 +2362,7 @@ }, "jp.anthropic.claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 4.125e-06, "cache_creation_input_token_cost_above_1hr": 6.6e-06, "cache_read_input_token_cost": 3.3e-07, @@ -2884,6 +2895,7 @@ }, "azure_ai/claude-opus-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "input_cost_per_token": 5e-06, "output_cost_per_token": 2.5e-05, "litellm_provider": "azure_ai", @@ -3110,6 +3122,7 @@ }, "azure_ai/claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_1hr": 6e-06, "cache_read_input_token_cost": 3e-07, @@ -11726,6 +11739,7 @@ "search_context_size_medium": 0.01 }, "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "supports_assistant_prefill": true, "supports_computer_use": true, "supports_function_calling": true, @@ -11928,6 +11942,7 @@ "search_context_size_medium": 0.01 }, "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "supports_assistant_prefill": false, "supports_computer_use": true, "supports_function_calling": true, @@ -11964,6 +11979,7 @@ "search_context_size_medium": 0.01 }, "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "supports_assistant_prefill": false, "supports_computer_use": true, "supports_function_calling": true, @@ -20903,6 +20919,7 @@ }, "github_copilot/claude-opus-4.6-fast": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "litellm_provider": "github_copilot", "max_input_tokens": 128000, "max_output_tokens": 16000, @@ -30455,6 +30472,7 @@ }, "openrouter/anthropic/claude-sonnet-4.6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_200k_tokens": 7.5e-06, "cache_read_input_token_cost": 3e-07, @@ -30499,6 +30517,7 @@ }, "openrouter/anthropic/claude-opus-4.6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.25e-06, "cache_read_input_token_cost": 5e-07, "input_cost_per_token": 5e-06, @@ -32489,6 +32508,7 @@ }, "perplexity/anthropic/claude-opus-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "litellm_provider": "perplexity", "mode": "responses", "supports_web_search": true, @@ -35754,6 +35774,7 @@ }, "vercel_ai_gateway/anthropic/claude-opus-4.6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.25e-06, "cache_read_input_token_cost": 5e-07, "input_cost_per_token": 5e-06, @@ -37000,6 +37021,7 @@ }, "vertex_ai/claude-opus-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.25e-06, "cache_creation_input_token_cost_above_1hr": 1e-05, "cache_read_input_token_cost": 5e-07, @@ -37030,6 +37052,7 @@ }, "vertex_ai/claude-opus-4-6@default": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.25e-06, "cache_creation_input_token_cost_above_1hr": 1e-05, "cache_read_input_token_cost": 5e-07, @@ -37372,6 +37395,7 @@ }, "vertex_ai/claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_1hr": 6e-06, "cache_read_input_token_cost": 3e-07, @@ -44961,6 +44985,7 @@ }, "vertex_ai/claude-sonnet-4-6@default": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_1hr": 6e-06, "cache_read_input_token_cost": 3e-07, @@ -46185,6 +46210,14 @@ "supports_adaptive_thinking": true } }, + { + "name": "claude-legacy-thinking-budget", + "pattern": "claude-[a-z]+-4[-._]6(?![0-9])", + "description": "Claude 4.6, in any id shape that contains claude--. The 4.6 generation is the only one that accepts both interfaces: adaptive thinking (output_config.effort) and the legacy thinking.type=enabled with an explicit budget_tokens ceiling. Anthropic rejects the legacy shape from 4.7 onwards, so only 4.6 requests can be forwarded with the caller's budget intact instead of being translated to an effort bucket.", + "model_info": { + "supports_legacy_thinking_budget": true + } + }, { "name": "claude-mid-conversation-system", "pattern": "claude-[a-z]+-(?:4[-._](?:[89]|[1-9]\\d)(?!\\d)|(?:[5-9]|[1-9]\\d)(?!\\d)(?:[-._]\\d{1,2}(?!\\d))?)", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index e4dfac48141..06b78b192d1 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -154,6 +154,7 @@ class ProviderSpecificModelInfo(TypedDict, total=False): supports_web_search: Optional[bool] supports_reasoning: Optional[bool] supports_adaptive_thinking: Optional[bool] + supports_legacy_thinking_budget: Optional[bool] supports_mid_conversation_system: Optional[bool] supports_url_context: Optional[bool] supports_none_reasoning_effort: Optional[bool] diff --git a/litellm/utils.py b/litellm/utils.py index 944bb61d5e7..b88b8589f09 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -5514,6 +5514,7 @@ def _get_model_info_helper( supports_url_context=_model_info.get("supports_url_context", None), supports_reasoning=_model_info.get("supports_reasoning", None), supports_adaptive_thinking=_model_info.get("supports_adaptive_thinking", None), + supports_legacy_thinking_budget=_model_info.get("supports_legacy_thinking_budget", None), supports_mid_conversation_system=_model_info.get("supports_mid_conversation_system", None), supports_none_reasoning_effort=_model_info.get("supports_none_reasoning_effort", None), supports_minimal_reasoning_effort=_model_info.get("supports_minimal_reasoning_effort", None), diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index db28118d52b..605c8cb2e1f 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1000,6 +1000,7 @@ }, "anthropic.claude-opus-4-6-v1": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.25e-06, "cache_creation_input_token_cost_above_1hr": 1e-05, "cache_read_input_token_cost": 5e-07, @@ -1033,6 +1034,7 @@ }, "global.anthropic.claude-opus-4-6-v1": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.25e-06, "cache_creation_input_token_cost_above_1hr": 1e-05, "cache_read_input_token_cost": 5e-07, @@ -1066,6 +1068,7 @@ }, "us.anthropic.claude-opus-4-6-v1": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_1hr": 1.1e-05, "cache_read_input_token_cost": 5.5e-07, @@ -1099,6 +1102,7 @@ }, "eu.anthropic.claude-opus-4-6-v1": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_1hr": 1.1e-05, "cache_read_input_token_cost": 5.5e-07, @@ -1132,6 +1136,7 @@ }, "au.anthropic.claude-opus-4-6-v1": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.875e-06, "cache_creation_input_token_cost_above_1hr": 1.1e-05, "cache_read_input_token_cost": 5.5e-07, @@ -2192,6 +2197,7 @@ }, "anthropic.claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_1hr": 6e-06, "cache_read_input_token_cost": 3e-07, @@ -2224,6 +2230,7 @@ }, "global.anthropic.claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_1hr": 6e-06, "cache_read_input_token_cost": 3e-07, @@ -2256,6 +2263,7 @@ }, "us.anthropic.claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 4.125e-06, "cache_creation_input_token_cost_above_1hr": 6.6e-06, "cache_read_input_token_cost": 3.3e-07, @@ -2288,6 +2296,7 @@ }, "eu.anthropic.claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 4.125e-06, "cache_creation_input_token_cost_above_1hr": 6.6e-06, "cache_read_input_token_cost": 3.3e-07, @@ -2320,6 +2329,7 @@ }, "au.anthropic.claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 4.125e-06, "cache_creation_input_token_cost_above_1hr": 6.6e-06, "cache_read_input_token_cost": 3.3e-07, @@ -2352,6 +2362,7 @@ }, "jp.anthropic.claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 4.125e-06, "cache_creation_input_token_cost_above_1hr": 6.6e-06, "cache_read_input_token_cost": 3.3e-07, @@ -2884,6 +2895,7 @@ }, "azure_ai/claude-opus-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "input_cost_per_token": 5e-06, "output_cost_per_token": 2.5e-05, "litellm_provider": "azure_ai", @@ -3110,6 +3122,7 @@ }, "azure_ai/claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_1hr": 6e-06, "cache_read_input_token_cost": 3e-07, @@ -11726,6 +11739,7 @@ "search_context_size_medium": 0.01 }, "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "supports_assistant_prefill": true, "supports_computer_use": true, "supports_function_calling": true, @@ -11928,6 +11942,7 @@ "search_context_size_medium": 0.01 }, "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "supports_assistant_prefill": false, "supports_computer_use": true, "supports_function_calling": true, @@ -11964,6 +11979,7 @@ "search_context_size_medium": 0.01 }, "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "supports_assistant_prefill": false, "supports_computer_use": true, "supports_function_calling": true, @@ -20979,6 +20995,7 @@ }, "github_copilot/claude-opus-4.6-fast": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "litellm_provider": "github_copilot", "max_input_tokens": 128000, "max_output_tokens": 16000, @@ -30530,6 +30547,7 @@ }, "openrouter/anthropic/claude-sonnet-4.6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_200k_tokens": 7.5e-06, "cache_read_input_token_cost": 3e-07, @@ -30574,6 +30592,7 @@ }, "openrouter/anthropic/claude-opus-4.6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.25e-06, "cache_read_input_token_cost": 5e-07, "input_cost_per_token": 5e-06, @@ -32580,6 +32599,7 @@ }, "perplexity/anthropic/claude-opus-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "litellm_provider": "perplexity", "mode": "responses", "supports_web_search": true, @@ -35845,6 +35865,7 @@ }, "vercel_ai_gateway/anthropic/claude-opus-4.6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.25e-06, "cache_read_input_token_cost": 5e-07, "input_cost_per_token": 5e-06, @@ -37091,6 +37112,7 @@ }, "vertex_ai/claude-opus-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.25e-06, "cache_creation_input_token_cost_above_1hr": 1e-05, "cache_read_input_token_cost": 5e-07, @@ -37121,6 +37143,7 @@ }, "vertex_ai/claude-opus-4-6@default": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 6.25e-06, "cache_creation_input_token_cost_above_1hr": 1e-05, "cache_read_input_token_cost": 5e-07, @@ -37463,6 +37486,7 @@ }, "vertex_ai/claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_1hr": 6e-06, "cache_read_input_token_cost": 3e-07, @@ -45082,6 +45106,7 @@ }, "vertex_ai/claude-sonnet-4-6@default": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_1hr": 6e-06, "cache_read_input_token_cost": 3e-07, @@ -45649,6 +45674,7 @@ }, "snowflake/claude-sonnet-4-6": { "supports_adaptive_thinking": true, + "supports_legacy_thinking_budget": true, "max_tokens": 16384, "max_input_tokens": 200000, "max_output_tokens": 16384, @@ -46307,6 +46333,14 @@ "supports_adaptive_thinking": true } }, + { + "name": "claude-legacy-thinking-budget", + "pattern": "claude-[a-z]+-4[-._]6(?![0-9])", + "description": "Claude 4.6, in any id shape that contains claude--. The 4.6 generation is the only one that accepts both interfaces: adaptive thinking (output_config.effort) and the legacy thinking.type=enabled with an explicit budget_tokens ceiling. Anthropic rejects the legacy shape from 4.7 onwards, so only 4.6 requests can be forwarded with the caller's budget intact instead of being translated to an effort bucket.", + "model_info": { + "supports_legacy_thinking_budget": true + } + }, { "name": "claude-mid-conversation-system", "pattern": "claude-[a-z]+-(?:4[-._](?:[89]|[1-9]\\d)(?!\\d)|(?:[5-9]|[1-9]\\d)(?!\\d)(?:[-._]\\d{1,2}(?!\\d))?)", diff --git a/model_prices_and_context_window.schema.json b/model_prices_and_context_window.schema.json index 988f56655fc..e9053aa3c20 100644 --- a/model_prices_and_context_window.schema.json +++ b/model_prices_and_context_window.schema.json @@ -591,6 +591,9 @@ "supports_image_size": { "type": "boolean" }, + "supports_legacy_thinking_budget": { + "type": "boolean" + }, "supports_low_reasoning_effort": { "type": "boolean" }, diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_messages_effort.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_messages_effort.py index e9d4d625421..95383f14197 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_messages_effort.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_messages_effort.py @@ -294,3 +294,48 @@ def test_non_adaptive_request_without_effort_is_untouched(): assert "thinking" not in result assert "output_config" not in result + +def test_legacy_thinking_budget_preserved_for_adaptive_model(): + """Regression: a caller-supplied ``budget_tokens`` is a hard reasoning ceiling that + adaptive thinking cannot express. Rewriting it to ``thinking={type: adaptive}`` plus + an effort bucket removed the ceiling, so a request bounded at 8000 thinking tokens + started reasoning past it and burning the whole max_tokens. 4.6/4.7 accept the legacy + shape, so it must be forwarded unchanged.""" + result = _transform( + "claude-sonnet-4-6", + {"max_tokens": 32768, "thinking": {"type": "enabled", "budget_tokens": 8000}}, + ) + + assert result["thinking"] == {"type": "enabled", "budget_tokens": 8000} + assert "output_config" not in result + + +def test_budget_less_legacy_thinking_translated_to_adaptive_for_4_6(): + """``thinking={type: enabled}`` without a budget is not a valid Anthropic request, so + the legacy claude-code shape still has to be translated to the adaptive interface.""" + result = _transform("claude-sonnet-4-6", {"max_tokens": 32768, "thinking": {"type": "enabled"}}) + + assert result["thinking"] == {"type": "adaptive"} + assert result["output_config"] == {"effort": "low"} + + +def test_legacy_thinking_budget_preserved_for_azure_ai_adaptive_model(): + """The Azure AI Foundry subclass resolves capabilities under its own provider + namespace; the budget must survive there too, since that is where this surfaced.""" + from litellm.llms.azure_ai.anthropic.messages_transformation import ( + AzureAnthropicMessagesConfig, + ) + + result = AzureAnthropicMessagesConfig().transform_anthropic_messages_request( + model="claude-sonnet-4-6", + messages=[{"role": "user", "content": "Hello"}], + anthropic_messages_optional_request_params={ + "max_tokens": 32768, + "thinking": {"type": "enabled", "budget_tokens": 8000}, + }, + litellm_params={}, + headers={}, + ) + + assert result["thinking"] == {"type": "enabled", "budget_tokens": 8000} + assert "output_config" not in result diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_reasoning_effort_translation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_reasoning_effort_translation.py index e3f0bbbcc69..0cd1ecc5711 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_reasoning_effort_translation.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_reasoning_effort_translation.py @@ -280,12 +280,11 @@ def test_reasoning_effort_in_supported_params(): "vertex_ai/claude-opus-4-6", ], ) -def test_legacy_thinking_high_budget_clamps_to_high_when_xhigh_unsupported( - local_model_cost_map, model -): - """Claude Code sends ``thinking.budget_tokens=31999``; Sonnet 4.6 and Opus 4.6 - have no ``xhigh`` tier, so the translator must emit ``high`` rather than the - provider-invalid ``xhigh`` (regression for issue #29282).""" +def test_legacy_thinking_budget_preserved_on_4_6(local_model_cost_map, model): + """The 4.6 generation accepts both interfaces, so Claude Code's + ``thinking.budget_tokens=31999`` must reach the provider untouched: bucketing it + into ``output_config.effort`` both drops the ceiling the caller asked for and + invents an ``xhigh`` tier these models don't have (regression for issue #29282).""" config = AnthropicMessagesConfig() optional_params = { "max_tokens": 1024, @@ -300,8 +299,8 @@ def test_legacy_thinking_high_budget_clamps_to_high_when_xhigh_unsupported( headers={}, ) - assert result.get("thinking") == {"type": "adaptive"} - assert result.get("output_config") == {"effort": "high"} + assert result.get("thinking") == {"type": "enabled", "budget_tokens": 31999} + assert "output_config" not in result def test_legacy_thinking_high_budget_keeps_xhigh_when_supported(): @@ -361,8 +360,8 @@ def test_legacy_thinking_translates_to_adaptive_for_opus_48( @pytest.mark.parametrize( "budget_tokens,expected_effort", [ - (DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET * 2, "high"), - (DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET, "high"), + (DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET * 2, "xhigh"), + (DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET, "xhigh"), (DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, "high"), (DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET - 1, "medium"), (DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, "medium"), @@ -370,7 +369,7 @@ def test_legacy_thinking_translates_to_adaptive_for_opus_48( (1, "low"), ], ) -def test_legacy_thinking_budget_buckets_on_sonnet_46(budget_tokens, expected_effort): +def test_legacy_thinking_budget_buckets_on_opus_47(budget_tokens, expected_effort): config = AnthropicMessagesConfig() optional_params = { "max_tokens": 1024, @@ -378,7 +377,7 @@ def test_legacy_thinking_budget_buckets_on_sonnet_46(budget_tokens, expected_eff } result = config.transform_anthropic_messages_request( - model="claude-sonnet-4-6", + model="claude-opus-4-7", messages=[{"role": "user", "content": "Hello"}], anthropic_messages_optional_request_params=optional_params, litellm_params={}, @@ -397,7 +396,7 @@ def test_legacy_thinking_does_not_override_explicit_output_config(): } result = config.transform_anthropic_messages_request( - model="claude-sonnet-4-6", + model="claude-opus-4-7", messages=[{"role": "user", "content": "Hello"}], anthropic_messages_optional_request_params=optional_params, litellm_params={},