mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-05 12:28:22 +00:00
fix: report openrouter:unknown when OpenRouter omits the serving provider
This commit is contained in:
@@ -2,6 +2,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
import httpx
|
||||
|
||||
from ..core.logging import get_logger
|
||||
from ..payment.models import Model, async_fetch_openrouter_models
|
||||
from .base import BaseUpstreamProvider, _reported_provider
|
||||
from .model_paths import public_provider_url
|
||||
@@ -9,6 +10,19 @@ from .model_paths import public_provider_url
|
||||
if TYPE_CHECKING:
|
||||
from ..core.db import UpstreamProviderRow
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
_UNKNOWN_SUB_PROVIDER = "unknown"
|
||||
|
||||
|
||||
def _carries_usage(payload: dict) -> bool:
|
||||
"""Whether a payload holds usage, at top level or in the Anthropic
|
||||
``message`` / Responses ``response`` envelope."""
|
||||
return any(
|
||||
isinstance(obj, dict) and isinstance(obj.get("usage"), dict)
|
||||
for obj in (payload, payload.get("message"), payload.get("response"))
|
||||
)
|
||||
|
||||
|
||||
class OpenRouterUpstreamProvider(BaseUpstreamProvider):
|
||||
"""Upstream provider specifically configured for OpenRouter API."""
|
||||
@@ -27,7 +41,8 @@ class OpenRouterUpstreamProvider(BaseUpstreamProvider):
|
||||
|
||||
- Real upstream sub-provider (e.g. ``"GMICloud"``) -> ``"openrouter:GMICloud"``.
|
||||
- Missing sub-provider, or one that merely echoes ``"openrouter"`` ->
|
||||
``"unknown"``.
|
||||
``"openrouter:unknown"``: the router is still known even when the
|
||||
serving provider is not (e.g. the Responses API never reports it).
|
||||
- Idempotent: re-stamping never produces ``"openrouter:openrouter:..."``;
|
||||
the ``openrouter:`` prefix appears at most once.
|
||||
"""
|
||||
@@ -40,9 +55,23 @@ class OpenRouterUpstreamProvider(BaseUpstreamProvider):
|
||||
prefix = f"{provider_type}:"
|
||||
while sub.lower().startswith(prefix.lower()):
|
||||
sub = sub[len(prefix) :].strip()
|
||||
# Already stamped as unknown on an earlier pass; keep it without
|
||||
# warning again.
|
||||
if sub.lower() == _UNKNOWN_SUB_PROVIDER:
|
||||
response_json["provider"] = f"{provider_type}:{_UNKNOWN_SUB_PROVIDER}"
|
||||
return
|
||||
# No real sub-provider, or it just echoes our own router name.
|
||||
if not sub or sub.lower() == provider_type.lower():
|
||||
response_json["provider"] = "unknown"
|
||||
# Warn only on the billed payload, not on every stream chunk.
|
||||
if _carries_usage(response_json):
|
||||
logger.warning(
|
||||
"OpenRouter did not report the serving provider",
|
||||
extra={
|
||||
"model": response_json.get("model"),
|
||||
"response_id": response_json.get("id"),
|
||||
},
|
||||
)
|
||||
response_json["provider"] = f"{provider_type}:{_UNKNOWN_SUB_PROVIDER}"
|
||||
return
|
||||
response_json["provider"] = f"{provider_type}:{sub}"
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from routstr.upstream.anthropic import AnthropicUpstreamProvider
|
||||
from routstr.upstream.base import BaseUpstreamProvider
|
||||
from routstr.upstream.generic import GenericUpstreamProvider
|
||||
@@ -33,12 +35,12 @@ def test_apply_provider_field_openrouter_passthrough() -> None:
|
||||
|
||||
|
||||
def test_apply_provider_field_openrouter_no_upstream_provider() -> None:
|
||||
"""If OpenRouter omits the provider field, the real serving provider is
|
||||
unknown — a bare ``openrouter`` value carries no information."""
|
||||
"""If OpenRouter omits the provider field, the serving provider is
|
||||
unknown but the router is not."""
|
||||
p = _make_provider(OpenRouterUpstreamProvider, "openrouter")
|
||||
data: dict = {"id": "gen-abc"}
|
||||
p._apply_provider_field(data)
|
||||
assert data["provider"] == "unknown"
|
||||
assert data["provider"] == "openrouter:unknown"
|
||||
|
||||
|
||||
def test_apply_provider_field_openrouter_echoes_router_name() -> None:
|
||||
@@ -46,7 +48,35 @@ def test_apply_provider_field_openrouter_echoes_router_name() -> None:
|
||||
p = _make_provider(OpenRouterUpstreamProvider, "openrouter")
|
||||
data: dict = {"provider": "openrouter"}
|
||||
p._apply_provider_field(data)
|
||||
assert data["provider"] == "unknown"
|
||||
assert data["provider"] == "openrouter:unknown"
|
||||
|
||||
|
||||
def test_apply_provider_field_openrouter_unknown_is_idempotent() -> None:
|
||||
"""Re-stamping an unknown payload (e.g. in inject_cost_metadata) keeps
|
||||
``openrouter:unknown`` instead of reading ``unknown`` as a sub-provider."""
|
||||
p = _make_provider(OpenRouterUpstreamProvider, "openrouter")
|
||||
data: dict = {"id": "gen-abc"}
|
||||
p._apply_provider_field(data)
|
||||
p._apply_provider_field(data)
|
||||
assert data["provider"] == "openrouter:unknown"
|
||||
|
||||
|
||||
def test_apply_provider_field_openrouter_warns_once_on_billed_payload() -> None:
|
||||
"""A missing provider is logged on the payload carrying usage, not on
|
||||
every stream chunk or on a re-stamp."""
|
||||
p = _make_provider(OpenRouterUpstreamProvider, "openrouter")
|
||||
chunk: dict = {"type": "response.output_text.delta", "delta": "hi"}
|
||||
completed: dict = {
|
||||
"type": "response.completed",
|
||||
"response": {"id": "gen-abc", "usage": {"input_tokens": 1}},
|
||||
}
|
||||
with patch("routstr.upstream.openrouter.logger.warning") as warning:
|
||||
p._apply_provider_field(chunk)
|
||||
p._apply_provider_field(completed)
|
||||
p._apply_provider_field(completed)
|
||||
|
||||
warning.assert_called_once()
|
||||
assert chunk["provider"] == completed["provider"] == "openrouter:unknown"
|
||||
|
||||
|
||||
def test_apply_provider_field_openrouter_idempotent_no_double_prefix() -> None:
|
||||
@@ -79,14 +109,14 @@ def test_apply_provider_field_blank_upstream_treated_as_missing() -> None:
|
||||
p = _make_provider(OpenRouterUpstreamProvider, "openrouter")
|
||||
data: dict = {"provider": " "}
|
||||
p._apply_provider_field(data)
|
||||
assert data["provider"] == "unknown"
|
||||
assert data["provider"] == "openrouter:unknown"
|
||||
|
||||
|
||||
def test_apply_provider_field_non_string_upstream_treated_as_missing() -> None:
|
||||
p = _make_provider(OpenRouterUpstreamProvider, "openrouter")
|
||||
data: dict = {"provider": 42}
|
||||
p._apply_provider_field(data)
|
||||
assert data["provider"] == "unknown"
|
||||
assert data["provider"] == "openrouter:unknown"
|
||||
|
||||
|
||||
def test_apply_provider_field_openrouter_reads_nested_envelopes() -> None:
|
||||
|
||||
Reference in New Issue
Block a user