fix(anthropic): strip @version suffix in _model_map_lookup_candidates (#32833)

vertex_ai/claude-opus-4-8@default (and sibling @default models) were
misclassified as non-adaptive because _model_map_lookup_candidates only
stripped provider prefixes but never the @<suffix> portion. The lookup
produced candidates like ["vertex_ai/claude-opus-4-8@default",
"claude-opus-4-8@default"], neither of which exists in model_cost, so
_is_adaptive_thinking_model returned False. LiteLLM then sent
thinking.type=enabled to a @default Vertex AI endpoint that requires
thinking.type=adaptive, resulting in a 400.

_strip_version_suffix now removes @<suffix> from each candidate,
adding the bare model name (e.g. "claude-opus-4-8") to the lookup
chain. Also adds supports_adaptive_thinking: true to the three
@default model_cost entries that were missing it as belt-and-suspenders.

Co-authored-by: Deepanshu <deepanshu.lulla@alpha-sense.com>
This commit is contained in:
Mateo Wang 2026-07-10 20:25:56 -07:00 committed by GitHub
parent 1ab1515d9e
commit 4baf326a39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 53 additions and 1 deletions

View file

@ -289,6 +289,13 @@ class AnthropicModelInfo(BaseLLMModelInfo):
status_code=400,
)
@staticmethod
def _strip_version_suffix(model: str) -> str:
at = model.rfind("@")
if at > 0:
return model[:at]
return model
@staticmethod
def _model_map_lookup_candidates(model: str) -> List[str]:
"""Model-map keys to try for ``model``: the id itself, the same id with a
@ -324,6 +331,7 @@ class AnthropicModelInfo(BaseLLMModelInfo):
_DATED_RELEASE_SUFFIX_RE.sub("", cand),
_DOTTED_VERSION_RE.sub(r"\1-\2", cand),
_strip_bedrock_id_suffixes(cand),
AnthropicModelInfo._strip_version_suffix(cand),
)
)
return list(dict.fromkeys((*primary, *normalized)))

View file

@ -772,7 +772,6 @@ class TestProxyOAuthHeaderForwarding:
self,
):
"""OAuth Authorization header IS forwarded when x-litellm-api-key was used for proxy auth."""
from unittest.mock import patch
from starlette.datastructures import Headers
@ -1724,3 +1723,48 @@ class TestClaudeOpus48AdaptiveThinking:
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
assert AnthropicModelInfo._is_adaptive_thinking_model(model) is False
class TestDefaultSuffixAdaptiveThinking:
"""@default-suffixed Vertex AI model names (e.g. vertex_ai/claude-opus-4-8@default)
must resolve as adaptive thinking. Before the fix, _model_map_lookup_candidates
never stripped the @default suffix, so the lookup fell through to the bare
model name without @default, which may or may not have the flag, and for
provider-prefixed forms the lookup always missed (issue #31760)."""
@pytest.mark.parametrize(
"model",
[
"vertex_ai/claude-opus-4-8@default",
"vertex_ai/claude-sonnet-4-6@default",
"vertex_ai/claude-opus-4-7@default",
"vertex_ai/claude-opus-4-6@default",
"vertex_ai/claude-fable-5@default",
],
)
def test_default_suffix_models_are_adaptive_thinking(
self, local_model_cost_map, model: str
) -> None:
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
assert AnthropicModelInfo._is_adaptive_thinking_model(model) is True, (
f"{model} not classified as adaptive thinking. "
"Check _model_map_lookup_candidates strips @default suffix."
)
@pytest.mark.parametrize(
"model,expected_bare",
[
("vertex_ai/claude-opus-4-8@default", "claude-opus-4-8"),
("vertex_ai/claude-sonnet-4-6@default", "claude-sonnet-4-6"),
],
)
def test_lookup_candidates_include_bare_name(
self, model: str, expected_bare: str
) -> None:
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
candidates = AnthropicModelInfo._model_map_lookup_candidates(model)
assert expected_bare in candidates, (
f"Expected '{expected_bare}' in candidates for '{model}', got: {candidates}"
)