fix: preserve PPQ model identity across catalog aliases

This commit is contained in:
9qeklajc
2026-10-04 01:21:11 +02:00
parent 0302580aa0
commit d821863795
2 changed files with 74 additions and 6 deletions
+7 -2
View File
@@ -221,6 +221,7 @@ class PPQAIUpstreamProvider(BaseUpstreamProvider):
]
models = []
seen_ids: set[str] = set()
for model_data in models_data:
try:
ppqai_model = PPQAIModel.parse_obj(model_data)
@@ -259,13 +260,16 @@ class PPQAIUpstreamProvider(BaseUpstreamProvider):
None,
)
model_id = or_model.id if or_model else ppqai_model.id
if model_id.lower() in seen_ids:
continue
if or_model:
# OpenRouter supplies metadata, not PPQ billing rates.
# Keep the stored model identity, but not foreign billing rates.
models.append(
or_model.copy(
deep=True,
update={
"id": ppqai_model.id,
"pricing": pricing,
"sats_pricing": None,
"context_length": ppqai_model.context_length
@@ -291,6 +295,7 @@ class PPQAIUpstreamProvider(BaseUpstreamProvider):
pricing=pricing,
)
)
seen_ids.add(model_id.lower())
except Exception as e:
logger.warning(
"Failed to parse PPQ.AI model",
+67 -4
View File
@@ -4,6 +4,8 @@ from unittest.mock import AsyncMock, patch
import httpx
import pytest
from routstr.algorithm import create_model_mappings
from routstr.core.db import ModelRow
from routstr.payment.models import Architecture, Model, Pricing
from routstr.upstream.ppqai import PPQAIUpstreamProvider
from routstr.upstream.venice import VeniceUpstreamProvider
@@ -75,16 +77,77 @@ async def test_ppq_does_not_inherit_other_provider_cache_or_request_rates() -> N
@pytest.mark.asyncio
async def test_ppq_alias_matches_do_not_share_mutated_prices() -> None:
async def test_ppq_alias_matches_emit_first_stable_id_once() -> None:
models = await _fetch(
[
_entry("vendor/model", {"api": {"input_per_1M": 4, "output_per_1M": 8}}),
_entry("model", {"api": {"input_per_1M": 6, "output_per_1M": 9}}),
]
)
assert [m.id for m in models] == ["vendor/model", "model"]
assert [m.pricing.prompt for m in models] == [4e-6, 6e-6]
assert models[0] is not models[1]
assert [m.id for m in models] == ["vendor/model"]
assert models[0].pricing.prompt == 4e-6
@pytest.mark.asyncio
async def test_ppq_suffix_match_keeps_disabled_model_unroutable() -> None:
metadata = _model().copy(
update={"id": "openai/gpt-4o", "canonical_slug": "openai/gpt-4o"}
)
discovered = await _fetch(
[_entry("gpt-4o", {"api": {"input_per_1M": 4, "output_per_1M": 8}})],
[metadata.dict()],
)
provider = PPQAIUpstreamProvider("test-only")
provider.db_id = 7
with patch.object(provider, "get_cached_models", return_value=discovered):
_, provider_map, unique_models = create_model_mappings(
[provider], {}, {("openai/gpt-4o", 7)}
)
assert provider_map == {}
assert unique_models == {}
assert discovered[0].id == metadata.id
assert provider.transform_model_name(discovered[0].id) == metadata.id
@pytest.mark.asyncio
async def test_ppq_suffix_match_keeps_override_as_only_candidate() -> None:
metadata = _model().copy(
update={"id": "openai/gpt-4o", "canonical_slug": "openai/gpt-4o"}
)
discovered = await _fetch(
[_entry("gpt-4o", {"api": {"input_per_1M": 4, "output_per_1M": 8}})],
[metadata.dict()],
)
provider = PPQAIUpstreamProvider("test-only")
provider.db_id = 7
override = ModelRow(
id=metadata.id,
name=metadata.name,
created=0,
description="",
context_length=8192,
architecture=metadata.architecture.json(),
pricing=Pricing(prompt=9e-6, completion=18e-6).json(),
enabled=True,
upstream_provider_id=7,
forwarded_model_id="operator-alias",
)
with (
patch.object(provider, "get_cached_models", return_value=discovered),
patch("routstr.payment.models.sats_usd_price", return_value=0.001),
):
_, provider_map, _ = create_model_mappings(
[provider], {(metadata.id, 7): (override, 1.0)}, set()
)
assert metadata.id in provider_map
assert "operator-alias" in provider_map
for candidates in provider_map.values():
assert len(candidates) == 1
candidate, serving = candidates[0]
assert serving is provider
assert candidate.id == metadata.id
assert candidate.forwarded_model_id == "operator-alias"
assert candidate.pricing.prompt == 9e-6
@pytest.mark.asyncio