Repository navigation
Replies: 3 comments
|
The premise is confirmed — Where it lives
const CHARS_PER_TOKEN = 4
const BLOCK_OVERHEAD = 4 // :16
export const ROLE_OVERHEAD = 4 // :19and the constant is applied uniformly — text blocks ( There are two estimators, and they disagree In the same tree, the pi-ai adapter's estimator uses a different constant: That is worth a line in your report because it means a fix in one place leaves the other: correcting On the impact claim Your chain — estimated
A framing note "Under-prices" implies the cost is the problem; the durable problem is that compaction decisions are made against a number that is wrong by a factor that depends on the user's language. A user writing Chinese gets a systematically different compaction policy from a user writing English, on the same session size. That is the sentence I would put at the top. |
|
The pure-CJK probe shows a weakness in I checked the published Log There is also a mismatch in the mixture calculation. If your percentages are character shares, |
|
Both points hold, and I checked them against the code rather than just against the published package. The anchor gate is So the constant cannot produce a whole-session shortfall on its own. A 22.7% gap between On the arithmetic you are also right, and this one is my error rather than the reporter's. What survives is narrower than the title suggests but still real: the constant is script-blind, the block-level 2.28x under-count for pure CJK stands, and the exposure is the cold or full-reprice measurement plus one step's growth. One detail that cuts in your favour and is worth stating: because the usage baseline is only adopted when usage is at least the full heuristic anchor price ( |
Uh oh!
There was an error while loading. Please reload this page.
Summary
tokenMeter's fixedCHARS_PER_TOKEN = 4heuristic systematically under-prices CJK content. Measured against a live OpenAI-compatible route (deepseek-v4.1-flash), the same request over-counts English text by ~1.4x and under-counts Chinese text by ~2.3x. On a real agent session whose surface was 5.3% CJK,tokenMeterestimated 106,241 tokens while the upstream billed 137,416 — a 22.7% shortfall.This matters because
compaction-basiccomparesmeasurement.totalTokensagainstthresholdTokensto decide whether to compact, but sizes the compaction span from the same estimate. When the estimate is low, a session sits below the trigger while the route sits above it, and each compaction removes a span priced by the low estimate.Measured evidence
Density probe against
POST /v1/responses(identical request shape, only the text differs; baseline = a single character):prompt_tokenschars/4would sayx)Per-character rates: Chinese 0.57 tokens/char, English 0.18 tokens/char.
CHARS_PER_TOKEN = 4(0.25 tokens/char) sits between them, so it is neither a conservative nor an accurate estimate — it is wrong in opposite directions depending on content.Full-session cross-check (
@deepseek-ai/dsh-token-meter0.2.1-alpha.2, 430 surface nodes):The session's surface was 5.3% CJK / 93.8% ASCII. A naive content-weighted prediction from the probe rates gives ~25% shortfall, matching the observed 22.7%.
Why it is hard to notice
The shortfall is proportional to the share of CJK text, so it is invisible in English-only sessions and grows as a conversation turns Chinese. It also compounds with any content the estimate does not price at all:
estimateContentwalksmessage.contentblocks only. Fields that still reach the request (for example the adapter'sdeveloper/system message assembled from the session, tool schema framing) are priced byestimateToolsTokensandestimateSystemMessageseparately, which is correct — but the per-message container overhead beyondBLOCK_OVERHEADis not.estimateStructuralBlockat reference cost, which is right for a route that prices images as images. On a route that cannot read the Responses API array form offunction_call_output.output, the route bills the base64 as text instead; that is a route-capability bug, not a meter bug, but the meter cannot see it and the compaction loop then works from a value that is orders of magnitude too small.Reproduction
ctx.tokenMeter.measure(session).totalTokensand theusage.inputTokensof the nextassistant/message.Or use the density probe directly — send the two 2 KB strings above to
/v1/responsesand compareusage.prompt_tokensagainstceil(len(text)/4).Suggested direction
CHARS_PER_TOKENis a single global constant, but the correct density is a property of the script mix, not of the model. Something like an ASCII-vs-CJK aware estimate (for exampleasciiChars/4 + cjkChars/1.75, calibrated from the probe above) would remove the systematic error without needing a real tokenizer. If a real tokenizer is out of scope, a per-route density multiplier would at least let a deployment correct for its own traffic.Alternatives considered and rejected: keeping
chars/4and raisingheadroomTokensonly makes the slop larger; compacting earlier is a global tax on every session to cover a defect that only affects CJK ones.Environment
@deepseek-ai/dsh-token-meter0.2.1-alpha.2@deepseek-ai/dsh-compaction-basic0.2.1-alpha.2openai-responses,contextWindow: 640000,maxTokens: 32000, modeldeepseek-v4.1-flashprompt_tokens, so the count is the model's, not the gateway's(Related: #9041 covers a threshold collapse from a large route
maxTokens; #8088 reports early compaction at ~50% context; #4454 covers thesummary is not smallerabort. This is a different defect — the estimate itself, not the threshold arithmetic.)All reactions