fix(anthropic): keep legacy thinking budget_tokens on Claude 4.6 models

This commit is contained in:
mateo-berri 2026-08-24 11:12:54 -07:00
parent 490c9f9f3f
commit fef5f41985
7 changed files with 168 additions and 19 deletions

View file

@ -440,6 +440,16 @@ class AnthropicModelInfo(BaseLLMModelInfo):
"""
return AnthropicModelInfo._supports_model_capability(model, "thinking_always_on", custom_llm_provider)
@staticmethod
def _supports_legacy_thinking(model: str, custom_llm_provider: str) -> bool:
"""Whether ``model`` is an adaptive-thinking model that still accepts legacy
``thinking.type=enabled`` with ``budget_tokens`` (the Claude 4.6 family).
The model cost map is authoritative: an explicit ``supports_legacy_thinking``
entry resolved under ``custom_llm_provider``, or a ``fallback_generalizations``
rule for unmapped 4.6 ids. Absent flag means the model rejects the legacy shape.
"""
return AnthropicModelInfo._supports_model_capability(model, "supports_legacy_thinking", custom_llm_provider)
@staticmethod
def maybe_drop_disabled_thinking(
model: str,

View file

@ -379,13 +379,19 @@ 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.
Caller-provided ``output_config.effort`` is never overridden.
"""Translate legacy ``thinking.type=enabled`` to adaptive for the
adaptive-thinking models that reject it (4.7+ and the 5 families).
Models flagged ``supports_legacy_thinking`` (the 4.6 family) accept the
legacy shape natively, so it is forwarded verbatim and the caller's
``budget_tokens`` cap keeps applying. Caller-provided
``output_config.effort`` is never overridden.
"""
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
if not AnthropicModelInfo._is_adaptive_thinking_model(model, custom_llm_provider):
return
if AnthropicModelInfo._supports_legacy_thinking(model, custom_llm_provider):
return
thinking: Final = optional_params.get("thinking")
if not isinstance(thinking, dict) or thinking.get("type") != "enabled":
return

View file

@ -1019,6 +1019,7 @@
},
"anthropic.claude-opus-4-6-v1": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -1053,6 +1054,7 @@
},
"global.anthropic.claude-opus-4-6-v1": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -1087,6 +1089,7 @@
},
"us.anthropic.claude-opus-4-6-v1": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -1121,6 +1124,7 @@
},
"eu.anthropic.claude-opus-4-6-v1": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -1155,6 +1159,7 @@
},
"au.anthropic.claude-opus-4-6-v1": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2233,6 +2238,7 @@
},
"anthropic.claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2266,6 +2272,7 @@
},
"global.anthropic.claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2299,6 +2306,7 @@
},
"us.anthropic.claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2332,6 +2340,7 @@
},
"eu.anthropic.claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2365,6 +2374,7 @@
},
"au.anthropic.claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2398,6 +2408,7 @@
},
"jp.anthropic.claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2950,6 +2961,7 @@
"azure_ai/claude-opus-4-6": {
"deprecation_date": "2027-02-02",
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"input_cost_per_token": 5e-06,
"output_cost_per_token": 2.5e-05,
"litellm_provider": "azure_ai",
@ -3181,6 +3193,7 @@
"azure_ai/claude-sonnet-4-6": {
"deprecation_date": "2027-02-10",
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -12489,6 +12502,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"supports_assistant_prefill": true,
"supports_computer_use": true,
"supports_function_calling": true,
@ -12698,6 +12712,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -12735,6 +12750,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -14677,6 +14693,7 @@
"source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving",
"supports_assistant_prefill": true,
"supports_function_calling": true,
"supports_legacy_thinking": true,
"supports_reasoning": true,
"supports_tool_choice": true
},
@ -14753,6 +14770,7 @@
"source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving",
"supports_assistant_prefill": true,
"supports_function_calling": true,
"supports_legacy_thinking": true,
"supports_reasoning": true,
"supports_tool_choice": true
},
@ -23180,6 +23198,7 @@
},
"github_copilot/claude-opus-4.6-fast": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"litellm_provider": "github_copilot",
"max_input_tokens": 128000,
"max_output_tokens": 16000,
@ -33563,6 +33582,7 @@
},
"openrouter/anthropic/claude-sonnet-4.6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -33607,6 +33627,7 @@
},
"openrouter/anthropic/claude-opus-4.6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_read_input_token_cost": 5e-07,
"input_cost_per_token": 5e-06,
@ -35681,6 +35702,7 @@
},
"perplexity/anthropic/claude-opus-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
@ -39056,6 +39078,7 @@
},
"vercel_ai_gateway/anthropic/claude-opus-4.6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_read_input_token_cost": 5e-07,
"input_cost_per_token": 5e-06,
@ -40315,6 +40338,7 @@
"deprecation_date": "2027-02-05",
"regional_endpoint_uplift_multiplier": 1.1,
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -40347,6 +40371,7 @@
"deprecation_date": "2027-02-05",
"regional_endpoint_uplift_multiplier": 1.1,
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -40712,6 +40737,7 @@
"vertex_ai/claude-sonnet-4-6": {
"regional_endpoint_uplift_multiplier": 1.1,
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -48481,6 +48507,7 @@
"vertex_ai/claude-sonnet-4-6@default": {
"regional_endpoint_uplift_multiplier": 1.1,
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -49265,6 +49292,7 @@
},
"snowflake/claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"max_tokens": 16384,
"max_input_tokens": 200000,
"max_output_tokens": 16384,
@ -50255,6 +50283,14 @@
"supports_adaptive_thinking": true
}
},
{
"name": "claude-legacy-thinking",
"pattern": "claude-[a-z]+-4[-._]6(?!\\d)",
"description": "Claude at version 4.6 exactly, in any id shape that contains claude-<family>-4-6 (dotted and underscored minors included, dated releases such as claude-sonnet-4-6-20260219 too). The 4.6 family is adaptive-thinking yet still accepts legacy thinking.type=enabled with budget_tokens, so the caller's hard budget cap is forwarded verbatim instead of being rewritten to an uncapped output_config.effort. The lookahead keeps two-digit minors such as 4-60 from matching. 4.7+ and 5+ majors reject the legacy shape and stay on the adaptive translation.",
"model_info": {
"supports_legacy_thinking": true
}
},
{
"name": "claude-always-on-thinking",
"pattern": "claude-(?:fable|mythos)-",

View file

@ -154,6 +154,7 @@ class ProviderSpecificModelInfo(TypedDict, total=False):
supports_web_search: bool | None
supports_reasoning: bool | None
supports_adaptive_thinking: bool | None
supports_legacy_thinking: ReadOnly[bool | None]
thinking_always_on: ReadOnly[bool | None]
supports_tool_search: bool | None
supports_mid_conversation_system: bool | None

View file

@ -5753,6 +5753,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=_model_info.get("supports_legacy_thinking", None),
thinking_always_on=_model_info.get("thinking_always_on", None),
supports_tool_search=_model_info.get("supports_tool_search", None),
supports_mid_conversation_system=_model_info.get("supports_mid_conversation_system", None),

View file

@ -1019,6 +1019,7 @@
},
"anthropic.claude-opus-4-6-v1": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -1053,6 +1054,7 @@
},
"global.anthropic.claude-opus-4-6-v1": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -1087,6 +1089,7 @@
},
"us.anthropic.claude-opus-4-6-v1": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -1121,6 +1124,7 @@
},
"eu.anthropic.claude-opus-4-6-v1": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -1155,6 +1159,7 @@
},
"au.anthropic.claude-opus-4-6-v1": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2233,6 +2238,7 @@
},
"anthropic.claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2266,6 +2272,7 @@
},
"global.anthropic.claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2299,6 +2306,7 @@
},
"us.anthropic.claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2332,6 +2340,7 @@
},
"eu.anthropic.claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2365,6 +2374,7 @@
},
"au.anthropic.claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2398,6 +2408,7 @@
},
"jp.anthropic.claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -2950,6 +2961,7 @@
"azure_ai/claude-opus-4-6": {
"deprecation_date": "2027-02-02",
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"input_cost_per_token": 5e-06,
"output_cost_per_token": 2.5e-05,
"litellm_provider": "azure_ai",
@ -3181,6 +3193,7 @@
"azure_ai/claude-sonnet-4-6": {
"deprecation_date": "2027-02-10",
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -12489,6 +12502,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"supports_assistant_prefill": true,
"supports_computer_use": true,
"supports_function_calling": true,
@ -12698,6 +12712,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -12735,6 +12750,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -14677,6 +14693,7 @@
"source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving",
"supports_assistant_prefill": true,
"supports_function_calling": true,
"supports_legacy_thinking": true,
"supports_reasoning": true,
"supports_tool_choice": true
},
@ -14753,6 +14770,7 @@
"source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving",
"supports_assistant_prefill": true,
"supports_function_calling": true,
"supports_legacy_thinking": true,
"supports_reasoning": true,
"supports_tool_choice": true
},
@ -23180,6 +23198,7 @@
},
"github_copilot/claude-opus-4.6-fast": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"litellm_provider": "github_copilot",
"max_input_tokens": 128000,
"max_output_tokens": 16000,
@ -33563,6 +33582,7 @@
},
"openrouter/anthropic/claude-sonnet-4.6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -33607,6 +33627,7 @@
},
"openrouter/anthropic/claude-opus-4.6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_read_input_token_cost": 5e-07,
"input_cost_per_token": 5e-06,
@ -35681,6 +35702,7 @@
},
"perplexity/anthropic/claude-opus-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"litellm_provider": "perplexity",
"mode": "responses",
"supports_web_search": true,
@ -39056,6 +39078,7 @@
},
"vercel_ai_gateway/anthropic/claude-opus-4.6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_read_input_token_cost": 5e-07,
"input_cost_per_token": 5e-06,
@ -40315,6 +40338,7 @@
"deprecation_date": "2027-02-05",
"regional_endpoint_uplift_multiplier": 1.1,
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -40347,6 +40371,7 @@
"deprecation_date": "2027-02-05",
"regional_endpoint_uplift_multiplier": 1.1,
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -40712,6 +40737,7 @@
"vertex_ai/claude-sonnet-4-6": {
"regional_endpoint_uplift_multiplier": 1.1,
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -48481,6 +48507,7 @@
"vertex_ai/claude-sonnet-4-6@default": {
"regional_endpoint_uplift_multiplier": 1.1,
"supports_adaptive_thinking": true,
"supports_legacy_thinking": 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,
@ -49265,6 +49292,7 @@
},
"snowflake/claude-sonnet-4-6": {
"supports_adaptive_thinking": true,
"supports_legacy_thinking": true,
"max_tokens": 16384,
"max_input_tokens": 200000,
"max_output_tokens": 16384,
@ -50255,6 +50283,14 @@
"supports_adaptive_thinking": true
}
},
{
"name": "claude-legacy-thinking",
"pattern": "claude-[a-z]+-4[-._]6(?!\\d)",
"description": "Claude at version 4.6 exactly, in any id shape that contains claude-<family>-4-6 (dotted and underscored minors included, dated releases such as claude-sonnet-4-6-20260219 too). The 4.6 family is adaptive-thinking yet still accepts legacy thinking.type=enabled with budget_tokens, so the caller's hard budget cap is forwarded verbatim instead of being rewritten to an uncapped output_config.effort. The lookahead keeps two-digit minors such as 4-60 from matching. 4.7+ and 5+ majors reject the legacy shape and stay on the adaptive translation.",
"model_info": {
"supports_legacy_thinking": true
}
},
{
"name": "claude-always-on-thinking",
"pattern": "claude-(?:fable|mythos)-",

View file

@ -2,7 +2,6 @@
import pytest
import litellm
from litellm.constants import (
DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET,
DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET,
@ -17,7 +16,6 @@ from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_tran
)
@pytest.mark.parametrize(
"reasoning_effort,expected_effort",
[
@ -258,19 +256,22 @@ def test_reasoning_effort_in_supported_params():
"model",
[
"claude-sonnet-4-6",
"bedrock/invoke/us.anthropic.claude-sonnet-4-6",
"vertex_ai/claude-sonnet-4-6",
"claude-opus-4-6",
"claude-sonnet-4-6-20260219",
"bedrock/invoke/us.anthropic.claude-sonnet-4-6",
"bedrock/invoke/us.anthropic.claude-opus-4-6-v1:0",
"vertex_ai/claude-sonnet-4-6",
"vertex_ai/claude-opus-4-6",
"azure_ai/claude-sonnet-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_verbatim_on_46(local_model_cost_map, model):
"""Regression for the passthrough silently dropping a caller's hard thinking
budget: the 4.6 family accepts ``thinking.type=enabled`` with ``budget_tokens``
natively, so rewriting it to ``thinking.type=adaptive`` + ``output_config.effort``
(which carries no ceiling) let reasoning run past the requested cap. The legacy
shape must be forwarded verbatim, in every 4.6 id shape including unmapped dated
releases resolved by the ``claude-legacy-thinking`` fallback rule."""
config = AnthropicMessagesConfig()
optional_params = {
"max_tokens": 1024,
@ -285,8 +286,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():
@ -343,11 +344,44 @@ def test_legacy_thinking_translates_to_adaptive_for_opus_48(
assert result.get("output_config") == {"effort": "xhigh"}
@pytest.mark.parametrize(
"model,expected_effort",
[
("claude-sonnet-5", "xhigh"),
("claude-opus-5", "xhigh"),
("claude-newfamily-6", "high"),
],
)
def test_legacy_thinking_translates_to_adaptive_for_5_and_future_models(
local_model_cost_map, model, expected_effort
):
"""The 5 families reject ``thinking.type=enabled``, so the adaptive translation
stays the safe default for every adaptive model not flagged
``supports_legacy_thinking``, unmapped future ids included. An unmapped id
cannot prove ``xhigh`` support, so its high-budget bucket clamps to ``high``."""
config = AnthropicMessagesConfig()
optional_params = {
"max_tokens": 1024,
"thinking": {"type": "enabled", "budget_tokens": 31999},
}
result = config.transform_anthropic_messages_request(
model=model,
messages=[{"role": "user", "content": "Hello"}],
anthropic_messages_optional_request_params=optional_params,
litellm_params={},
headers={},
)
assert result.get("thinking") == {"type": "adaptive"}
assert result.get("output_config") == {"effort": expected_effort}
@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"),
@ -355,7 +389,9 @@ 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_48(
local_model_cost_map, budget_tokens, expected_effort
):
config = AnthropicMessagesConfig()
optional_params = {
"max_tokens": 1024,
@ -363,7 +399,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-8",
messages=[{"role": "user", "content": "Hello"}],
anthropic_messages_optional_request_params=optional_params,
litellm_params={},
@ -373,7 +409,29 @@ def test_legacy_thinking_budget_buckets_on_sonnet_46(budget_tokens, expected_eff
assert result.get("output_config") == {"effort": expected_effort}
def test_legacy_thinking_does_not_override_explicit_output_config():
def test_legacy_thinking_does_not_override_explicit_output_config(local_model_cost_map):
config = AnthropicMessagesConfig()
optional_params = {
"max_tokens": 1024,
"thinking": {"type": "enabled", "budget_tokens": 31999},
"output_config": {"effort": "low"},
}
result = config.transform_anthropic_messages_request(
model="claude-opus-4-8",
messages=[{"role": "user", "content": "Hello"}],
anthropic_messages_optional_request_params=optional_params,
litellm_params={},
headers={},
)
assert result.get("thinking") == {"type": "adaptive"}
assert result.get("output_config") == {"effort": "low"}
def test_legacy_thinking_with_explicit_output_config_untouched_on_46(
local_model_cost_map,
):
config = AnthropicMessagesConfig()
optional_params = {
"max_tokens": 1024,
@ -389,6 +447,7 @@ def test_legacy_thinking_does_not_override_explicit_output_config():
headers={},
)
assert result.get("thinking") == {"type": "enabled", "budget_tokens": 31999}
assert result.get("output_config") == {"effort": "low"}