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 = [] models = []
seen_ids: set[str] = set()
for model_data in models_data: for model_data in models_data:
try: try:
ppqai_model = PPQAIModel.parse_obj(model_data) ppqai_model = PPQAIModel.parse_obj(model_data)
@@ -259,13 +260,16 @@ class PPQAIUpstreamProvider(BaseUpstreamProvider):
None, None,
) )
model_id = or_model.id if or_model else ppqai_model.id
if model_id.lower() in seen_ids:
continue
if or_model: if or_model:
# OpenRouter supplies metadata, not PPQ billing rates. # Keep the stored model identity, but not foreign billing rates.
models.append( models.append(
or_model.copy( or_model.copy(
deep=True, deep=True,
update={ update={
"id": ppqai_model.id,
"pricing": pricing, "pricing": pricing,
"sats_pricing": None, "sats_pricing": None,
"context_length": ppqai_model.context_length "context_length": ppqai_model.context_length
@@ -291,6 +295,7 @@ class PPQAIUpstreamProvider(BaseUpstreamProvider):
pricing=pricing, pricing=pricing,
) )
) )
seen_ids.add(model_id.lower())
except Exception as e: except Exception as e:
logger.warning( logger.warning(
"Failed to parse PPQ.AI model", "Failed to parse PPQ.AI model",
+67 -4
View File
@@ -4,6 +4,8 @@ from unittest.mock import AsyncMock, patch
import httpx import httpx
import pytest 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.payment.models import Architecture, Model, Pricing
from routstr.upstream.ppqai import PPQAIUpstreamProvider from routstr.upstream.ppqai import PPQAIUpstreamProvider
from routstr.upstream.venice import VeniceUpstreamProvider 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 @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( models = await _fetch(
[ [
_entry("vendor/model", {"api": {"input_per_1M": 4, "output_per_1M": 8}}), _entry("vendor/model", {"api": {"input_per_1M": 4, "output_per_1M": 8}}),
_entry("model", {"api": {"input_per_1M": 6, "output_per_1M": 9}}), _entry("model", {"api": {"input_per_1M": 6, "output_per_1M": 9}}),
] ]
) )
assert [m.id for m in models] == ["vendor/model", "model"] assert [m.id for m in models] == ["vendor/model"]
assert [m.pricing.prompt for m in models] == [4e-6, 6e-6] assert models[0].pricing.prompt == 4e-6
assert models[0] is not models[1]
@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 @pytest.mark.asyncio