fix(ci): skip variable-priced catalog rows so the schema gate stays green

This commit is contained in:
mateo-berri 2026-08-29 13:27:21 -07:00
parent cc3d07511d
commit f0c6efc695
2 changed files with 44 additions and 3 deletions

View file

@ -69,6 +69,10 @@ def write_to_file(file_path: str, data: dict[str, object]) -> None:
def transform_openrouter_data(data):
transformed = {}
for row in data:
if float(row["pricing"]["prompt"]) < 0 or float(row["pricing"]["completion"]) < 0:
print(f"Skipping openrouter/{row['id']}: the catalog lists no usable per-token price for it.")
continue
# Add the fields 'max_tokens' and 'input_cost_per_token'
obj = {
"max_tokens": row["context_length"],
@ -106,8 +110,10 @@ def transform_openrouter_data(data):
def _vercel_entry(model: VercelModel) -> dict[str, object] | None:
mode: Final = VERCEL_TYPE_TO_MODE[model.type]
pricing: Final = model.pricing if model.pricing is not None else VercelPricing()
if pricing.input is None or (mode == "chat" and pricing.output is None):
print(f"Skipping vercel_ai_gateway/{model.id}: the catalog lists no per-token price for it.")
input_price_unusable: Final = pricing.input is None or pricing.input < 0
output_price_unusable: Final = mode == "chat" and (pricing.output is None or pricing.output < 0)
if input_price_unusable or output_price_unusable:
print(f"Skipping vercel_ai_gateway/{model.id}: the catalog lists no usable per-token price for it.")
return None
candidate: Final = {
"max_tokens": model.max_tokens,

View file

@ -40,11 +40,31 @@ VIDEO_ROW = {
"pricing": {"video_duration_pricing": [{"resolution": "720p", "price_per_second": "0.4"}]},
}
IMAGE_ROW = {"id": "openai/gpt-image-1", "type": "image"}
NEGATIVE_PRICE_LANGUAGE_ROW = {
"id": "openrouter/auto",
"type": "language",
"context_window": 2000000,
"max_tokens": 2000000,
"pricing": {"input": "-1", "output": "-1"},
}
OPENROUTER_ROW = {
"id": "anthropic/claude-sonnet-4.5",
"context_length": 1000000,
"pricing": {"prompt": "0.000003", "completion": "0.000015", "image": "0"},
"top_provider": {"max_completion_tokens": 64000},
"architecture": {"modality": "text+image->text"},
}
OPENROUTER_VARIABLE_PRICE_ROW = {
"id": "openrouter/auto",
"context_length": 2000000,
"pricing": {"prompt": "-1", "completion": "-1"},
"top_provider": {"max_completion_tokens": None},
}
def test_vercel_rows_without_per_token_pricing_are_skipped_instead_of_crashing() -> None:
result = price_sync.transform_vercel_ai_gateway_data(
[LANGUAGE_ROW, EMBEDDING_ROW, UNPRICED_LANGUAGE_ROW, VIDEO_ROW, IMAGE_ROW]
[LANGUAGE_ROW, EMBEDDING_ROW, UNPRICED_LANGUAGE_ROW, VIDEO_ROW, IMAGE_ROW, NEGATIVE_PRICE_LANGUAGE_ROW]
)
assert result == {
@ -71,6 +91,21 @@ def test_vercel_rows_without_per_token_pricing_are_skipped_instead_of_crashing()
}
def test_openrouter_variable_priced_rows_are_skipped_so_the_schema_gate_stays_green() -> None:
result = price_sync.transform_openrouter_data([OPENROUTER_ROW, OPENROUTER_VARIABLE_PRICE_ROW])
assert result == {
"openrouter/anthropic/claude-sonnet-4.5": {
"max_tokens": 1000000,
"max_output_tokens": 64000,
"input_cost_per_token": 3e-06,
"output_cost_per_token": 1.5e-05,
"litellm_provider": "openrouter",
"mode": "chat",
}
}
def test_write_to_file_matches_the_checked_in_cost_map_format(tmp_path: Path) -> None:
target = tmp_path / "model_prices_and_context_window.json"