[internal copy of #29232] feat: route future Claude models to Anthropic provider via pattern matching (#29239)

* feat: route future Claude models to Anthropic provider via pattern matching

Add pattern-based matching for Claude model names so that future models
(e.g., claude-opus-4-9, claude-sonnet-5-0) are automatically routed to
the Anthropic provider without requiring model_prices_and_context_window.json
updates.

The pattern matches: claude-{opus|sonnet|haiku}-{major}-{minor}[-YYYYMMDD]

https://claude.ai/code/session_017asCVDN5jBFMBcZRjiQR6C

* fix: don't hard-code the tier names

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* style: move import re to module level (PEP 8)

Move `import re` from inside the module body to the top-level imports
section, following PEP 8 style guidelines that all imports should
appear at the top of the file.

https://claude.ai/code/session_01Dt8fzn81eYMfxu1MoBa5hN

* test: fix claude-mini-4-5 assertion to match generic-tier pattern

The pattern intentionally accepts any [a-z]+ tier (see f835f84) rather
than a hard-coded opus|sonnet|haiku list, so claude-mini-4-5 routes to
anthropic and the old 'is False' assertion was wrong. Replace it with a
positive test that locks in the generic-tier behavior and guards against
a regression to hard-coded tier names.

* style: collapse _CLAUDE_PATTERN to one line (black)

Black 26.3.1 (CI) collapses the re.compile call onto a single line since
it fits within the line limit. Fixes the failing lint check.

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
Mateo Wang 2026-06-02 15:16:01 -07:00 committed by GitHub
parent d991c47018
commit f81d8ae077
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 128 additions and 0 deletions

View File

@ -1,3 +1,4 @@
import re
from typing import Optional, Tuple
from urllib.parse import urlparse
@ -71,6 +72,25 @@ def _is_azure_claude_model(model: str) -> bool:
return False
_CLAUDE_PATTERN = re.compile(r"^claude-[a-z]+-\d+-\d+(?:-\d{8})?$", re.IGNORECASE)
def _matches_claude_model_pattern(model: str) -> bool:
"""
Check if a model string matches the Claude model naming pattern.
Matches patterns like:
- claude-opus-4-7
- claude-sonnet-4-6
- claude-haiku-4-5
- claude-opus-5-1-20270101 (with optional date suffix)
This allows future Claude models to be routed to the Anthropic provider
without requiring updates to model_prices_and_context_window.json.
"""
return _CLAUDE_PATTERN.match(model) is not None
def handle_cohere_chat_model_custom_llm_provider(
model: str, custom_llm_provider: Optional[str] = None
) -> Tuple[str, Optional[str]]:
@ -398,6 +418,9 @@ def get_llm_provider( # noqa: PLR0915
custom_llm_provider = "anthropic_text"
else:
custom_llm_provider = "anthropic"
## anthropic - pattern-based matching for future Claude models
elif _matches_claude_model_pattern(model):
custom_llm_provider = "anthropic"
## cohere
elif model in litellm.cohere_models or model in litellm.cohere_embedding_models:
custom_llm_provider = "cohere"

View File

@ -478,3 +478,108 @@ def test_get_llm_provider_use_proxy_arg_true_with_direct_args():
assert key == arg_api_key # Should use the argument key
assert base == arg_api_base # Should use the argument base
# -------- Tests for Claude model pattern matching ---------
class TestClaudeModelPatternMatching:
"""
Tests for _matches_claude_model_pattern which routes future Claude models
to the Anthropic provider without requiring model_prices_and_context_window.json updates.
"""
def test_matches_claude_opus_pattern(self):
"""Test claude-opus-X-Y pattern matching."""
from litellm.litellm_core_utils.get_llm_provider_logic import (
_matches_claude_model_pattern,
)
assert _matches_claude_model_pattern("claude-opus-4-7") is True
assert _matches_claude_model_pattern("claude-opus-4-9") is True
assert _matches_claude_model_pattern("claude-opus-5-1") is True
def test_matches_claude_sonnet_pattern(self):
"""Test claude-sonnet-X-Y pattern matching."""
from litellm.litellm_core_utils.get_llm_provider_logic import (
_matches_claude_model_pattern,
)
assert _matches_claude_model_pattern("claude-sonnet-4-6") is True
assert _matches_claude_model_pattern("claude-sonnet-5-0") is True
def test_matches_claude_haiku_pattern(self):
"""Test claude-haiku-X-Y pattern matching."""
from litellm.litellm_core_utils.get_llm_provider_logic import (
_matches_claude_model_pattern,
)
assert _matches_claude_model_pattern("claude-haiku-4-5") is True
assert _matches_claude_model_pattern("claude-haiku-5-0") is True
def test_matches_claude_with_date_suffix(self):
"""Test claude model pattern with date suffix."""
from litellm.litellm_core_utils.get_llm_provider_logic import (
_matches_claude_model_pattern,
)
assert _matches_claude_model_pattern("claude-opus-5-1-20270101") is True
assert _matches_claude_model_pattern("claude-sonnet-4-7-20260601") is True
assert _matches_claude_model_pattern("claude-haiku-4-6-20251201") is True
def test_matches_unknown_tier_name(self):
"""A tier segment we don't know about today should still route to anthropic.
The pattern intentionally accepts any ``[a-z]+`` tier rather than a
hard-coded ``opus|sonnet|haiku`` list so a future tier (e.g. a new
"mini" line) is covered without a code change. This guards against a
regression back to hard-coded tier names.
"""
from litellm.litellm_core_utils.get_llm_provider_logic import (
_matches_claude_model_pattern,
)
assert _matches_claude_model_pattern("claude-mini-4-5") is True
assert _matches_claude_model_pattern("claude-neptune-6-0") is True
def test_rejects_non_claude_models(self):
"""Test that non-Claude models are not matched."""
from litellm.litellm_core_utils.get_llm_provider_logic import (
_matches_claude_model_pattern,
)
assert _matches_claude_model_pattern("gpt-4") is False
assert _matches_claude_model_pattern("mistral-large") is False
assert _matches_claude_model_pattern("llama-3") is False
def test_rejects_invalid_claude_patterns(self):
"""Test that invalid Claude model patterns are not matched."""
from litellm.litellm_core_utils.get_llm_provider_logic import (
_matches_claude_model_pattern,
)
# Wrong order (variant before name)
assert _matches_claude_model_pattern("claude-4-opus") is False
# Missing version numbers
assert _matches_claude_model_pattern("claude-opus") is False
# Old format (claude-3-opus instead of claude-opus-3)
assert _matches_claude_model_pattern("claude-3-opus-20240229") is False
def test_get_llm_provider_future_claude_model(self):
"""Test that get_llm_provider routes future Claude models to anthropic."""
model, custom_llm_provider, dynamic_api_key, api_base = (
litellm.get_llm_provider(
model="claude-opus-4-9",
)
)
assert custom_llm_provider == "anthropic"
assert model == "claude-opus-4-9"
def test_get_llm_provider_future_claude_model_with_date(self):
"""Test that get_llm_provider routes future Claude models with date suffix."""
model, custom_llm_provider, dynamic_api_key, api_base = (
litellm.get_llm_provider(
model="claude-opus-5-1-20270101",
)
)
assert custom_llm_provider == "anthropic"
assert model == "claude-opus-5-1-20270101"