[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:
parent
d991c47018
commit
f81d8ae077
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user