DEV Community

Mattias chaw
Mattias chaw

Posted on

A CI Check for Chinese LLM Model Names and Token Budgets

Chinese model APIs move quickly enough that hardcoded model names become a production risk. The problem is not only quality drift. It is also billing drift, context-window drift, deprecation drift, and integration drift across OpenAI-compatible clients.

If you run a SaaS feature, an internal coding agent, or a support automation pipeline, you want the model catalog to behave like any other deploy-time dependency. Pin it, inspect it, budget it, and fail the build when the assumptions are stale.

This walkthrough builds a small manifest check around the AIWave pricing page, then uses the manifest in an OpenAI-compatible client pointed at https://aiwave.live/v1. AIWave is a Singapore-hosted gateway for Chinese AI models behind one API key. It currently exposes pricing data through the live pricing page API; for this article I fetched it on 2026-08-04 and calculated USD prices from the page's current model_ratio and completion_ratio fields.

Do not put API keys in source control. Every code example below uses YOUR_API_KEY_HERE as a placeholder.

Why a manifest check belongs in CI

Official model catalogs are not static. DeepSeek's API changelog documents the V4 transition and alias behavior around deepseek-chat and deepseek-reasoner. Moonshot's Kimi model list says older kimi-k2 models should move to kimi-k2.6. Qwen's public repository tracks frequent Qwen3 and Qwen3-Coder 2507 updates. Z.AI's release notes list GLM-4.5 on 2025-07-28 and GLM-5.1 on 2026-04-07. MiniMax's model release notes list M2.5 in February 2026 and M2.7 on 2026-03-18.

Those changes matter in ordinary engineering work:

  • a model alias can point to a different backend than last month;
  • a cheaper model may be fine for classification but wrong for agentic coding;
  • a cache lane may change the economics of long prompts;
  • a gateway may expose a model before your app has retry and budget policy for it.

The safe pattern is boring: treat the model list as runtime data, not as a constant in the app.

Current AIWave prices used in this post

Source: live AIWave pricing API behind https://aiwave.live/pricing, fetched 2026-08-04. The page returned 62 models. The input price below is model_ratio * 2. The output price is input_price * completion_ratio. All values are USD per 1M tokens.

Model Input $/1M Output $/1M Cache read $/1M
qwen3-235b-a22b-thinking-2507 0.342466 3.424660 0.034247
deepseek-r1 0.605000 2.409000 n/a
glm-4.5 0.697500 2.170000 0.180000
kimi-k2.6 1.090000 4.599800 0.186857
qwen3.5-122b-a10b 0.493150 3.726044 n/a
ERNIE 5.0 2.465754 9.315125 n/a

For a simple workload estimate of 10M input tokens and 2M output tokens:

Model Input cost Output cost Estimated total
qwen3-235b-a22b-thinking-2507 $3.42 $6.85 $10.27
deepseek-r1 $6.05 $4.82 $10.87
glm-4.5 $6.98 $4.34 $11.32
qwen3.5-122b-a10b $4.93 $7.45 $12.38
kimi-k2.6 $10.90 $9.20 $20.10
ERNIE 5.0 $24.66 $18.63 $43.29

This is not a benchmark. It is only billing math. Latency, tool reliability, context behavior, and output quality still need your own evaluation.

Step 1: fetch a live pricing manifest

This script reads the public pricing JSON, calculates input and output prices, and writes a compact model manifest. It exits non-zero if a required model is missing, which makes it useful in CI.

#!/usr/bin/env python3
import json
import sys
from datetime import date
from urllib.request import Request, urlopen

PRICING_URL = "https://aiwave.live/api/pricing"
REQUIRED_MODELS = {
    "qwen3-235b-a22b-thinking-2507",
    "deepseek-r1",
    "glm-4.5",
    "kimi-k2.6",
}


def fetch_json(url: str) -> dict:
    req = Request(url, headers={"User-Agent": "model-manifest-ci/1.0"})
    with urlopen(req, timeout=20) as response:
        if response.status != 200:
            raise RuntimeError(f"pricing API returned HTTP {response.status}")
        return json.loads(response.read().decode("utf-8"))


def to_price(row: dict) -> dict:
    input_price = float(row["model_ratio"]) * 2
    output_price = input_price * float(row.get("completion_ratio", 1))
    cache_ratio = row.get("cache_ratio")
    cache_read = input_price * float(cache_ratio) if cache_ratio is not None else None
    return {
        "model": row["model_name"],
        "input_usd_per_1m": round(input_price, 6),
        "output_usd_per_1m": round(output_price, 6),
        "cache_read_usd_per_1m": None if cache_read is None else round(cache_read, 6),
    }


payload = fetch_json(PRICING_URL)
manifest = {item["model"]: item for item in map(to_price, payload["data"])}
missing = sorted(REQUIRED_MODELS - set(manifest))

if missing:
    print(f"Missing required models: {', '.join(missing)}", file=sys.stderr)
    sys.exit(1)

result = {
    "source": PRICING_URL,
    "verified_on": date.today().isoformat(),
    "model_count": len(manifest),
    "models": {name: manifest[name] for name in sorted(REQUIRED_MODELS)},
}

print(json.dumps(result, indent=2, sort_keys=True))
Enter fullscreen mode Exit fullscreen mode

In a deployment pipeline, store the generated JSON as a build artifact. Review it when a model disappears, when output prices change materially, or when a cache lane appears for a model that previously had none.

Step 2: use the manifest for routing

The model router should not decide from vibes. Give it a cost ceiling, a workload type, and a current manifest. This JavaScript example uses the OpenAI SDK with AIWave's OpenAI-compatible base URL.

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: "YOUR_API_KEY_HERE",
  baseURL: "https://aiwave.live/v1",
});

const models = {
  "qwen3-235b-a22b-thinking-2507": {
    inputUsdPer1m: 0.342466,
    outputUsdPer1m: 3.42466,
    useFor: ["reasoning", "long_context"],
  },
  "glm-4.5": {
    inputUsdPer1m: 0.6975,
    outputUsdPer1m: 2.17,
    useFor: ["tools", "structured_output"],
  },
  "kimi-k2.6": {
    inputUsdPer1m: 1.09,
    outputUsdPer1m: 4.5998,
    useFor: ["agentic_coding", "large_context"],
  },
};

function estimateUsd(model, inputTokens, outputTokens) {
  const m = models[model];
  return (
    (inputTokens / 1_000_000) * m.inputUsdPer1m +
    (outputTokens / 1_000_000) * m.outputUsdPer1m
  );
}

function chooseModel(task, inputTokens, expectedOutputTokens, maxUsd) {
  const candidates = Object.entries(models)
    .filter(([, spec]) => spec.useFor.includes(task))
    .map(([name]) => ({
      name,
      estimatedUsd: estimateUsd(name, inputTokens, expectedOutputTokens),
    }))
    .filter((row) => row.estimatedUsd <= maxUsd)
    .sort((a, b) => a.estimatedUsd - b.estimatedUsd);

  if (candidates.length === 0) {
    throw new Error(`No model fits budget for ${task}`);
  }
  return candidates[0].name;
}

const model = chooseModel("structured_output", 25_000, 1_200, 0.05);

const response = await client.chat.completions.create({
  model,
  messages: [
    { role: "system", content: "Return compact JSON only." },
    { role: "user", content: "Extract company, role, and required skills from this job post: ..." },
  ],
  response_format: { type: "json_object" },
});

console.log(response.choices[0].message.content);
Enter fullscreen mode Exit fullscreen mode

Keep the manifest in your app repository if it is generated by CI, but never commit the API key. In production, read the key from a secret manager or deployment environment.

Step 3: reject prompts that exceed the budget

Budget checks should run before the API call. That gives you predictable failure modes and avoids turning a user-provided document into an unexpectedly large bill.

from dataclasses import dataclass


@dataclass(frozen=True)
class Price:
    input_usd_per_1m: float
    output_usd_per_1m: float


PRICES = {
    "glm-4.5": Price(input_usd_per_1m=0.6975, output_usd_per_1m=2.17),
    "kimi-k2.6": Price(input_usd_per_1m=1.09, output_usd_per_1m=4.5998),
    "deepseek-r1": Price(input_usd_per_1m=0.605, output_usd_per_1m=2.409),
}


def estimate_cost(model: str, input_tokens: int, max_output_tokens: int) -> float:
    price = PRICES[model]
    return (
        input_tokens / 1_000_000 * price.input_usd_per_1m
        + max_output_tokens / 1_000_000 * price.output_usd_per_1m
    )


def enforce_budget(model: str, input_tokens: int, max_output_tokens: int, limit_usd: float) -> None:
    estimated = estimate_cost(model, input_tokens, max_output_tokens)
    if estimated > limit_usd:
        raise ValueError(
            f"{model} estimate ${estimated:.4f} exceeds request budget ${limit_usd:.4f}"
        )


enforce_budget("glm-4.5", input_tokens=80_000, max_output_tokens=4_000, limit_usd=0.10)
print("request is inside budget")
Enter fullscreen mode Exit fullscreen mode

This is intentionally conservative because it uses max_output_tokens. If the model stops early, the real bill should be lower. If your tokenizer estimate is inaccurate, add margin instead of trusting a perfect count.

Operational notes

Model routing should be explicit in logs. Record the chosen model, estimated input tokens, maximum output tokens, estimated USD, retry count, and failure reason. Do not log raw prompts unless you have a privacy policy and retention process for that data.

For EU or UK users, keep regional controls separate from model choice. A gateway with Singapore servers can be useful for latency and consolidation, but GDPR readiness still depends on your own data processing agreement, retention policy, subprocessors, user deletion workflow, and whether prompts include personal data.

The honest drawbacks are also worth stating. A gateway adds another dependency between your app and the model vendor. Some provider-specific features may lag behind the native API. Pricing pages can update faster than your docs. That is why the manifest check exists.

FAQ

Is AIWave a drop-in OpenAI replacement?

For many chat-completion workloads, yes: use base_url or baseURL as https://aiwave.live/v1 and keep the OpenAI SDK. You still need to test model-specific behavior, tool calling, JSON mode, streaming, and token limits.

Should I pick the cheapest row in the table?

No. Use the table to set a budget, then benchmark your own tasks. A support classifier, a code-editing agent, and a long-context legal review have different failure costs.

Why not hardcode one model?

Hardcoding is fine for a prototype. Production systems usually need a fallback when a model is unavailable, deprecated, slower than expected, or outside the request budget.

Where should I start?

Read the AIWave API documentation, verify the latest prices on AIWave pricing, generate a manifest in CI, and keep YOUR_API_KEY_HERE out of git.

Sources checked

  • AIWave pricing page and live pricing API, fetched 2026-08-04.
  • DeepSeek API changelog: https://api-docs.deepseek.com/updates.
  • Moonshot Kimi model list: https://platform.kimi.ai/docs/models.
  • Qwen3-Coder official blog and Qwen3 repository news: https://qwenlm.github.io/blog/qwen3-coder/ and https://github.com/QwenLM/Qwen3.
  • Z.AI release notes and pricing docs: https://docs.z.ai/release-notes/new-released and https://docs.z.ai/guides/overview/pricing.
  • MiniMax release notes: https://platform.minimax.io/docs/release-notes/models.

Top comments (0)