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.
This commit is contained in:
Seongho Bae 2026-10-17 15:45:32 +09:00
parent d346d769f0
commit ece58bbc2f
4 changed files with 31 additions and 3 deletions

View file

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

View file

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

View file

@ -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",
[

View file

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