mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-05 20:28:23 +00:00
fix: certify and model-test send model.id like the proxy, not the client alias
This commit is contained in:
@@ -1733,8 +1733,9 @@ async def certify_upstream_provider(
|
|||||||
provider.provider_fee,
|
provider.provider_fee,
|
||||||
sats_to_usd,
|
sats_to_usd,
|
||||||
)
|
)
|
||||||
# The timeout applies per upstream call. The run makes up to five
|
# The timeout applies per upstream call. The run makes up to six
|
||||||
# calls, so the request can stay open for up to five times it.
|
# calls (models, two short probes after a max_completion_tokens retry,
|
||||||
|
# three cache probes), so the request can stay open for six times it.
|
||||||
live_rows = await run_live_checks(
|
live_rows = await run_live_checks(
|
||||||
provider.base_url,
|
provider.base_url,
|
||||||
provider.api_key,
|
provider.api_key,
|
||||||
|
|||||||
@@ -689,7 +689,8 @@ def _model_test_target(
|
|||||||
upstream.build_request_url(path, model_obj),
|
upstream.build_request_url(path, model_obj),
|
||||||
upstream.prepare_headers({"content-type": "application/json"}),
|
upstream.prepare_headers({"content-type": "application/json"}),
|
||||||
dict(upstream.prepare_params(path, None)),
|
dict(upstream.prepare_params(path, None)),
|
||||||
upstream.transform_model_name(model_id),
|
# The proxy forwards ``model.id``, not the row's client alias.
|
||||||
|
upstream.transform_model_name(model_obj.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -247,17 +247,14 @@ def shape_body(
|
|||||||
) -> Any:
|
) -> Any:
|
||||||
"""The JSON body the proxy would forward, model-name transforms included.
|
"""The JSON body the proxy would forward, model-name transforms included.
|
||||||
|
|
||||||
``prepare_request_body`` rewrites ``model`` from ``model.id``; the probe
|
``prepare_request_body`` sets ``model`` from ``model.id``, exactly as
|
||||||
keeps the id it chose (``forwarded_model_id`` first) and only applies the
|
``forward_request`` does, so an alias row's ``forwarded_model_id`` never
|
||||||
provider's own name transform to it.
|
reaches the upstream here either.
|
||||||
"""
|
"""
|
||||||
if upstream is None or model is None:
|
if upstream is None or model is None:
|
||||||
return body
|
return body
|
||||||
shaped = upstream.prepare_request_body(json.dumps(body).encode(), model)
|
shaped = upstream.prepare_request_body(json.dumps(body).encode(), model)
|
||||||
data = json.loads(shaped) if shaped else dict(body)
|
return json.loads(shaped) if shaped else body
|
||||||
if isinstance(data, dict) and isinstance(body.get("model"), str):
|
|
||||||
data["model"] = upstream.transform_model_name(body["model"])
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
async def probe_upstream(
|
async def probe_upstream(
|
||||||
@@ -876,7 +873,7 @@ async def run_live_checks(
|
|||||||
probe = await probe_upstream(
|
probe = await probe_upstream(
|
||||||
base_url,
|
base_url,
|
||||||
api_key,
|
api_key,
|
||||||
model.forwarded_model_id or model.id,
|
model.id,
|
||||||
endpoint_tag=endpoint_tag,
|
endpoint_tag=endpoint_tag,
|
||||||
client=client,
|
client=client,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
|
|||||||
@@ -604,7 +604,7 @@ async def run_cache_checks(
|
|||||||
probe = await probe_cache(
|
probe = await probe_cache(
|
||||||
base_url,
|
base_url,
|
||||||
api_key,
|
api_key,
|
||||||
model.forwarded_model_id or model.id,
|
model.id,
|
||||||
endpoint_tag=endpoint_tag,
|
endpoint_tag=endpoint_tag,
|
||||||
client=client,
|
client=client,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
|
|||||||
@@ -106,7 +106,9 @@ async def test_certify_forwarded_alias_listed_path_succeeds(
|
|||||||
assert response.status_code == 200, response.text
|
assert response.status_code == 200, response.text
|
||||||
assert chat.call_count == 1
|
assert chat.call_count == 1
|
||||||
body: dict[str, Any] = json.loads(chat.calls[0].request.content)
|
body: dict[str, Any] = json.loads(chat.calls[0].request.content)
|
||||||
assert body["model"] == forwarded
|
# The path is keyed by the exposed id, but the upstream gets what the
|
||||||
|
# proxy sends for this row: transform_model_name(model.id).
|
||||||
|
assert body["model"] == "local-alias"
|
||||||
assert body["provider"] == {"order": ["endpoint"], "allow_fallbacks": False}
|
assert body["provider"] == {"order": ["endpoint"], "allow_fallbacks": False}
|
||||||
mismatch = await integration_client.post(
|
mismatch = await integration_client.post(
|
||||||
f"/admin/api/upstream-providers/{provider.id}/certify",
|
f"/admin/api/upstream-providers/{provider.id}/certify",
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
"""Certification must send the upstream the model id the proxy sends.
|
||||||
|
|
||||||
|
An admin alias row has ``id`` and ``forwarded_model_id`` that differ. The
|
||||||
|
proxy forwards ``transform_model_name(model.id)`` (``prepare_request_body``),
|
||||||
|
so a probe that sends the forwarded id certifies a request no client can make.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import respx
|
||||||
|
from httpx import AsyncClient, Response
|
||||||
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
|
from routstr.core.db import ApiKey
|
||||||
|
from routstr.proxy import reinitialize_upstreams
|
||||||
|
|
||||||
|
from .test_certify_endpoint import _admin_headers, _make_provider, _model_row
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@respx.mock
|
||||||
|
async def test_certify_and_proxy_send_the_same_model(
|
||||||
|
integration_session: AsyncSession,
|
||||||
|
integration_client: AsyncClient,
|
||||||
|
) -> None:
|
||||||
|
base_url = "https://certify-upstream.example/v1"
|
||||||
|
respx.get(f"{base_url}/models").mock(return_value=Response(200, json={"data": []}))
|
||||||
|
chat = respx.post(f"{base_url}/chat/completions").mock(
|
||||||
|
return_value=Response(
|
||||||
|
200,
|
||||||
|
json={
|
||||||
|
"id": "x",
|
||||||
|
"object": "chat.completion",
|
||||||
|
"model": "m",
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"message": {"role": "assistant", "content": "ok"},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": {"prompt_tokens": 5, "completion_tokens": 1},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
provider = await _make_provider(integration_session)
|
||||||
|
model = _model_row(provider.id, model_id="row-id") # type: ignore[arg-type]
|
||||||
|
model.forwarded_model_id = "client-alias"
|
||||||
|
integration_session.add(model)
|
||||||
|
integration_session.add(
|
||||||
|
ApiKey(
|
||||||
|
hashed_key="certify-contract", balance=10**9, created_at=int(time.time())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await integration_session.commit()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("routstr.payment.models.sats_usd_price", return_value=0.0005),
|
||||||
|
patch("routstr.payment.cost_calculation.sats_usd_price", return_value=0.0005),
|
||||||
|
patch("routstr.payment.price.SATS_USD_PRICE", 0.0005),
|
||||||
|
):
|
||||||
|
await reinitialize_upstreams()
|
||||||
|
proxied = await integration_client.post(
|
||||||
|
"/v1/chat/completions",
|
||||||
|
headers={"Authorization": "Bearer sk-certify-contract"},
|
||||||
|
json={
|
||||||
|
"model": "client-alias",
|
||||||
|
"messages": [{"role": "user", "content": "hi"}],
|
||||||
|
"max_tokens": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert proxied.status_code == 200, proxied.text
|
||||||
|
certified = await integration_client.post(
|
||||||
|
f"/admin/api/upstream-providers/{provider.id}/certify",
|
||||||
|
headers=_admin_headers(),
|
||||||
|
json={"model_id": "row-id", "check_cache": False},
|
||||||
|
)
|
||||||
|
assert certified.status_code == 200, certified.text
|
||||||
|
|
||||||
|
bodies: list[dict[str, Any]] = [
|
||||||
|
json.loads(call.request.content) for call in chat.calls
|
||||||
|
]
|
||||||
|
assert len(bodies) == 2
|
||||||
|
proxy_model, certify_model = bodies[0]["model"], bodies[1]["model"]
|
||||||
|
assert certify_model == proxy_model
|
||||||
Reference in New Issue
Block a user