feat: drop provider id from model paths and route them to the cheapest provider

This commit is contained in:
9qeklajc
2026-09-20 15:37:41 +02:00
parent ad98845428
commit 2f1d36ae5f
5 changed files with 283 additions and 99 deletions
+19 -11
View File
@@ -345,13 +345,13 @@ GET /v1/models/paths
"id": "anthropic/claude-sonnet-4", "id": "anthropic/claude-sonnet-4",
"paths": [ "paths": [
{ {
"path": "url=https%3A%2F%2Fapi.anthropic.com%2Fv1&provider-id=12&model-id=anthropic%2Fclaude-sonnet-4", "path": "url=https%3A%2F%2Fapi.anthropic.com%2Fv1&model-id=anthropic%2Fclaude-sonnet-4",
"provider": {"id": 12, "slug": "anthropic-primary", "type": "anthropic"}, "provider": {"slug": "anthropic-primary", "type": "anthropic"},
"endpoint": null "endpoint": null
}, },
{ {
"path": "url=https%3A%2F%2Fopenrouter.ai%2Fapi%2Fv1&provider-id=42&model-id=anthropic%2Fclaude-sonnet-4&endpoint=google-vertex%2Fus", "path": "url=https%3A%2F%2Fopenrouter.ai%2Fapi%2Fv1&model-id=anthropic%2Fclaude-sonnet-4&endpoint=google-vertex%2Fus",
"provider": {"id": 42, "slug": "openrouter-main", "type": "openrouter"}, "provider": {"slug": "openrouter-main", "type": "openrouter"},
"endpoint": {"tag": "google-vertex/us", "name": "Google"} "endpoint": {"tag": "google-vertex/us", "name": "Google"}
} }
] ]
@@ -362,13 +362,21 @@ GET /v1/models/paths
``` ```
`path` is an opaque, percent-encoded selector. Clients must store and return it `path` is an opaque, percent-encoded selector. Clients must store and return it
unchanged rather than parsing or reconstructing it. It identifies the exact unchanged rather than parsing or reconstructing it. It identifies the route with
configured route with `url`, `provider-id`, and `model-id`. To avoid exposing `url` and `model-id`. To avoid exposing private network details, a configured
private network details, a configured private IP address or any URL with an private IP address or any URL with an explicit port is advertised as
explicit port is advertised as `http://localhost`. OpenRouter routes additionally `http://localhost`. OpenRouter routes additionally preserve the exact
preserve the exact machine-readable endpoint `tag`. Provider slugs/types and machine-readable endpoint `tag`. Provider slugs/types and endpoint names remain
endpoint names remain display data. When request-side selection is implemented, display data. When request-side selection is implemented, an endpoint tag must
an endpoint tag must not silently fall back to another backend. not silently fall back to another backend.
A path names no provider, so several configured providers sharing an upstream URL
collapse onto a single path. Such a path always routes to the cheapest of those
providers, and the advertised slug, type and pricing describe that same cheapest
provider. A pinned path never fails over: if the selected provider errors, the
error is returned rather than retried elsewhere. Paths issued before this change
still carry `provider-id` and are still honoured, pinning the exact provider they
name.
### List Paths for One Model ### List Paths for One Model
+17 -7
View File
@@ -132,12 +132,18 @@ def _candidate_for_selector(
selector: ModelPathSelector, selector: ModelPathSelector,
candidates: list[tuple[Model, BaseUpstreamProvider]], candidates: list[tuple[Model, BaseUpstreamProvider]],
) -> tuple[Model, BaseUpstreamProvider] | None: ) -> tuple[Model, BaseUpstreamProvider] | None:
"""Resolve a route selector to one candidate.
``candidates`` is ranked by cost, so the first URL match is the cheapest
provider configured against that URL. A selector still carrying a legacy
``provider-id`` keeps pinning that exact provider instead.
"""
for model_obj, upstream in candidates: for model_obj, upstream in candidates:
if ( if public_provider_url(upstream.base_url) != selector.base_url:
upstream.db_id == selector.provider_id continue
and public_provider_url(upstream.base_url) == selector.base_url if selector.provider_id is not None and upstream.db_id != selector.provider_id:
): continue
return model_obj, upstream return model_obj, upstream
return None return None
@@ -536,10 +542,14 @@ async def _proxy(
if selector is not None: if selector is not None:
pinned = _candidate_for_selector(selector, candidates) pinned = _candidate_for_selector(selector, candidates)
if pinned is None: if pinned is None:
target = (
f"provider {selector.provider_id}"
if selector.provider_id is not None
else f"'{selector.base_url}'"
)
return create_error_response( return create_error_response(
"invalid_model_path", "invalid_model_path",
f"Model '{selector.model_id}' is not routable through provider " f"Model '{selector.model_id}' is not routable through {target}",
f"{selector.provider_id}",
404, 404,
request=request, request=request,
) )
+50 -45
View File
@@ -2,11 +2,16 @@
Exposes every selectable upstream route a Routstr model is reachable through. Exposes every selectable upstream route a Routstr model is reachable through.
A path is a standard percent-encoded query string containing the configured A path is a standard percent-encoded query string containing the configured
upstream URL, provider ID, client-visible model ID and, for an exact OpenRouter upstream URL, client-visible model ID and, for an exact OpenRouter endpoint,
endpoint, its machine-readable tag:: its machine-readable tag::
url=https%3A%2F%2Fapi.anthropic.com%2Fv1&provider-id=12&model-id=claude-sonnet-4 url=https%3A%2F%2Fapi.anthropic.com%2Fv1&model-id=claude-sonnet-4
url=https%3A%2F%2Fopenrouter.ai%2Fapi%2Fv1&provider-id=42&model-id=claude-sonnet-4&endpoint=google-vertex%2Fus url=https%3A%2F%2Fopenrouter.ai%2Fapi%2Fv1&model-id=claude-sonnet-4&endpoint=google-vertex%2Fus
A path names no provider, so several providers sharing an upstream URL collapse
onto one selector that routes to the cheapest of them. ``provider-id`` is still
accepted when decoding so paths issued before that change keep pinning the exact
provider they named.
""" """
from __future__ import annotations from __future__ import annotations
@@ -16,6 +21,7 @@ import ipaddress
import json import json
import random import random
import time import time
from collections.abc import Iterable
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Callable from typing import TYPE_CHECKING, Any, Callable
from urllib.parse import parse_qsl, urlencode, urlsplit from urllib.parse import parse_qsl, urlencode, urlsplit
@@ -117,16 +123,11 @@ def public_provider_url(base_url: str) -> str:
def encode_model_path( def encode_model_path(
base_url: str, base_url: str,
provider_id: int,
model_id: str, model_id: str,
endpoint_tag: str | None = None, endpoint_tag: str | None = None,
) -> str: ) -> str:
"""Encode the complete upstream route selector advertised to clients.""" """Encode the upstream route selector advertised to clients."""
components: list[tuple[str, str | int]] = [ components = [("url", base_url), ("model-id", model_id)]
("url", base_url),
("provider-id", provider_id),
("model-id", model_id),
]
if endpoint_tag: if endpoint_tag:
components.append(("endpoint", endpoint_tag)) components.append(("endpoint", endpoint_tag))
return urlencode(components) return urlencode(components)
@@ -137,9 +138,9 @@ class ModelPathSelector:
"""Decoded client-supplied route selector.""" """Decoded client-supplied route selector."""
base_url: str base_url: str
provider_id: int
model_id: str model_id: str
endpoint_tag: str | None = None endpoint_tag: str | None = None
provider_id: int | None = None
def decode_model_path(path: str) -> ModelPathSelector | None: def decode_model_path(path: str) -> ModelPathSelector | None:
@@ -166,20 +167,21 @@ def decode_model_path(path: str) -> ModelPathSelector | None:
return None return None
base_url = params.get("url", "") base_url = params.get("url", "")
model_id = params.get("model-id", "") model_id = params.get("model-id", "")
raw_provider_id = params.get("provider-id", "") if not base_url or not model_id:
if not base_url or not model_id or not raw_provider_id:
return None
try:
provider_id = int(raw_provider_id)
except ValueError:
return None
if provider_id <= 0:
return None return None
provider_id: int | None = None
if (raw_provider_id := params.get("provider-id")) is not None:
try:
provider_id = int(raw_provider_id)
except ValueError:
return None
if provider_id <= 0:
return None
return ModelPathSelector( return ModelPathSelector(
base_url=base_url, base_url=base_url,
provider_id=provider_id,
model_id=model_id, model_id=model_id,
endpoint_tag=params.get("endpoint") or None, endpoint_tag=params.get("endpoint") or None,
provider_id=provider_id,
) )
@@ -490,9 +492,7 @@ async def _collect_provider_paths(
model_id = exposed_model_id(model) model_id = exposed_model_id(model)
return DiscoveredPath( return DiscoveredPath(
model_id=model_id, model_id=model_id,
path=encode_model_path( path=encode_model_path(provider_identity.base_url, model_id),
provider_identity.base_url, provider_identity.id, model_id
),
provider=provider_identity, provider=provider_identity,
model_metadata=_serialize_model_metadata(model, model_id), model_metadata=_serialize_model_metadata(model, model_id),
) )
@@ -529,7 +529,6 @@ async def _collect_provider_paths(
model_id=model_id, model_id=model_id,
path=encode_model_path( path=encode_model_path(
provider_identity.base_url, provider_identity.base_url,
provider_identity.id,
model_id, model_id,
endpoint.tag, endpoint.tag,
), ),
@@ -900,7 +899,6 @@ def _serialize_path(row: ModelPathRow, provider_fee: float) -> dict[str, Any]:
return { return {
"path": row.path, "path": row.path,
"provider": { "provider": {
"id": row.upstream_provider_id,
"slug": row.provider_slug, "slug": row.provider_slug,
"type": row.provider_type, "type": row.provider_type,
}, },
@@ -914,6 +912,25 @@ async def _provider_fees(session: "AsyncSession") -> dict[int, float]:
return {row.id: row.provider_fee for row in rows if row.id is not None} return {row.id: row.provider_fee for row in rows if row.id is not None}
def _cheapest_rows_by_path(
rows: Iterable[ModelPathRow], fees: dict[int, float]
) -> list[tuple[ModelPathRow, float]]:
"""Collapse rows sharing a path onto the cheapest provider, with its fee.
Routing sends a provider-less path to the cheapest provider on that URL, so
the advertised slug and pricing must come from that same provider or clients
are quoted a price they will never be charged.
"""
best: dict[tuple[str, str], tuple[ModelPathRow, float]] = {}
for row in rows:
key = (row.model_id, row.path)
fee = fees.get(row.upstream_provider_id, 1.01)
incumbent = best.get(key)
if incumbent is None or fee < incumbent[1]:
best[key] = (row, fee)
return list(best.values())
async def get_all_model_paths() -> dict: async def get_all_model_paths() -> dict:
"""All models with exact routes and provider-specific model metadata.""" """All models with exact routes and provider-specific model metadata."""
async with create_session() as session: async with create_session() as session:
@@ -929,16 +946,9 @@ async def get_all_model_paths() -> dict:
fees = await _provider_fees(session) fees = await _provider_fees(session)
grouped: dict[str, list[dict[str, Any]]] = {} grouped: dict[str, list[dict[str, Any]]] = {}
seen_paths: dict[str, set[str]] = {} updated_at = max((row.updated_at for row in rows), default=0)
updated_at = 0 for row, fee in _cheapest_rows_by_path(rows, fees):
for row in rows: grouped.setdefault(row.model_id, []).append(_serialize_path(row, fee))
updated_at = max(updated_at, row.updated_at)
if row.path in seen_paths.setdefault(row.model_id, set()):
continue
seen_paths[row.model_id].add(row.path)
grouped.setdefault(row.model_id, []).append(
_serialize_path(row, fees.get(row.upstream_provider_id, 1.01))
)
data = [ data = [
{ {
"id": grouped_model_id, "id": grouped_model_id,
@@ -974,13 +984,8 @@ async def get_paths_for_model(model_id: str) -> dict:
rows = await load_rows(session, unprefixed_id) rows = await load_rows(session, unprefixed_id)
fees = await _provider_fees(session) fees = await _provider_fees(session)
seen: set[str] = set() updated_at = max((row.updated_at for row in rows), default=0)
paths: list[dict] = [] paths = [
updated_at = 0 _serialize_path(row, fee) for row, fee in _cheapest_rows_by_path(rows, fees)
for row in rows: ]
updated_at = max(updated_at, row.updated_at)
if row.path in seen:
continue
seen.add(row.path)
paths.append(_serialize_path(row, fees.get(row.upstream_provider_id, 1.01)))
return {"data": paths, "updated_at": updated_at or None} return {"data": paths, "updated_at": updated_at or None}
+88 -27
View File
@@ -75,17 +75,31 @@ async def _run_proxy(
def test_decode_model_path_round_trips_encode() -> None: def test_decode_model_path_round_trips_encode() -> None:
selector = decode_model_path( selector = decode_model_path(
encode_model_path("https://openrouter.ai/api/v1", 7, MODEL_ID, "deepinfra/fp8") encode_model_path("https://openrouter.ai/api/v1", MODEL_ID, "deepinfra/fp8")
) )
assert selector is not None assert selector is not None
assert selector.base_url == "https://openrouter.ai/api/v1" assert selector.base_url == "https://openrouter.ai/api/v1"
assert selector.provider_id == 7 assert selector.provider_id is None
assert selector.model_id == MODEL_ID assert selector.model_id == MODEL_ID
assert selector.endpoint_tag == "deepinfra/fp8" assert selector.endpoint_tag == "deepinfra/fp8"
def test_encoded_path_carries_no_provider_id() -> None:
assert "provider-id" not in encode_model_path("http://localhost", MODEL_ID)
def test_decode_model_path_still_accepts_a_legacy_provider_id() -> None:
selector = decode_model_path(
"url=http%3A%2F%2Flocalhost&provider-id=7&model-id=test-model"
)
assert selector is not None
assert selector.provider_id == 7
assert selector.base_url == "http://localhost"
assert selector.model_id == MODEL_ID
def test_decode_model_path_without_endpoint_has_no_tag() -> None: def test_decode_model_path_without_endpoint_has_no_tag() -> None:
selector = decode_model_path(encode_model_path("http://localhost", 1, MODEL_ID)) selector = decode_model_path(encode_model_path("http://localhost", MODEL_ID))
assert selector is not None assert selector is not None
assert selector.endpoint_tag is None assert selector.endpoint_tag is None
@@ -94,10 +108,12 @@ def test_decode_model_path_without_endpoint_has_no_tag() -> None:
"path", "path",
[ [
"", "",
"url=http://localhost&model-id=test-model",
"url=http://localhost&provider-id=abc&model-id=test-model", "url=http://localhost&provider-id=abc&model-id=test-model",
"url=http://localhost&provider-id=0&model-id=test-model",
"provider-id=1&model-id=test-model", "provider-id=1&model-id=test-model",
"url=http://localhost&provider-id=1", "url=http://localhost&provider-id=1",
"model-id=test-model",
"url=http://localhost",
], ],
) )
def test_decode_model_path_rejects_malformed_selectors(path: str) -> None: def test_decode_model_path_rejects_malformed_selectors(path: str) -> None:
@@ -105,20 +121,63 @@ def test_decode_model_path_rejects_malformed_selectors(path: str) -> None:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_model_path_routes_to_the_selected_provider() -> None: async def test_model_path_routes_to_the_cheapest_provider_sharing_the_url() -> None:
first, selected = _make_upstream(1), _make_upstream(2) # get_candidates ranks by cost, so the first match for a provider-less
# selector is the cheapest provider configured against that URL.
cheapest, pricier = _make_upstream(1), _make_upstream(2)
request = _make_request( request = _make_request(
{ {
"authorization": "Bearer sk-mpkey", "authorization": "Bearer sk-mpkey",
"x-routstr-model-path": encode_model_path("http://localhost", 2, MODEL_ID), "x-routstr-model-path": encode_model_path("http://localhost", MODEL_ID),
}, },
json.dumps({"model": MODEL_ID}).encode(), json.dumps({"model": MODEL_ID}).encode(),
) )
await _run_proxy(request, [(MagicMock(), first), (MagicMock(), selected)]) await _run_proxy(request, [(MagicMock(), cheapest), (MagicMock(), pricier)])
selected.forward_request.assert_awaited_once() cheapest.forward_request.assert_awaited_once()
first.forward_request.assert_not_awaited() pricier.forward_request.assert_not_awaited()
@pytest.mark.asyncio
async def test_cheapest_provider_failure_does_not_fall_back_to_the_pricier_one() -> (
None
):
cheapest, pricier = _make_upstream(1, status_code=503), _make_upstream(2)
request = _make_request(
{
"authorization": "Bearer sk-mpkey",
"x-routstr-model-path": encode_model_path("http://localhost", MODEL_ID),
},
json.dumps({"model": MODEL_ID}).encode(),
)
response = await _run_proxy(
request, [(MagicMock(), cheapest), (MagicMock(), pricier)]
)
assert response.status_code == 503
cheapest.forward_request.assert_awaited_once()
pricier.forward_request.assert_not_awaited()
@pytest.mark.asyncio
async def test_legacy_provider_id_still_pins_that_exact_provider() -> None:
cheapest, pinned = _make_upstream(1), _make_upstream(2)
request = _make_request(
{
"authorization": "Bearer sk-mpkey",
"x-routstr-model-path": (
f"url=http%3A%2F%2Flocalhost&provider-id=2&model-id={MODEL_ID}"
),
},
json.dumps({"model": MODEL_ID}).encode(),
)
await _run_proxy(request, [(MagicMock(), cheapest), (MagicMock(), pinned)])
pinned.forward_request.assert_awaited_once()
cheapest.forward_request.assert_not_awaited()
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -131,7 +190,7 @@ async def test_model_path_failure_is_returned_without_falling_back(
request = _make_request( request = _make_request(
{ {
"authorization": "Bearer sk-mpkey", "authorization": "Bearer sk-mpkey",
"x-routstr-model-path": encode_model_path("http://localhost", 1, MODEL_ID), "x-routstr-model-path": encode_model_path("http://localhost", MODEL_ID),
}, },
json.dumps({"model": MODEL_ID}).encode(), json.dumps({"model": MODEL_ID}).encode(),
) )
@@ -146,12 +205,14 @@ async def test_model_path_failure_is_returned_without_falling_back(
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_unknown_provider_in_model_path_is_rejected() -> None: async def test_unknown_legacy_provider_in_model_path_is_rejected() -> None:
upstream = _make_upstream(1) upstream = _make_upstream(1)
request = _make_request( request = _make_request(
{ {
"authorization": "Bearer sk-mpkey", "authorization": "Bearer sk-mpkey",
"x-routstr-model-path": encode_model_path("http://localhost", 99, MODEL_ID), "x-routstr-model-path": (
f"url=http%3A%2F%2Flocalhost&provider-id=99&model-id={MODEL_ID}"
),
}, },
json.dumps({"model": MODEL_ID}).encode(), json.dumps({"model": MODEL_ID}).encode(),
) )
@@ -170,7 +231,7 @@ async def test_model_path_disagreeing_with_the_body_model_is_rejected() -> None:
{ {
"authorization": "Bearer sk-mpkey", "authorization": "Bearer sk-mpkey",
"x-routstr-model-path": encode_model_path( "x-routstr-model-path": encode_model_path(
"http://localhost", 1, "other-model" "http://localhost", "other-model"
), ),
}, },
json.dumps({"model": MODEL_ID}).encode(), json.dumps({"model": MODEL_ID}).encode(),
@@ -207,7 +268,7 @@ async def test_endpoint_tag_pins_the_upstream_subprovider() -> None:
{ {
"authorization": "Bearer sk-mpkey", "authorization": "Bearer sk-mpkey",
"x-routstr-model-path": encode_model_path( "x-routstr-model-path": encode_model_path(
"https://openrouter.ai/api/v1", 1, MODEL_ID, "deepinfra/fp8" "https://openrouter.ai/api/v1", MODEL_ID, "deepinfra/fp8"
), ),
}, },
json.dumps({"model": MODEL_ID}).encode(), json.dumps({"model": MODEL_ID}).encode(),
@@ -245,7 +306,7 @@ async def test_selector_url_must_match_configured_provider() -> None:
{ {
"authorization": "Bearer key", "authorization": "Bearer key",
"x-routstr-model-path": encode_model_path( "x-routstr-model-path": encode_model_path(
"http://169.254.169.254", 1, MODEL_ID "http://169.254.169.254", MODEL_ID
), ),
}, },
json.dumps({"model": MODEL_ID}).encode(), json.dumps({"model": MODEL_ID}).encode(),
@@ -266,7 +327,7 @@ async def test_endpoint_pin_cannot_be_stripped_on_retry() -> None:
{ {
"authorization": "Bearer key", "authorization": "Bearer key",
"x-routstr-model-path": encode_model_path( "x-routstr-model-path": encode_model_path(
upstream.base_url, 1, MODEL_ID, "deepinfra/fp8" upstream.base_url, MODEL_ID, "deepinfra/fp8"
), ),
}, },
json.dumps({"model": MODEL_ID}).encode(), json.dumps({"model": MODEL_ID}).encode(),
@@ -298,7 +359,7 @@ async def test_cashu_receives_endpoint_pinned_body(path: str, handler: str) -> N
{ {
"x-cashu": "test-token", "x-cashu": "test-token",
"x-routstr-model-path": encode_model_path( "x-routstr-model-path": encode_model_path(
upstream.base_url, 1, MODEL_ID, "deepinfra/fp8" upstream.base_url, MODEL_ID, "deepinfra/fp8"
), ),
}, },
json.dumps( json.dumps(
@@ -354,7 +415,7 @@ async def test_cashu_pin_reaches_http_transport(path: str, status_code: int) ->
{ {
"x-cashu": "test-token", "x-cashu": "test-token",
"x-routstr-model-path": encode_model_path( "x-routstr-model-path": encode_model_path(
upstream.base_url, 1, MODEL_ID, "deepinfra/fp8" upstream.base_url, MODEL_ID, "deepinfra/fp8"
), ),
}, },
json.dumps({"model": MODEL_ID, "provider": {"allow_fallbacks": True}}).encode(), json.dumps({"model": MODEL_ID, "provider": {"allow_fallbacks": True}}).encode(),
@@ -418,7 +479,7 @@ async def test_pinned_exception_does_not_fall_back() -> None:
request = _make_request( request = _make_request(
{ {
"authorization": "Bearer key", "authorization": "Bearer key",
"x-routstr-model-path": encode_model_path(first.base_url, 1, MODEL_ID), "x-routstr-model-path": encode_model_path(first.base_url, MODEL_ID),
}, },
json.dumps({"model": MODEL_ID}).encode(), json.dumps({"model": MODEL_ID}).encode(),
) )
@@ -442,7 +503,7 @@ async def test_unsupported_endpoint_pins_fail_before_payment(
headers = { headers = {
"x-cashu": "test-token", "x-cashu": "test-token",
"x-routstr-model-path": encode_model_path( "x-routstr-model-path": encode_model_path(
upstream.base_url, 1, MODEL_ID, "deepinfra/fp8" upstream.base_url, MODEL_ID, "deepinfra/fp8"
), ),
} }
if is_ehbp: if is_ehbp:
@@ -471,7 +532,7 @@ async def test_ehbp_pin_does_not_fall_back(cashu: bool) -> None:
headers = { headers = {
"ehbp-encapsulated-key": "sealed", "ehbp-encapsulated-key": "sealed",
"x-routstr-model": MODEL_ID, "x-routstr-model": MODEL_ID,
"x-routstr-model-path": encode_model_path(selected.base_url, 1, MODEL_ID), "x-routstr-model-path": encode_model_path(selected.base_url, MODEL_ID),
} }
headers.update({"x-cashu": "token"} if cashu else {"authorization": "Bearer key"}) headers.update({"x-cashu": "token"} if cashu else {"authorization": "Bearer key"})
request = _make_request(headers, b"encrypted-body") request = _make_request(headers, b"encrypted-body")
@@ -495,7 +556,7 @@ async def test_duplicate_header_fields_are_rejected() -> None:
from starlette.datastructures import Headers from starlette.datastructures import Headers
selected = _make_upstream(1) selected = _make_upstream(1)
route = encode_model_path(selected.base_url, 1, MODEL_ID).encode() route = encode_model_path(selected.base_url, MODEL_ID).encode()
request = _make_request({}, json.dumps({"model": MODEL_ID}).encode()) request = _make_request({}, json.dumps({"model": MODEL_ID}).encode())
request.headers = Headers( request.headers = Headers(
raw=[ raw=[
@@ -512,7 +573,7 @@ async def test_duplicate_header_fields_are_rejected() -> None:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_attestation_does_not_ignore_model_path() -> None: async def test_attestation_does_not_ignore_model_path() -> None:
request = _make_request( request = _make_request(
{"x-routstr-model-path": encode_model_path("http://localhost", 1, MODEL_ID)}, {"x-routstr-model-path": encode_model_path("http://localhost", MODEL_ID)},
b"", b"",
) )
request.method = "GET" request.method = "GET"
@@ -528,7 +589,7 @@ async def test_model_fallback_list_is_rejected_when_pinned() -> None:
request = _make_request( request = _make_request(
{ {
"authorization": "Bearer key", "authorization": "Bearer key",
"x-routstr-model-path": encode_model_path(selected.base_url, 1, MODEL_ID), "x-routstr-model-path": encode_model_path(selected.base_url, MODEL_ID),
}, },
json.dumps({"model": MODEL_ID, "models": ["other-model"]}).encode(), json.dumps({"model": MODEL_ID, "models": ["other-model"]}).encode(),
) )
@@ -564,7 +625,7 @@ async def test_pinned_recovery_stays_on_selected_provider(
{ {
"authorization": "Bearer key", "authorization": "Bearer key",
"x-routstr-model-path": encode_model_path( "x-routstr-model-path": encode_model_path(
selected.base_url, 1, MODEL_ID, endpoint selected.base_url, MODEL_ID, endpoint
), ),
}, },
json.dumps( json.dumps(
@@ -608,7 +669,7 @@ async def test_pinned_recovery_preserves_routing_fields(
{ {
"authorization": "Bearer key", "authorization": "Bearer key",
"x-routstr-model-path": encode_model_path( "x-routstr-model-path": encode_model_path(
selected.base_url, 1, MODEL_ID, endpoint selected.base_url, MODEL_ID, endpoint
), ),
}, },
json.dumps( json.dumps(
+109 -9
View File
@@ -272,6 +272,17 @@ async def _set_provider_fee(engine: AsyncEngine, provider_id: int, fee: float) -
await session.commit() await session.commit()
async def _set_provider_base_url(
engine: AsyncEngine, provider_id: int, base_url: str
) -> None:
async with AsyncSession(engine) as session:
provider = await session.get(UpstreamProviderRow, provider_id)
assert provider is not None
provider.base_url = base_url
session.add(provider)
await session.commit()
def _paths_of(payload: dict, model_id: str) -> set[str]: def _paths_of(payload: dict, model_id: str) -> set[str]:
for entry in payload["data"]: for entry in payload["data"]:
if entry["id"] == model_id: if entry["id"] == model_id:
@@ -289,7 +300,7 @@ def _expected_path(
endpoint_tag: str | None = None, endpoint_tag: str | None = None,
) -> str: ) -> str:
return mp.encode_model_path( return mp.encode_model_path(
f"https://provider-{provider_id}", provider_id, model_id, endpoint_tag f"https://provider-{provider_id}", model_id, endpoint_tag
) )
@@ -308,7 +319,6 @@ def _path_entry(
return { return {
"path": _expected_path(provider_id, model_id, endpoint_tag), "path": _expected_path(provider_id, model_id, endpoint_tag),
"provider": { "provider": {
"id": provider_id,
"slug": provider_slug or f"p{provider_id}", "slug": provider_slug or f"p{provider_id}",
"type": provider_type "type": provider_type
or ("anthropic" if provider_id == 1 else "openrouter"), or ("anthropic" if provider_id == 1 else "openrouter"),
@@ -368,21 +378,20 @@ def test_public_provider_url_preserves_public_urls_without_ports() -> None:
assert mp.public_provider_url("http://localhost") == "http://localhost" assert mp.public_provider_url("http://localhost") == "http://localhost"
def test_encode_model_path_includes_complete_route_identity() -> None: def test_encode_model_path_omits_provider_id() -> None:
assert mp.encode_model_path( assert mp.encode_model_path(
"https://openrouter.ai/api/v1", 42, "anthropic/claude-sonnet-4" "https://openrouter.ai/api/v1", "anthropic/claude-sonnet-4"
) == ( ) == (
"url=https%3A%2F%2Fopenrouter.ai%2Fapi%2Fv1" "url=https%3A%2F%2Fopenrouter.ai%2Fapi%2Fv1"
"&provider-id=42&model-id=anthropic%2Fclaude-sonnet-4" "&model-id=anthropic%2Fclaude-sonnet-4"
) )
assert mp.encode_model_path( assert mp.encode_model_path(
"https://openrouter.ai/api/v1", "https://openrouter.ai/api/v1",
42,
"anthropic/claude-sonnet-4", "anthropic/claude-sonnet-4",
"google-vertex/us-east5", "google-vertex/us-east5",
) == ( ) == (
"url=https%3A%2F%2Fopenrouter.ai%2Fapi%2Fv1" "url=https%3A%2F%2Fopenrouter.ai%2Fapi%2Fv1"
"&provider-id=42&model-id=anthropic%2Fclaude-sonnet-4" "&model-id=anthropic%2Fclaude-sonnet-4"
"&endpoint=google-vertex%2Fus-east5" "&endpoint=google-vertex%2Fus-east5"
) )
@@ -506,7 +515,7 @@ async def test_direct_path_masks_private_configured_provider_url(
await mp.refresh_model_paths([provider]) await mp.refresh_model_paths([provider])
assert _paths_of(await mp.get_all_model_paths(), "local-model") == { assert _paths_of(await mp.get_all_model_paths(), "local-model") == {
mp.encode_model_path("http://localhost", 1, "local-model") mp.encode_model_path("http://localhost", "local-model")
} }
@@ -886,7 +895,7 @@ async def test_openrouter_provider_adds_endpoint_paths(
assert { assert {
item["endpoint"]["name"] for item in payload["data"] if item["endpoint"] item["endpoint"]["name"] for item in payload["data"] if item["endpoint"]
} == {"Google"} } == {"Google"}
assert {item["provider"]["id"] for item in payload["data"]} == {2} assert {item["provider"]["slug"] for item in payload["data"]} == {"p2"}
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -984,6 +993,97 @@ async def test_path_pricing_uses_the_provider_fee_of_its_own_provider(
) )
@pytest.mark.asyncio
async def test_providers_sharing_a_url_collapse_onto_the_cheapest_path(
patched_session: AsyncEngine, sats_quote: float
) -> None:
"""One selector per URL, describing the provider routing will actually use."""
shared_url = "https://shared.example/v1"
await _set_provider_base_url(patched_session, 1, shared_url)
await _set_provider_base_url(patched_session, 2, shared_url)
await _set_provider_fee(patched_session, 1, 2.0)
await _set_provider_fee(patched_session, 2, 1.1)
await mp.refresh_model_paths(
[
_FakeProvider(
provider_type="anthropic",
base_url=shared_url,
models=[_model("shared-model")],
db_id=1,
),
_FakeProvider(
provider_type="openrouter",
base_url=shared_url,
models=[_model("shared-model")],
db_id=2,
),
]
)
payload = await mp.get_paths_for_model("shared-model")
assert [item["path"] for item in payload["data"]] == [
mp.encode_model_path(shared_url, "shared-model")
]
assert payload["data"][0]["provider"] == {"slug": "p2", "type": "openrouter"}
@pytest.mark.asyncio
async def test_collapsed_path_advertises_the_cheapest_provider_pricing(
patched_session: AsyncEngine, sats_quote: float
) -> None:
shared_url = "https://shared.example/v1"
await _set_provider_base_url(patched_session, 1, shared_url)
await _set_provider_base_url(patched_session, 2, shared_url)
await _set_provider_fee(patched_session, 1, 2.0)
await _set_provider_fee(patched_session, 2, 1.1)
priced = _model("shared-model")
priced.pricing = {"prompt": "0.000005", "completion": "0.000005"}
await mp.refresh_model_paths(
[
_FakeProvider(
provider_type="anthropic",
base_url=shared_url,
models=[priced],
db_id=1,
),
_FakeProvider(
provider_type="openrouter",
base_url=shared_url,
models=[priced],
db_id=2,
),
]
)
payload = await mp.get_paths_for_model("shared-model")
assert len(payload["data"]) == 1
assert payload["data"][0]["model"]["pricing"]["prompt"] == pytest.approx(
0.000005 * 1.1
)
@pytest.mark.asyncio
async def test_paths_advertise_no_provider_id(patched_session: AsyncEngine) -> None:
await mp.refresh_model_paths(
[
_FakeProvider(
provider_type="anthropic",
base_url="https://provider-1",
models=[_model("claude-x")],
db_id=1,
)
]
)
payload = await mp.get_all_model_paths()
entry = payload["data"][0]["paths"][0]
assert "provider-id" not in entry["path"]
assert "id" not in entry["provider"]
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_paths_keep_upstream_pricing_when_the_quote_is_unavailable( async def test_paths_keep_upstream_pricing_when_the_quote_is_unavailable(
patched_session: AsyncEngine, monkeypatch: pytest.MonkeyPatch patched_session: AsyncEngine, monkeypatch: pytest.MonkeyPatch