fix(upstream): break OpenRouter bare-tail ties by highest price

When a bare model id matched several OpenRouter entries by tail, the first feed
entry won, making the resolved price depend on feed order and risking an
undercharge if a cheaper reseller happened to sort first. Pick the
highest-priced candidate instead: deterministic and money-safe, since
undercharging is the hazard. An exact id match still wins ahead of any tail
match. Measured against the live feed there are zero bare-tail collisions
today, so this only governs the latent case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jeroen Ubbink
2026-07-05 15:40:43 +02:00
co-authored by Claude Opus 4.8
parent ccee76b31e
commit ae9748db02
2 changed files with 49 additions and 3 deletions
+11 -3
View File
@@ -121,14 +121,22 @@ def _match_openrouter(model_id: str, feed: list[dict]) -> dict | None:
Bare-tail matching (``deepseek-chat`` ↔ ``deepseek/deepseek-chat``) is a
looser, lower-trust match — OpenRouter fans a model out across resellers —
so an exact id match always wins first.
so an exact id match always wins first. When several entries share the bare
tail, the highest-priced one wins: the choice must be deterministic (not
feed-order-dependent) and money-safe, since undercharging is the hazard.
The live feed has no such collisions today; this only governs the latent
case.
"""
bare = model_id.split("/", 1)[-1]
exact = next((m for m in feed if m.get("id") == model_id), None)
if exact is not None:
return exact
return next(
(m for m in feed if m.get("id", "").split("/", 1)[-1] == bare), None
matches = [m for m in feed if m.get("id", "").split("/", 1)[-1] == bare]
if not matches:
return None
return max(
matches,
key=lambda m: _as_float(m.get("pricing", {}).get("prompt")) or 0.0,
)
+38
View File
@@ -266,6 +266,44 @@ async def test_unknown_to_litellm_resolves_via_openrouter() -> None:
or_feed.assert_awaited()
@pytest.mark.asyncio
async def test_openrouter_bare_tail_collision_picks_highest_price() -> None:
"""When a bare model id matches several OpenRouter entries by tail
(``model`` ↔ ``a/model``, ``b/model``), the match must be deterministic and
money-safe: pick the highest-priced candidate regardless of feed order, so
ordering can never leave the node charging below true cost. (The live feed
has zero such collisions today; this guards the latent case.)"""
payload = {
"data": [
{"id": "zzz-phantom-model", "object": "model", "owned_by": "mystery"},
]
}
# Same bare tail, different resellers; the pricier one is listed *second*
# so a first-wins match would pick the cheaper (undercharging) entry.
or_feed = AsyncMock(
return_value=[
{
"id": "cheapco/zzz-phantom-model",
"context_length": 8192,
"pricing": {"prompt": "0.000001", "completion": "0.000002"},
},
{
"id": "premiumco/zzz-phantom-model",
"context_length": 8192,
"pricing": {"prompt": "0.000009", "completion": "0.000010"},
},
]
)
with _patch_models_endpoint(payload):
with patch("routstr.payment.models.async_fetch_openrouter_models", or_feed):
models = await GenericUpstreamProvider(base_url="http://x").fetch_models()
model = _model_by_id(models, "zzz-phantom-model")
assert model.pricing.prompt == pytest.approx(9e-06)
assert model.pricing.completion == pytest.approx(1e-05)
@pytest.mark.asyncio
async def test_openrouter_feed_fetched_once_per_discovery() -> None:
"""Two models both missing litellm must share a single OpenRouter fetch —