From ece58bbc2fbe8266ae62c84bd39e94bb12360cea Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 17 Oct 2026 15:45:32 +0900 Subject: [PATCH] fix: keep reasoning_effort xhigh on Gemini and suffixed Claude ids Gemini has no thinking level above high, so xhigh was a 400. Claude ids with an effort suffix such as claude-sonnet-5-5-high now inherit the base model's xhigh flag. --- litellm/llms/anthropic/common_utils.py | 6 +++++- .../gemini/vertex_and_google_ai_studio_gemini.py | 6 ++++-- .../chat/test_anthropic_chat_transformation.py | 6 ++++++ .../test_vertex_and_google_ai_studio_gemini.py | 16 ++++++++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index dd9b22f3d5d..58c430c821f 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -108,6 +108,7 @@ def anthropic_error_frame_exception(error_type: str, message: str, status_code: _BEDROCK_VERSION_SUFFIX_RE: Final = re.compile(r"-v\d+(?::\d+)?$") _INFERENCE_PROFILE_MINOR_RE: Final = re.compile(r":\d+$") _DATED_RELEASE_SUFFIX_RE: Final = re.compile(r"-\d{8}$") +_EFFORT_ALIAS_SUFFIX_RE: Final = re.compile(r"-(?:minimal|low|medium|high|xhigh|max)$") _DOTTED_VERSION_RE: Final = re.compile(r"(\d)\.(\d)") _CLAUDE_CODE_BILLING_HEADER_PREFIX: Final = "x-anthropic-billing-header:" OBJECT_MAPPING_ADAPTER: Final = TypeAdapter(dict[object, object]) @@ -743,7 +744,9 @@ class AnthropicModelInfo(BaseLLMModelInfo): (``4.6`` -> ``4-6``). Lets any reasonable alias (e.g. ``bedrock/invoke/global.anthropic.claude-opus-4-7-v1:0``, ``claude-sonnet-4-6-20260219`` or ``claude-sonnet-4.6``) resolve to its base - cost-map entry so the capability flag on that entry stays authoritative.""" + cost-map entry so the capability flag on that entry stays authoritative. + A trailing effort alias (``-high``, ``-xhigh``) is tried the same way, so + ``claude-sonnet-5-5-high`` keeps the xhigh flag from ``claude-sonnet-5-5``.""" prefixes: Final = ( "bedrock/converse/", "bedrock/invoke/", @@ -766,6 +769,7 @@ class AnthropicModelInfo(BaseLLMModelInfo): _BEDROCK_VERSION_SUFFIX_RE.sub("", cand), _INFERENCE_PROFILE_MINOR_RE.sub("", cand), _DATED_RELEASE_SUFFIX_RE.sub("", cand), + _EFFORT_ALIAS_SUFFIX_RE.sub("", cand), _DOTTED_VERSION_RE.sub(r"\1-\2", cand), _strip_bedrock_id_suffixes(cand), AnthropicModelInfo._strip_version_suffix(cand), diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index 257fafbb6b8..781bf721c71 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -985,7 +985,8 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): "thinkingBudget": DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, "includeThoughts": sends_thoughts, } - elif reasoning_effort == "high": + elif reasoning_effort in ("high", "xhigh"): + # Gemini has no thinking budget above high. OpenAI clients send xhigh. return { "thinkingBudget": DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, "includeThoughts": sends_thoughts, @@ -1037,7 +1038,8 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): return {"thinkingLevel": "low", "includeThoughts": sends_thoughts} elif reasoning_effort == "medium": return {"thinkingLevel": "medium", "includeThoughts": sends_thoughts} - elif reasoning_effort == "high": + elif reasoning_effort in ("high", "xhigh"): + # Gemini 3+ thinkingLevel tops out at high. Keep xhigh instead of 400. return {"thinkingLevel": "high", "includeThoughts": sends_thoughts} elif reasoning_effort in ("disable", "none"): return { diff --git a/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py index 6323132db36..baba1d65dd8 100644 --- a/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -2640,6 +2640,12 @@ def test_supports_effort_level_handles_provider_prefixes(model, level, expected) assert AnthropicConfig._supports_effort_level(model, level, "anthropic") is expected +def test_effort_alias_suffix_inherits_base_model_xhigh(): + """``claude-sonnet-5-5-high`` is the same model as ``claude-sonnet-5-5``.""" + assert AnthropicConfig._supports_effort_level("claude-sonnet-5-5-high", "xhigh", "anthropic") is True + assert AnthropicConfig.validate_effort_for_model("claude-sonnet-5-5-high", "xhigh", "anthropic") is None + + @pytest.mark.parametrize( "model,effort,expect_error", [ diff --git a/tests/unit/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py b/tests/unit/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py index 5c0e9f1a305..a43c46a29a1 100644 --- a/tests/unit/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py +++ b/tests/unit/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py @@ -3285,6 +3285,22 @@ def test_reasoning_effort_maps_to_thinking_level_gemini_3(): assert result["thinkingConfig"]["thinkingLevel"] == "high" assert result["thinkingConfig"]["includeThoughts"] is True + # xhigh has no Gemini thinkingLevel above high, so it uses that ceiling. + optional_params = {} + non_default_params = {"reasoning_effort": "xhigh"} + result = v.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model=model, + drop_params=False, + ) + assert result["thinkingConfig"]["thinkingLevel"] == "high" + assert result["thinkingConfig"]["includeThoughts"] is True + assert ( + VertexGeminiConfig._map_reasoning_effort_to_thinking_budget("xhigh", "gemini-2.5-pro") + == VertexGeminiConfig._map_reasoning_effort_to_thinking_budget("high", "gemini-2.5-pro") + ) + # Test disable -> low + includeThoughts=False (cannot fully disable in Gemini 3) optional_params = {} non_default_params = {"reasoning_effort": "disable"}