From 4baf326a39e76d3c8df00ed74e41564681052e33 Mon Sep 17 00:00:00 2001 From: Mateo Wang <277851410+mateo-berri@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:25:56 -0700 Subject: [PATCH] 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 @ 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 @ 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 --- litellm/llms/anthropic/common_utils.py | 8 ++++ .../anthropic/test_anthropic_common_utils.py | 46 ++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index db540e5441d..964186fd76a 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -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))) diff --git a/tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py b/tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py index a2fbb68bbb9..f7a234c8ae8 100644 --- a/tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py +++ b/tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py @@ -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}" + )