Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion strix/report/pricing.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,18 @@ 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]
# Every route prices the model the same, so picking one is free; pick
# the first-party listing over an aggregator's re-listing. Aggregators
# (OpenRouter, DeepInfra, ...) re-key a model under a namespaced path
# such as "openrouter/x-ai/grok-4.5", which sorts ahead of the native
# "xai/grok-4.5", so the alphabetically first key would be misleading.
native = [
key
for key in matches
if isinstance(model_cost.get(key), dict)
and key == f"{model_cost[key].get('litellm_provider')}/{name}"
]
return native[0] if len(native) == 1 else matches[0]
return None # noqa: TRY300
except Exception: # noqa: BLE001
return None
54 changes: 54 additions & 0 deletions tests/test_pricing.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,16 @@ def test_resolver_uses_provider_when_bare_entry_has_one() -> None:

def test_resolver_does_not_guess_between_differently_priced_providers() -> None:
original = litellm.model_cost
# Real catalog entries always carry ``litellm_provider``; without it the native
# route could never match and this guard would not exercise the preference.
litellm.model_cost = {
"provider-a/example": {
"litellm_provider": "provider-a",
"input_cost_per_token": 1.0,
"output_cost_per_token": 2.0,
},
"provider-b/example": {
"litellm_provider": "provider-b",
"input_cost_per_token": 3.0,
"output_cost_per_token": 4.0,
},
Expand All @@ -269,3 +273,53 @@ def test_resolver_does_not_guess_between_differently_priced_providers() -> None:
finally:
litellm.model_cost = original
resolve_litellm_model.cache_clear()


def test_resolver_prefers_native_provider_over_aggregator() -> None:
# The same model re-listed by an aggregator (which sorts first) and by its
# first-party provider at the same price. The resolver must return the
# native listing, not the alphabetically first aggregator key.
original = litellm.model_cost
litellm.model_cost = {
"openrouter/x-ai/grok-4.5": {
"litellm_provider": "openrouter",
"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:
resolve_litellm_model.cache_clear()
assert resolve_litellm_model("grok-4.5") == "xai/grok-4.5"
finally:
litellm.model_cost = original
resolve_litellm_model.cache_clear()


def test_resolver_does_not_guess_when_a_native_route_exists_but_prices_differ() -> None:
# The native preference only breaks ties between routes that price the model the
# same. When an aggregator charges differently, the cost depends on the route and
# the resolver still declines to guess, as it did before the native preference.
original = litellm.model_cost
litellm.model_cost = {
"deepinfra/x/example-model": {
"litellm_provider": "deepinfra",
"input_cost_per_token": 1e-06,
"output_cost_per_token": 2e-06,
},
"example/example-model": {
"litellm_provider": "example",
"input_cost_per_token": 5e-06,
"output_cost_per_token": 9e-06,
},
}
try:
resolve_litellm_model.cache_clear()
assert resolve_litellm_model("example-model") is None
finally:
litellm.model_cost = original
resolve_litellm_model.cache_clear()