mirror of
https://github.com/usestrix/strix.git
synced 2026-11-14 09:01:25 +00:00
fix(pricing): prefer the provider's own route when several are equally priced
resolve_litellm_model returned matches[0] after sorting the candidate routes
alphabetically. LiteLLM 1.90.1 lists grok-4.5 under three routes with identical
pricing — xai/grok-4.5, openrouter/x-ai/grok-4.5 and perplexity/xai/grok-4.5 —
so the aggregator route now sorts ahead of the provider's own and wins.
This broke test_resolves_common_bare_model_names on main:
assert resolve_litellm_model("grok-4.5") == "xai/grok-4.5"
AssertionError: assert 'openrouter/x-ai/grok-4.5' == 'xai/grok-4.5'
Pick the canonical route instead of the first one alphabetically: among equally
priced matches, the provider's own route is the one shaped exactly
{litellm_provider}/{name}. An explicitly aggregator-qualified name such as
x-ai/grok-4.5 still resolves to the aggregator, since that is what was asked for.
The added test pins the behaviour against a fixed model_cost mapping rather than
the live LiteLLM registry, so it will not drift when the registry changes again.
This commit is contained in:
parent
52b1923347
commit
e0623b5096
2 changed files with 53 additions and 1 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -20,6 +20,35 @@ 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,
|
||||
},
|
||||
}
|
||||
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"
|
||||
|
||||
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue