This commit is contained in:
Javier Valero 2026-11-04 17:05:43 +00:00 • committed by GitHub
commit c627d04a9a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 58 additions and 1 deletions

View file

@ -6,6 +6,29 @@ from functools import lru_cache
from typing import Any, cast
def _preferred_route(
matches: list[str],
model_cost: dict[str, dict[str, Any]],
name: str,
) -> str:
"""Pick the provider's own route when several equally priced ones match.
LiteLLM lists the same model under its native provider (``xai/grok-4.5``) and
under the aggregators that resell it (``openrouter/x-ai/grok-4.5``,
``perplexity/xai/grok-4.5``). Alphabetical order would hand back an
aggregator; the canonical route is the one shaped exactly
``{litellm_provider}/{name}``.
"""
for key in matches:
entry = model_cost.get(key)
if not isinstance(entry, dict):
continue
provider = entry.get("litellm_provider")
if isinstance(provider, str) and key == f"{provider}/{name}":
return key
return matches[0]
@lru_cache(maxsize=512)
def resolve_litellm_model(model: str) -> str | None:
"""Return a provider-qualified model name that LiteLLM can price."""
@ -48,7 +71,7 @@ def resolve_litellm_model(model: str) -> str | None:
if isinstance(model_cost.get(key), dict)
}
if len(matches) == 1 or len(prices) == 1:
return matches[0]
return _preferred_route(matches, model_cost, name)
return None # noqa: TRY300
except Exception: # noqa: BLE001
return None

View file

@ -62,6 +62,40 @@ def test_resolves_common_bare_model_names() -> None:
assert resolve_litellm_model("minimax/MiniMax-M3") == "minimax/MiniMax-M3"
def test_prefers_native_provider_route_over_aggregators() -> None:
"""Equally priced routes must resolve to the provider's own, not the first alphabetically."""
resolve_litellm_model.cache_clear()
model_cost = {
"openrouter/x-ai/grok-4.5": {
"litellm_provider": "openrouter",
"input_cost_per_token": 2e-06,
"output_cost_per_token": 6e-06,
},
"perplexity/xai/grok-4.5": {
"litellm_provider": "perplexity",
"input_cost_per_token": 2e-06,
"output_cost_per_token": 6e-06,
},
"xai/grok-4.5": {
"litellm_provider": "xai",
"input_cost_per_token": 2e-06,
"output_cost_per_token": 6e-06,
},
}
try:
with patch.object(litellm, "model_cost", model_cost):
assert resolve_litellm_model("grok-4.5") == "xai/grok-4.5"
# An explicitly aggregator-qualified name still resolves to that aggregator.
resolve_litellm_model.cache_clear()
with patch.object(litellm, "model_cost", model_cost):
assert resolve_litellm_model("x-ai/grok-4.5") == "openrouter/x-ai/grok-4.5"
finally:
# The resolver cache is process-wide: drop the mock-derived entries so a
# later test cannot read them instead of consulting the real registry.
resolve_litellm_model.cache_clear()
def test_resolver_returns_none_for_unresolvable_model() -> None:
resolve_litellm_model.cache_clear()
assert resolve_litellm_model("provider/not-a-real-model") is None