mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-08-09 11:04:36 +00:00
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:
co-authored by
Claude Opus 4.8
parent
ccee76b31e
commit
ae9748db02
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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 —
|
||||
|
||||
Reference in New Issue
Block a user