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>
This commit is contained in:
milan 2026-07-29 21:42:29 +00:00
parent 440b1bcf65
commit 4e791e4400
8 changed files with 142 additions and 14 deletions

View file

@ -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)
):

View file

@ -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-<family>-. 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))?)",

View file

@ -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]

View file

@ -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),

View file

@ -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-<family>-. 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))?)",

View file

@ -591,6 +591,9 @@
"supports_image_size": {
"type": "boolean"
},
"supports_legacy_thinking_budget": {
"type": "boolean"
},
"supports_low_reasoning_effort": {
"type": "boolean"
},

View file

@ -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

View file

@ -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={},