Files
2026-07-13 13:12:33 +08:00

321 lines
12 KiB
Python

"""Provider-scoped live catalog ingest (provider/live_catalog.py).
Covers the TokenRhythm listing parser, the catalog's scoped live layer
(authority order and cross-provider isolation), and the registry-driven
warm helper the gateway boot calls. Everything is offline: HTTP is patched,
payloads are synthetic mirrors of the platform shape.
"""
from __future__ import annotations
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from opensquilla.provider.live_catalog import (
_TOKENRHYTHM_CNY_PER_USD,
fetch_live_catalog_entries,
parse_tokenrhythm_models,
warm_live_provider_catalogs,
)
from opensquilla.provider.model_catalog import ModelCatalog
def _tokenrhythm_row(**overrides: Any) -> dict[str, Any]:
row: dict[str, Any] = {
"id": "deepseek-v4-pro",
"name": "DeepSeek V4 Pro",
"type": "chat",
"status": "online",
"contextWindow": 900_000,
"maxOutputTokens": 300_000,
"capabilities": {"tools": True, "vision": False},
"billingUnit": 1_000_000,
"currency": "CNY",
"inputPrice": "12",
"outputPrice": "24",
"cacheReadPrice": "1",
}
row.update(overrides)
return row
def test_parse_tokenrhythm_models_maps_published_fields() -> None:
entries = parse_tokenrhythm_models({"code": 0, "data": [_tokenrhythm_row()]})
fields = entries["deepseek-v4-pro"]
assert fields["context_window"] == 900_000
assert fields["max_output_tokens"] == 300_000
assert fields["display_name"] == "DeepSeek V4 Pro"
assert fields["supports_tools"] is True
assert fields["supports_vision"] is False
# CNY per billingUnit tokens → USD per Mtok at the documented ~6.975
# conversion (matches the packaged corrections rows).
assert fields["input_cost_per_mtok"] == pytest.approx(12 / _TOKENRHYTHM_CNY_PER_USD, abs=1e-4)
assert fields["output_cost_per_mtok"] == pytest.approx(24 / _TOKENRHYTHM_CNY_PER_USD, abs=1e-4)
assert fields["cache_read_cost_per_mtok"] == pytest.approx(
1 / _TOKENRHYTHM_CNY_PER_USD, abs=1e-4
)
# The listing has no reasoning knowledge — the corrections ladder keeps
# owning supports_reasoning/reasoning_format, so the parser must never
# emit either field.
assert "supports_reasoning" not in fields
assert "reasoning_format" not in fields
def test_parse_tokenrhythm_models_skips_offline_and_malformed_rows() -> None:
payload = {
"code": 0,
"data": [
_tokenrhythm_row(),
_tokenrhythm_row(id="glm-5", status="offline"),
_tokenrhythm_row(id=""),
"not-a-model-row",
{"name": "row with no id"},
],
}
assert set(parse_tokenrhythm_models(payload)) == {"deepseek-v4-pro"}
assert parse_tokenrhythm_models({"code": 0, "data": "junk"}) == {}
assert parse_tokenrhythm_models({}) == {}
def test_parse_tokenrhythm_models_coerces_string_and_float_budget_fields() -> None:
# The platform demonstrably serves numbers loosely (prices arrive as
# strings); windows/outputs must survive the same shape drift instead
# of silently zeroing the exact fields this module exists to track.
entries = parse_tokenrhythm_models(
{
"code": 0,
"data": [_tokenrhythm_row(contextWindow="1000000", maxOutputTokens=384000.0)],
}
)
fields = entries["deepseek-v4-pro"]
assert fields["context_window"] == 1_000_000
assert fields["max_output_tokens"] == 384_000
def test_parse_tokenrhythm_models_halves_near_window_output_caps() -> None:
# A published output cap at/near the whole window (input and output
# share it) would trip resolve_max_tokens' request-safety clamp down to
# 8192; the parser halves it to the engine's output-reserve ceiling.
entries = parse_tokenrhythm_models(
{
"code": 0,
"data": [
_tokenrhythm_row(id="minimax-m2.5", contextWindow=200_000,
maxOutputTokens=200_000),
_tokenrhythm_row(id="minimax-m2.7", contextWindow=200_000,
maxOutputTokens=192_000),
],
}
)
assert entries["minimax-m2.5"]["max_output_tokens"] == 100_000
assert entries["minimax-m2.7"]["max_output_tokens"] == 100_000
catalog = ModelCatalog()
catalog.set_live_provider_entries("tokenrhythm", entries)
# The resolved value survives resolve_max_tokens un-clamped — the whole
# point of halving at ingest time.
assert catalog.resolve_max_tokens("minimax-m2.5", provider="tokenrhythm") == 100_000
def test_refreshed_corrections_rows_do_not_trip_the_near_window_clamp() -> None:
# Offline fallback parity for the same hazard: every packaged
# tokenrhythm row must resolve max_tokens above the 8192 safe-default
# collapse the clamp would apply to a near-window output cap.
catalog = ModelCatalog()
for model, expected in (
("minimax-m2.5", 100_000),
("minimax-m2.7", 100_000),
("mimo-v2.5-pro", 128_000),
):
assert catalog.resolve_max_tokens(model, provider="tokenrhythm") == expected
def test_parse_tokenrhythm_models_unknown_currency_emits_no_costs() -> None:
entries = parse_tokenrhythm_models(
{"code": 0, "data": [_tokenrhythm_row(currency="USD")]}
)
fields = entries["deepseek-v4-pro"]
assert "input_cost_per_mtok" not in fields
assert "output_cost_per_mtok" not in fields
assert fields["context_window"] == 900_000
def test_scoped_live_entries_outrank_corrections_without_leaking() -> None:
catalog = ModelCatalog()
catalog.set_live_provider_entries(
"tokenrhythm",
parse_tokenrhythm_models({"code": 0, "data": [_tokenrhythm_row()]}),
)
# Scoped live beats the packaged corrections row (1M/384k) for the
# ingested provider…
assert catalog.resolve_context_window_with_source(
"deepseek-v4-pro", provider="tokenrhythm"
) == (900_000, "catalog")
assert catalog.resolve_max_tokens("deepseek-v4-pro", provider="tokenrhythm") == 300_000
assert catalog.resolve_entry("deepseek-v4-pro", provider="tokenrhythm").source == "live"
# …while the same bare id on other providers (and provider-less
# lookups) never sees the relay's rows.
assert catalog.resolve_context_window("deepseek-v4-pro", "deepseek") == 1_000_000
assert catalog.resolve_max_tokens("deepseek-v4-pro", provider="deepseek") == 384_000
assert catalog.resolve_context_window("deepseek-v4-pro") == 1_000_000
def test_scoped_live_keeps_corrections_reasoning_dialect() -> None:
# The relay rejects thinking-toggle payloads, so its corrections rows
# pin reasoning_format="none". Live ingest claims no reasoning fields;
# capabilities must stay exactly as the ladder decided them.
catalog = ModelCatalog()
catalog.set_live_provider_entries(
"tokenrhythm",
parse_tokenrhythm_models({"code": 0, "data": [_tokenrhythm_row()]}),
)
caps = catalog.get_capabilities("deepseek-v4-pro", provider_name="tokenrhythm")
assert caps.supports_reasoning is False
assert caps.reasoning_format == "none"
assert caps.supports_tools is True
def test_user_overrides_still_beat_scoped_live() -> None:
catalog = ModelCatalog()
catalog.set_live_provider_entries(
"tokenrhythm", {"deepseek-v4-pro": {"context_window": 900_000}}
)
catalog.set_user_overrides(
{"tokenrhythm/deepseek-v4-pro": {"context_window": 555_000}}
)
assert catalog.resolve_context_window_with_source(
"deepseek-v4-pro", provider="tokenrhythm"
) == (555_000, "override")
def test_set_live_provider_entries_drops_bad_fields_and_replaces_table() -> None:
catalog = ModelCatalog()
catalog.set_live_provider_entries(
"tokenrhythm",
{
"deepseek-v4-pro": {
"context_window": 900_000,
"not_a_field": 1,
"max_output_tokens": "mistyped",
},
"all-fields-invalid": {"bogus": True},
},
)
# Valid fields survive, invalid ones degrade like packaged corrections.
assert catalog.resolve_context_window("deepseek-v4-pro", "tokenrhythm") == 900_000
# max_output_tokens was dropped → the corrections row still supplies it.
assert catalog.resolve_max_tokens("deepseek-v4-pro", provider="tokenrhythm") == 384_000
# A re-warm replaces the whole provider table — stale rows cannot linger.
catalog.set_live_provider_entries(
"tokenrhythm", {"glm-5": {"context_window": 777_000}}
)
assert catalog.resolve_context_window("glm-5", "tokenrhythm") == 777_000
assert catalog.resolve_context_window("deepseek-v4-pro", "tokenrhythm") == 1_000_000
async def test_fetch_live_catalog_entries_uses_url_verbatim_without_auth() -> None:
captured: dict[str, Any] = {}
mock_response = MagicMock()
mock_response.raise_for_status = MagicMock()
mock_response.json.return_value = {"code": 0, "data": [_tokenrhythm_row()]}
with patch("opensquilla.provider.live_catalog.httpx.AsyncClient") as mock_client_cls:
mock_client = AsyncMock()
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
mock_client.__aexit__ = AsyncMock(return_value=False)
async def capture_get(url: str, **kwargs: Any) -> Any:
captured["url"] = url
captured["kwargs"] = kwargs
return mock_response
mock_client.get = AsyncMock(side_effect=capture_get)
mock_client_cls.return_value = mock_client
entries = await fetch_live_catalog_entries(
"https://tokenrhythm.studio/api/models", "tokenrhythm"
)
assert captured["url"] == "https://tokenrhythm.studio/api/models"
# The listing stays keyless while carrying fixed public app attribution.
headers = captured["kwargs"]["headers"]
assert headers == {
"HTTP-Referer": "https://opensquilla.ai",
"X-Title": "OpenSquilla",
}
assert "Authorization" not in headers
assert entries["deepseek-v4-pro"]["context_window"] == 900_000
async def test_fetch_live_catalog_entries_rejects_unknown_shape() -> None:
with pytest.raises(ValueError, match="unknown live catalog shape"):
await fetch_live_catalog_entries("https://example.invalid/models", "no-such-shape")
class _RecordingCatalog:
def __init__(self) -> None:
self.calls: list[tuple[str, dict[str, dict[str, Any]]]] = []
def set_live_provider_entries(
self, provider_id: str, entries: dict[str, dict[str, Any]]
) -> None:
self.calls.append((provider_id, entries))
async def test_warm_ingests_only_providers_with_live_catalog_metadata(
monkeypatch: pytest.MonkeyPatch,
) -> None:
fetched: list[tuple[str, str]] = []
async def fake_fetch(url: str, shape: str, *, proxy: str = "", timeout: float = 5.0) -> dict:
fetched.append((url, shape))
return {"deepseek-v4-pro": {"context_window": 900_000}}
monkeypatch.setattr(
"opensquilla.provider.live_catalog.fetch_live_catalog_entries", fake_fetch
)
catalog = _RecordingCatalog()
counts = await warm_live_provider_catalogs(
catalog,
# duplicates, casing, blanks, unknown ids, and providers without
# live-catalog metadata are all skipped without error
["tokenrhythm", "TokenRhythm", "", "openai", "no-such-provider"],
)
assert counts == {"tokenrhythm": 1}
assert fetched == [("https://tokenrhythm.studio/api/models", "tokenrhythm")]
assert catalog.calls == [
("tokenrhythm", {"deepseek-v4-pro": {"context_window": 900_000}})
]
async def test_warm_degrades_per_provider_on_fetch_failure(
monkeypatch: pytest.MonkeyPatch,
) -> None:
async def failing_fetch(*args: Any, **kwargs: Any) -> dict:
raise OSError("network unreachable")
monkeypatch.setattr(
"opensquilla.provider.live_catalog.fetch_live_catalog_entries", failing_fetch
)
catalog = _RecordingCatalog()
counts = await warm_live_provider_catalogs(catalog, ["tokenrhythm"])
assert counts == {}
assert catalog.calls == []