Build a Model Catalog Drift Monitor for Chinese AI APIs
Chinese AI APIs are moving quickly enough that a static SDK configuration can become stale before the next sprint planning meeting. Model names change, cache billing fields appear, context windows expand, output limits move, and pricing notices can arrive before finance has updated the spreadsheet.
That does not mean every application needs a complex provider abstraction. It means production teams need a small control loop that treats model catalogs as live operational data. If your SaaS product calls DeepSeek, Qwen, GLM, Kimi, or an aggregator such as AIWave, the question is not only "does the request succeed?" The better question is "does the model contract we ship today still match the provider facts we checked today?"
This article walks through a practical model catalog drift monitor for OpenAI-compatible Chinese AI APIs. The goal is to catch changes before they become incidents: a model version changes, cache pricing moves, the output limit is smaller than your summarizer expects, or a provider adds a peak-hour rule that your cost estimates ignore.
I checked the official source pages on August 13, 2026. DeepSeek's pricing page currently lists deepseek-v4-flash and deepseek-v4-pro with 1M context and includes an announced peak/off-peak pricing update for August 16, 2026. Kimi's K3 page lists a 1,048,576-token context window and separate cache-hit, cache-miss, and output rates. Z.AI publishes USD pricing for GLM-5.2 and GLM-5.1, including cached input. QwenCloud's model marketplace lists per-model details for Qwen3.7 Max, Qwen3.7 Flash, and Qwen3 open-source snapshots. AIWave exposes an OpenAI-compatible model list endpoint for applications that want one place to discover available Chinese models.
The monitor below does not scrape credentials, does not store prompts, and does not need real user traffic. It stores only provider metadata.
Why catalog drift matters
Most AI incidents are not dramatic provider outages. They are boring mismatches.
A coding agent sends 90K tokens to a model that used to support the request shape, but the configured alias now points somewhere else. A billing forecast assumes one output rate, while the provider has split pricing by cache hit and cache miss. A procurement review compares list prices from last month and misses a dated pricing notice. An engineering team deploys a fallback chain but never checks whether the fallback has function calling, structured outputs, or enough context.
These problems are preventable if you promote model metadata to a first-class artifact.
At minimum, track:
| Field | Why it matters | Example source checked on August 13, 2026 |
|---|---|---|
| Model ID | Request routing and SDK config depend on exact identifiers. |
deepseek-v4-pro, qwen3.7-max, glm-5.2, kimi-k3
|
| Model version | Version changes can affect evaluations and prompt behavior. | DeepSeek lists V4 model versions on its pricing page. |
| Context window | Long-context agents fail or truncate silently when assumptions drift. | Kimi K3 and Qwen3.7 pages list 1M context. |
| Output limit | Summarizers, code generators, and report writers need realistic caps. | QwenCloud lists max output per model page. |
| Cache input rate | Repeated context cost depends on cache treatment. | DeepSeek, Kimi, Z.AI, and QwenCloud expose cache-related fields. |
| Output rate | Agent cost is often dominated by generated tokens. | Each provider lists separate output pricing. |
| Rate limits | Production concurrency should reflect documented RPM and TPM. | QwenCloud pages include model-level rate limits. |
| Upcoming notices | Future changes should create tickets before the effective date. | DeepSeek announces a price schedule change for August 16, 2026. |
The table is intentionally operational. It is not a market comparison for a landing page. It is an input to CI, release review, and finance reconciliation.
Normalize the fields first
Every provider describes its catalog differently. Some publish one pricing table, some expose model pages, and aggregators usually expose an API endpoint. Normalize those sources into a small schema before you compare anything.
from dataclasses import dataclass, asdict
from decimal import Decimal
from typing import Optional
@dataclass(frozen=True)
class ModelCatalogRow:
provider: str
model: str
source_url: str
checked_date: str
input_per_mtok: Optional[Decimal] = None
cached_input_per_mtok: Optional[Decimal] = None
cache_write_per_mtok: Optional[Decimal] = None
output_per_mtok: Optional[Decimal] = None
context_tokens: Optional[int] = None
max_output_tokens: Optional[int] = None
rpm: Optional[int] = None
tpm: Optional[int] = None
pricing_note: str = ""
def serialize(row: ModelCatalogRow) -> dict:
data = asdict(row)
for key, value in data.items():
if isinstance(value, Decimal):
data[key] = str(value)
return data
Use Decimal for prices. Float math is tolerable for dashboards, but it is a poor default for billing controls. Also store the source URL and the date you checked it. A price without a date is not an operational fact; it is a rumor waiting to become a stale assumption.
Here is a hand-maintained seed file based on the official pages checked today. In production, you can move the collection step behind browser automation, provider APIs, or a manual approval queue. The drift logic stays the same.
from decimal import Decimal
CHECKED_DATE = "2026-08-13"
CATALOG = [
ModelCatalogRow(
provider="DeepSeek",
model="deepseek-v4-flash",
source_url="https://api-docs.deepseek.com/quick_start/pricing/",
checked_date=CHECKED_DATE,
input_per_mtok=Decimal("0.14"),
cached_input_per_mtok=Decimal("0.0028"),
output_per_mtok=Decimal("0.28"),
context_tokens=1_000_000,
max_output_tokens=384_000,
pricing_note="Provider page announces new peak/off-peak rates effective 2026-08-16 16:00 UTC.",
),
ModelCatalogRow(
provider="DeepSeek",
model="deepseek-v4-pro",
source_url="https://api-docs.deepseek.com/quick_start/pricing/",
checked_date=CHECKED_DATE,
input_per_mtok=Decimal("0.435"),
cached_input_per_mtok=Decimal("0.003625"),
output_per_mtok=Decimal("0.87"),
context_tokens=1_000_000,
max_output_tokens=384_000,
pricing_note="Provider page announces new peak/off-peak rates effective 2026-08-16 16:00 UTC.",
),
ModelCatalogRow(
provider="Kimi",
model="kimi-k3",
source_url="https://www.kimi.com/resources/kimi-k3-pricing",
checked_date=CHECKED_DATE,
input_per_mtok=Decimal("3.00"),
cached_input_per_mtok=Decimal("0.30"),
output_per_mtok=Decimal("15.00"),
context_tokens=1_048_576,
),
ModelCatalogRow(
provider="Z.AI",
model="glm-5.2",
source_url="https://docs.z.ai/guides/overview/pricing",
checked_date=CHECKED_DATE,
input_per_mtok=Decimal("1.40"),
cached_input_per_mtok=Decimal("0.26"),
output_per_mtok=Decimal("4.40"),
),
ModelCatalogRow(
provider="QwenCloud",
model="qwen3.7-max",
source_url="https://www.qwencloud.com/models/qwen3.7-max",
checked_date=CHECKED_DATE,
input_per_mtok=Decimal("1.25"),
cached_input_per_mtok=Decimal("0.25"),
cache_write_per_mtok=Decimal("1.5625"),
output_per_mtok=Decimal("3.75"),
context_tokens=1_000_000,
max_output_tokens=131_000,
rpm=600,
tpm=1_000_000,
),
ModelCatalogRow(
provider="QwenCloud",
model="qwen3.7-flash",
source_url="https://www.qwencloud.com/models/qwen3.7-flash",
checked_date=CHECKED_DATE,
input_per_mtok=Decimal("0.03"),
cached_input_per_mtok=Decimal("0.006"),
cache_write_per_mtok=Decimal("0.038"),
output_per_mtok=Decimal("0.13"),
context_tokens=1_000_000,
max_output_tokens=131_000,
rpm=15_000,
tpm=5_000_000,
),
]
Notice the monitor captures both provider-specific nuance and normalized values. QwenCloud separates implicit cache reads and explicit cache creation. DeepSeek has a dated future pricing notice. Kimi K3 has a large output price compared with its cache-hit input rate. Z.AI publishes cached input rates for GLM. Those details should not be flattened into a single "price" column.
Compare snapshots, not vibes
Once you have yesterday's snapshot and today's snapshot, drift detection is straightforward. Compare by provider and model, then emit changes that matter to engineering, finance, and product.
import json
from pathlib import Path
WATCH_FIELDS = [
"input_per_mtok",
"cached_input_per_mtok",
"cache_write_per_mtok",
"output_per_mtok",
"context_tokens",
"max_output_tokens",
"rpm",
"tpm",
"pricing_note",
]
def load_snapshot(path: Path) -> dict[tuple[str, str], dict]:
if not path.exists():
return {}
rows = json.loads(path.read_text(encoding="utf-8"))
return {(row["provider"], row["model"]): row for row in rows}
def diff_snapshots(previous: dict, current: dict) -> list[dict]:
events = []
all_keys = sorted(set(previous) | set(current))
for key in all_keys:
before = previous.get(key)
after = current.get(key)
provider, model = key
if before is None:
events.append({"severity": "info", "provider": provider, "model": model, "change": "model_added"})
continue
if after is None:
events.append({"severity": "warning", "provider": provider, "model": model, "change": "model_removed"})
continue
for field in WATCH_FIELDS:
if before.get(field) != after.get(field):
severity = "warning" if field.endswith("_per_mtok") or field in {"context_tokens", "max_output_tokens"} else "info"
events.append({
"severity": severity,
"provider": provider,
"model": model,
"change": field,
"before": before.get(field),
"after": after.get(field),
"source_url": after.get("source_url"),
"checked_date": after.get("checked_date"),
})
return events
def write_snapshot(path: Path, rows: list[ModelCatalogRow]) -> None:
payload = [serialize(row) for row in rows]
path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
The key design choice is severity. A model added to the marketplace is useful information. A model removed from a configured route is a release blocker. A context window reduction can break user workflows. A cache price change can distort gross margin. A dated pricing notice should create a finance and routing review task even before the number changes.
Add policy checks for your own stack
Snapshot diffs tell you what changed. Policy checks tell you whether your application can still operate within its own requirements.
For example, suppose a Tier 1 SaaS team uses long-context coding agents and requires:
- at least 128K input context for codebase analysis,
- structured output support for automation,
- a published source URL for every model,
- no hard-coded credentials in example configs,
- pricing checked within the last seven days.
Represent that as code. Keep it small enough that an on-call engineer can read it at 2 a.m.
import datetime as dt
def validate_policy(rows: list[ModelCatalogRow], today: str) -> list[str]:
issues = []
today_date = dt.date.fromisoformat(today)
for row in rows:
age = (today_date - dt.date.fromisoformat(row.checked_date)).days
if age > 7:
issues.append(f"{row.provider}/{row.model}: source check is {age} days old")
if not row.source_url.startswith("https://"):
issues.append(f"{row.provider}/{row.model}: source URL is missing or not HTTPS")
if row.context_tokens is not None and row.context_tokens < 128_000:
issues.append(f"{row.provider}/{row.model}: context below 128K")
if row.output_per_mtok is None:
issues.append(f"{row.provider}/{row.model}: output price missing")
if row.cached_input_per_mtok is None and row.context_tokens and row.context_tokens >= 500_000:
issues.append(f"{row.provider}/{row.model}: long-context model has no cached input field")
return issues
Run this as part of a daily job and again before changing model routes. If it fails, do not silently update the SDK. Open a review. The point is not to block every change; the point is to make invisible drift visible.
Where AIWave fits
If you use a direct provider integration, your monitor should read each provider's public docs or marketplace pages. If you use AIWave, you can also check AIWave's OpenAI-compatible model list endpoint and compare it with the provider facts you care about.
The useful pattern is two layers:
- Provider fact layer: official model IDs, pricing, context, output limits, cache fields, and notices.
- Application route layer: the models your product actually exposes, their aliases, fallback order, and usage policy.
AIWave can simplify the route layer because your application can keep one OpenAI-compatible client, one USD billing relationship, and one set of operational policies while still switching among 25+ Chinese models. That does not remove the need for validation. It makes validation easier to centralize.
Here is a minimal route check against an OpenAI-compatible model list. Use your own base URL and keep the key in the environment.
import os
import requests
def fetch_openai_compatible_models(base_url: str) -> set[str]:
api_key = os.environ.get("AIWAVE_API_KEY")
if not api_key:
raise RuntimeError("AIWAVE_API_KEY is required")
response = requests.get(
f"{base_url.rstrip('/')}/v1/models",
headers={"Authorization": f"Bearer {api_key}"},
timeout=20,
)
response.raise_for_status()
payload = response.json()
return {item["id"] for item in payload.get("data", []) if "id" in item}
def check_required_routes(available: set[str], required: set[str]) -> list[str]:
return sorted(required - available)
This is intentionally separate from price collection. A production gateway can expose a model while a pricing page has changed; or a provider page can add a model before your gateway makes it available. You need both facts.
Turn drift into actions
A good drift monitor produces boring, specific tickets:
- "DeepSeek V4 pricing notice found; update forecasts before 2026-08-16 16:00 UTC."
- "Qwen3.7 Flash output rate changed; rerun coding-agent cost tests."
- "Kimi K3 output cost changed; review long-context report generation budget."
- "GLM-5.2 cached input field changed; update cache-hit assumptions."
- "Required AIWave route missing; block release until fallback config is reviewed."
The ticket should include the source URL, checked date, old value, new value, affected internal route, and owner. Avoid generic alerts like "AI pricing changed." They create work without creating clarity.
For finance, keep a compact CSV export. For engineering, keep a JSON snapshot in version control or object storage. For product, summarize changes in release review when they affect user-facing capabilities.
Production checklist
Before you trust the monitor, run it through the same discipline as any operational tool:
- Store only metadata, never prompts or customer payloads.
- Keep API keys in environment variables or secret storage.
- Save source URLs and checked dates with every row.
- Treat dated provider notices as first-class events.
- Separate provider facts from your own routing policy.
- Use
Decimalfor price fields. - Fail CI only for changes that can break cost, reliability, or contract assumptions.
- Keep a human approval step for provider docs that require interpretation.
The engineering work is small. The habit is the hard part. Model catalogs are now part of production configuration. Teams that track them explicitly will move faster because every route, fallback, and cost estimate starts from current facts instead of stale notes.
Chinese AI model APIs are valuable precisely because the ecosystem is active. New versions, bigger contexts, cache rules, and pricing updates are normal. A model catalog drift monitor lets you benefit from that pace without letting it surprise your SDK, your users, or your invoice review.
Top comments (1)
Model catalog drift is a practical production issue. Teams often pin prompts but forget that the available model names, limits, regions, and defaults can move underneath them. A monitor is most useful when it turns drift into an actionable migration note, not just an alert.