LLM pricing tables hard-coded inside tools drift out of sync with what providers actually charge. Because vendor price pages change silently — and because legacy model names keep working after the underlying model is retired and repriced — a local table can keep quoting a rate that no longer exists. The result is systematically wrong cost figures (typically overstated) that persist across many releases, because nothing in the codebase compares the table to reality. The fix is not auto-patching prices from an API, but treating a mismatch as a drift alarm: a signal that sends a human to the vendor's page.
This page distills a worked DeepSeek/YoYo case and the prior art from yoagent's price_audit.rs.
Key Points
Vendor models are retired while their names keep working. DeepSeek now lists only two models (deepseek-flash, deepseek-v4-pro), but still accepts deepseek-v4-flash and deepseek-v4-flash-vision-exp; those requests are served by DeepSeek-V4.1-Flash and billed at the Flash price. A table keyed on the legacy name can never notice.
A stale row is provably stale repo-internally..yoyo.toml's own comment (2026-09-15) says deepseek-v4-flash is a legacy name served by V4.1-Flash at the Flash price — so the table row pricing it otherwise contradicts the repo's own documentation, independent of the live page.
First-match ordering hides the correct row.src/format/cost.rs:187 matches deepseek-v4-flash | deepseek-r1 and is tested before the correct row at :200, so the config's model id lands on the stale entry.
A test that hard-codes the table is vacuous.:1713's near-miss guard pins the wrong number byte-identically; agreeing with the table proves nothing when the table is what drifts.
Silent drift is durable.yoagent's own header records claude_sonnet_5 carrying Sonnet 4.6 rates ($3/$15 vs published $2/$10) from v0.9.0 through v0.16.5 — 18 tagged releases, 50% overstatement on every , found by a person asking, not by any mechanism.
cost_usd
Cache-read pricing is where tables break quietly. Cache-hit reads are often an order of magnitude below cache-miss (DeepSeek: $0.003 vs $0.15 per 1M). A table leaving cache_read at 0.0 is not "conservative"; it reports a higher bill than reality.
A machine-readable external source of truth exists.https://models.dev/api.json (222 providers, ~4.7 MB) exposes per-provider, per-model cost {input, output, reasoning, cache_read}.
Design rule: a drift failure is an alarm for a human to check the vendor page — never an auto-patch.
Concepts
Pricing table drift — divergence between a locally hard-coded rate and the vendor's current published rate.
Legacy alias / retired model name — a name still accepted by the API but now served by a different model at a different price.
Drift alarm — a failing audit test whose purpose is to notify a human, not to rewrite data.
Cache-hit (cache_read) rate — price per 1M tokens for reads that hit the provider's prompt cache.
Peak / off-peak pricing — time-windowed multiplier on list price.
models.dev — OpenCode-team-maintained model catalog, TOML on GitHub, served as api.json / models.json / catalog.json.
Provider-serving facts vs model facts — models.dev separates what a model is from how a provider serves and prices it; pricing tables live on that second axis.
Near-miss guard — a test that pins a specific numeric constant; useful only if the constant is independently sourced.
Details
The DeepSeek ground truth
DeepSeek's own pricing table lists exactly two current models: deepseek-flash and deepseek-v4-pro. Footnote (1) states, verbatim:
"Use deepseek-flash as the model name. The legacy names deepseek-v4-flash and deepseek-v4-flash-vision-exp are still accepted, but the corresponding models have been retired, their requests are served by the DeepSeek-V4.1-Flash model and billed at the Flash price."
deepseek-flash, off-peak, per 1M tokens:
Component
Off-peak
Peak
Input (cache miss)
$0.15
$0.30
Output
$0.60
$1.20
Cache-hit read
$0.003
$0.006
Peak windows are 01:00–04:00 and 06:00–10:00 UTC, Mon–Fri (double the off-peak rate).
src/format/cost.rs:187 prices the arm deepseek-v4-flash | deepseek-r1 at (0.55, 0.0, 0.0, 2.19) — roughly 3.7× overstatement (0.55 / 0.15 ≈ 3.67, 2.19 / 0.60 ≈ 3.65), with cache-read wrongly zeroed.
That arm is matched before:200 (the Flash-rate row), so the model id named in .yoyo.toml resolves to the stale row.
.yoyo.toml's own comment (2026-09-15) already asserts the legacy name is served at the Flash price — i.e. that :187 should carry the :200 numbers. The live page and models.dev independently confirm the comment; :187 is stale.
src/format/cost.rs:1713's near-miss guard pins the wrong number byte-identically. A test whose expected value is copied from the table is vacuous against table drift.
Prior art: yoagent 0.18.1 tests/price_audit.rs
Audits the crate's presets against models.dev.
Header records the claude_sonnet_5 defect: Sonnet 4.6 rates ($3/$15) instead of published $2/$10, shipped from v0.9.0 through v0.16.5 — 18 tagged releases, every cost_usd 50% too high.
It was found by someone asking, not by any mechanism.
The stated design rule: a failure is a drift alarm that sends a human to the vendor's page, never an auto-patch.
Why an external source of truth
models.dev is built by the OpenCode team; its data is TOML on GitHub, exposed as api.json / models.json / catalog.json. Critically, it separates model facts from provider-serving facts (pricing, context limits) — exactly the axis a pricing table occupies, and therefore a usable reference for an audit test.
Rendering diagram…
The recurring lesson: cost tables are configuration that silently expires, so they need an external comparator and a loud, human-routed failure mode.