{"slug": "build-a-model-catalog-drift-monitor-for-chinese-ai-apis", "title": "Build a Model Catalog Drift Monitor for Chinese AI APIs", "summary": "A developer built a model catalog drift monitor for OpenAI-compatible Chinese AI APIs, designed to catch changes in model names, context windows, output limits, and pricing before they cause incidents. The monitor checks official source pages, such as DeepSeek, Kimi, Z.AI, and QwenCloud, and stores only provider metadata, not credentials or user traffic. It aims to prevent 'boring mismatches' like outdated aliases or incorrect billing assumptions.", "body_md": "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.\n\nThat 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?\"\n\nThis 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.\n\nI checked the official source pages on August 13, 2026. DeepSeek's pricing page currently lists `deepseek-v4-flash`\n\nand `deepseek-v4-pro`\n\nwith 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.\n\nThe monitor below does not scrape credentials, does not store prompts, and does not need real user traffic. It stores only provider metadata.\n\nMost AI incidents are not dramatic provider outages. They are boring mismatches.\n\nA 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.\n\nThese problems are preventable if you promote model metadata to a first-class artifact.\n\nAt minimum, track:\n\n| Field | Why it matters | Example source checked on August 13, 2026 |\n|---|---|---|\n| Model ID | Request routing and SDK config depend on exact identifiers. |\n`deepseek-v4-pro` , `qwen3.7-max` , `glm-5.2` , `kimi-k3`\n|\n| Model version | Version changes can affect evaluations and prompt behavior. | DeepSeek lists V4 model versions on its pricing page. |\n| Context window | Long-context agents fail or truncate silently when assumptions drift. | Kimi K3 and Qwen3.7 pages list 1M context. |\n| Output limit | Summarizers, code generators, and report writers need realistic caps. | QwenCloud lists max output per model page. |\n| Cache input rate | Repeated context cost depends on cache treatment. | DeepSeek, Kimi, Z.AI, and QwenCloud expose cache-related fields. |\n| Output rate | Agent cost is often dominated by generated tokens. | Each provider lists separate output pricing. |\n| Rate limits | Production concurrency should reflect documented RPM and TPM. | QwenCloud pages include model-level rate limits. |\n| Upcoming notices | Future changes should create tickets before the effective date. | DeepSeek announces a price schedule change for August 16, 2026. |\n\nThe 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.\n\nEvery 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.\n\n``` python\nfrom dataclasses import dataclass, asdict\nfrom decimal import Decimal\nfrom typing import Optional\n\n@dataclass(frozen=True)\nclass ModelCatalogRow:\n    provider: str\n    model: str\n    source_url: str\n    checked_date: str\n    input_per_mtok: Optional[Decimal] = None\n    cached_input_per_mtok: Optional[Decimal] = None\n    cache_write_per_mtok: Optional[Decimal] = None\n    output_per_mtok: Optional[Decimal] = None\n    context_tokens: Optional[int] = None\n    max_output_tokens: Optional[int] = None\n    rpm: Optional[int] = None\n    tpm: Optional[int] = None\n    pricing_note: str = \"\"\n\ndef serialize(row: ModelCatalogRow) -> dict:\n    data = asdict(row)\n    for key, value in data.items():\n        if isinstance(value, Decimal):\n            data[key] = str(value)\n    return data\n```\n\nUse `Decimal`\n\nfor 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.\n\nHere 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.\n\n``` python\nfrom decimal import Decimal\n\nCHECKED_DATE = \"2026-08-13\"\n\nCATALOG = [\n    ModelCatalogRow(\n        provider=\"DeepSeek\",\n        model=\"deepseek-v4-flash\",\n        source_url=\"https://api-docs.deepseek.com/quick_start/pricing/\",\n        checked_date=CHECKED_DATE,\n        input_per_mtok=Decimal(\"0.14\"),\n        cached_input_per_mtok=Decimal(\"0.0028\"),\n        output_per_mtok=Decimal(\"0.28\"),\n        context_tokens=1_000_000,\n        max_output_tokens=384_000,\n        pricing_note=\"Provider page announces new peak/off-peak rates effective 2026-08-16 16:00 UTC.\",\n    ),\n    ModelCatalogRow(\n        provider=\"DeepSeek\",\n        model=\"deepseek-v4-pro\",\n        source_url=\"https://api-docs.deepseek.com/quick_start/pricing/\",\n        checked_date=CHECKED_DATE,\n        input_per_mtok=Decimal(\"0.435\"),\n        cached_input_per_mtok=Decimal(\"0.003625\"),\n        output_per_mtok=Decimal(\"0.87\"),\n        context_tokens=1_000_000,\n        max_output_tokens=384_000,\n        pricing_note=\"Provider page announces new peak/off-peak rates effective 2026-08-16 16:00 UTC.\",\n    ),\n    ModelCatalogRow(\n        provider=\"Kimi\",\n        model=\"kimi-k3\",\n        source_url=\"https://www.kimi.com/resources/kimi-k3-pricing\",\n        checked_date=CHECKED_DATE,\n        input_per_mtok=Decimal(\"3.00\"),\n        cached_input_per_mtok=Decimal(\"0.30\"),\n        output_per_mtok=Decimal(\"15.00\"),\n        context_tokens=1_048_576,\n    ),\n    ModelCatalogRow(\n        provider=\"Z.AI\",\n        model=\"glm-5.2\",\n        source_url=\"https://docs.z.ai/guides/overview/pricing\",\n        checked_date=CHECKED_DATE,\n        input_per_mtok=Decimal(\"1.40\"),\n        cached_input_per_mtok=Decimal(\"0.26\"),\n        output_per_mtok=Decimal(\"4.40\"),\n    ),\n    ModelCatalogRow(\n        provider=\"QwenCloud\",\n        model=\"qwen3.7-max\",\n        source_url=\"https://www.qwencloud.com/models/qwen3.7-max\",\n        checked_date=CHECKED_DATE,\n        input_per_mtok=Decimal(\"1.25\"),\n        cached_input_per_mtok=Decimal(\"0.25\"),\n        cache_write_per_mtok=Decimal(\"1.5625\"),\n        output_per_mtok=Decimal(\"3.75\"),\n        context_tokens=1_000_000,\n        max_output_tokens=131_000,\n        rpm=600,\n        tpm=1_000_000,\n    ),\n    ModelCatalogRow(\n        provider=\"QwenCloud\",\n        model=\"qwen3.7-flash\",\n        source_url=\"https://www.qwencloud.com/models/qwen3.7-flash\",\n        checked_date=CHECKED_DATE,\n        input_per_mtok=Decimal(\"0.03\"),\n        cached_input_per_mtok=Decimal(\"0.006\"),\n        cache_write_per_mtok=Decimal(\"0.038\"),\n        output_per_mtok=Decimal(\"0.13\"),\n        context_tokens=1_000_000,\n        max_output_tokens=131_000,\n        rpm=15_000,\n        tpm=5_000_000,\n    ),\n]\n```\n\nNotice 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.\n\nOnce 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.\n\n``` python\nimport json\nfrom pathlib import Path\n\nWATCH_FIELDS = [\n    \"input_per_mtok\",\n    \"cached_input_per_mtok\",\n    \"cache_write_per_mtok\",\n    \"output_per_mtok\",\n    \"context_tokens\",\n    \"max_output_tokens\",\n    \"rpm\",\n    \"tpm\",\n    \"pricing_note\",\n]\n\ndef load_snapshot(path: Path) -> dict[tuple[str, str], dict]:\n    if not path.exists():\n        return {}\n    rows = json.loads(path.read_text(encoding=\"utf-8\"))\n    return {(row[\"provider\"], row[\"model\"]): row for row in rows}\n\ndef diff_snapshots(previous: dict, current: dict) -> list[dict]:\n    events = []\n    all_keys = sorted(set(previous) | set(current))\n    for key in all_keys:\n        before = previous.get(key)\n        after = current.get(key)\n        provider, model = key\n\n        if before is None:\n            events.append({\"severity\": \"info\", \"provider\": provider, \"model\": model, \"change\": \"model_added\"})\n            continue\n        if after is None:\n            events.append({\"severity\": \"warning\", \"provider\": provider, \"model\": model, \"change\": \"model_removed\"})\n            continue\n\n        for field in WATCH_FIELDS:\n            if before.get(field) != after.get(field):\n                severity = \"warning\" if field.endswith(\"_per_mtok\") or field in {\"context_tokens\", \"max_output_tokens\"} else \"info\"\n                events.append({\n                    \"severity\": severity,\n                    \"provider\": provider,\n                    \"model\": model,\n                    \"change\": field,\n                    \"before\": before.get(field),\n                    \"after\": after.get(field),\n                    \"source_url\": after.get(\"source_url\"),\n                    \"checked_date\": after.get(\"checked_date\"),\n                })\n    return events\n\ndef write_snapshot(path: Path, rows: list[ModelCatalogRow]) -> None:\n    payload = [serialize(row) for row in rows]\n    path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + \"\\n\", encoding=\"utf-8\")\n```\n\nThe 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.\n\nSnapshot diffs tell you what changed. Policy checks tell you whether your application can still operate within its own requirements.\n\nFor example, suppose a Tier 1 SaaS team uses long-context coding agents and requires:\n\nRepresent that as code. Keep it small enough that an on-call engineer can read it at 2 a.m.\n\n``` python\nimport datetime as dt\n\ndef validate_policy(rows: list[ModelCatalogRow], today: str) -> list[str]:\n    issues = []\n    today_date = dt.date.fromisoformat(today)\n\n    for row in rows:\n        age = (today_date - dt.date.fromisoformat(row.checked_date)).days\n        if age > 7:\n            issues.append(f\"{row.provider}/{row.model}: source check is {age} days old\")\n\n        if not row.source_url.startswith(\"https://\"):\n            issues.append(f\"{row.provider}/{row.model}: source URL is missing or not HTTPS\")\n\n        if row.context_tokens is not None and row.context_tokens < 128_000:\n            issues.append(f\"{row.provider}/{row.model}: context below 128K\")\n\n        if row.output_per_mtok is None:\n            issues.append(f\"{row.provider}/{row.model}: output price missing\")\n\n        if row.cached_input_per_mtok is None and row.context_tokens and row.context_tokens >= 500_000:\n            issues.append(f\"{row.provider}/{row.model}: long-context model has no cached input field\")\n\n    return issues\n```\n\nRun 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.\n\nIf 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.\n\nThe useful pattern is two layers:\n\nAIWave 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.\n\nHere is a minimal route check against an OpenAI-compatible model list. Use your own base URL and keep the key in the environment.\n\n``` php\nimport os\nimport requests\n\ndef fetch_openai_compatible_models(base_url: str) -> set[str]:\n    api_key = os.environ.get(\"AIWAVE_API_KEY\")\n    if not api_key:\n        raise RuntimeError(\"AIWAVE_API_KEY is required\")\n\n    response = requests.get(\n        f\"{base_url.rstrip('/')}/v1/models\",\n        headers={\"Authorization\": f\"Bearer {api_key}\"},\n        timeout=20,\n    )\n    response.raise_for_status()\n    payload = response.json()\n    return {item[\"id\"] for item in payload.get(\"data\", []) if \"id\" in item}\n\ndef check_required_routes(available: set[str], required: set[str]) -> list[str]:\n    return sorted(required - available)\n```\n\nThis 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.\n\nA good drift monitor produces boring, specific tickets:\n\nThe 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.\n\nFor 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.\n\nBefore you trust the monitor, run it through the same discipline as any operational tool:\n\n`Decimal`\n\nfor price fields.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.\n\nChinese 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.", "url": "https://wpnews.pro/news/build-a-model-catalog-drift-monitor-for-chinese-ai-apis", "canonical_source": "https://dev.to/aiwave/build-a-model-catalog-drift-monitor-for-chinese-ai-apis-56e3", "published_at": "2026-08-13 13:12:29+00:00", "updated_at": "2026-08-13 13:19:47.510829+00:00", "lang": "en", "topics": ["developer-tools", "ai-infrastructure", "mlops"], "entities": ["DeepSeek", "Kimi", "Z.AI", "QwenCloud", "AIWave", "GLM", "Qwen"], "alternates": {"html": "https://wpnews.pro/news/build-a-model-catalog-drift-monitor-for-chinese-ai-apis", "markdown": "https://wpnews.pro/news/build-a-model-catalog-drift-monitor-for-chinese-ai-apis.md", "text": "https://wpnews.pro/news/build-a-model-catalog-drift-monitor-for-chinese-ai-apis.txt", "jsonld": "https://wpnews.pro/news/build-a-model-catalog-drift-monitor-for-chinese-ai-apis.jsonld"}}