Skip to content

@deepseek-ai/dsh-token-meter 提供 ctx.tokenMeter,一个具备回放感知能力的单一计量服务,它回答基于持久化会话日志的两个问题:下一个请求要花多少钱(请求压力)以及 当前活动的对模型可见内容有多少(surface)。它从不依赖实时的适配器状态——一切皆由会话事件推导而来,因此具有确定性、可重启恢复,并且天然适合投影(projection)。

描述
@deepseek-ai/dsh-token-meter"具备回放感知能力的 token 计量服务(ctx.tokenMeter)"

它与 dsh-llmdsh-sessiondsh-compactiondsh-session-projectiondsh-invariantscordis 协同;唯一不在 workspace 内的依赖是 zod@^4.4.3

计量什么

该服务计量的是 token,而不是金额。在这个 harness 里,"用量核算"被拆分为两个互补的视角:

  1. Provider 上报的用量 —— 每次模型调用返回的确切 usage 块,存储在会话的 assistant/message 事件上,并折叠进 tokenUsage 投影(这些是 provider 返回的真实 token 计数)。
  2. 启发式压力/ surface —— TokenMeter.measure(session, requestHeader?) 回放日志,并对一个"surface"(有序的模型可见消息集合)加上一个"baseline"锚点定价。这是对一个请求成本的预测,用于压缩(compaction)与长上下文决策。

在整个 meter 中不存在逐 token 的价目表或美元成本估算——计费/成本不在这个 seam 的范围内。

数据模型

ts
// packages/llm/token-meter/src/types.ts
export type TokenMeasurementBaseline =
  | { readonly kind: 'none'; tokens: 0 }
  | { readonly kind: 'estimated'; tokens: number }      // full heuristic price
  | { readonly kind: 'usage'; tokens: number; usage: Readonly<TokenUsage> }  // provider-anchored

export interface TokenMeasurement {
  logRevision: number          // = next unread event seq
  baseline: TokenMeasurementBaseline
  surfaceDeltaTokens: number   // signed repricing vs. the baseline anchor
  totalTokens: number          // non-negative current request+response pressure
  surfaceTokens: number        // heuristic total across the current surface
  nodes: readonly TokenSurfaceNode[]  // ordered (seq, tokens) per surface position
}

基线(Baselines)

measure()请求请求头(规范请求信封,即 provider/model/system/tools)选取一条基线:

  • usage —— 当最近一次成功调用的规范信封与所给请求头匹配,其 provider 总数不小于该调用的完整启发式价格(保守锚点规则)。provider 用量按原样复用,仅对自该调用以来的signed surface delta重新定价。
  • estimated —— 否则,对信封(system prompt + tool schemas)与当前 surface 进行启发式重新定价。
  • none —— 在存在任何 surface 为空的请求之前。

这样既避免了每次读取都支付启发式计算的代价,又保持了正确性:自锚点以来的新大消息会被加入;缩小了 surface 的压缩会被减去。

固定的启发式估算器

packages/llm/token-meter/src/estimate.ts 是一个刻意保持简单、密度固定的估算器(与 contextBreakdown 投影逐字共享,以保证两个 surface 一致):

元素价格
密度每 token 4 个字符(CHARS_PER_TOKEN
每个块的固定结构开销4 个 token(BLOCK_OVERHEAD
每条消息的 role 字段框架4 个 token(ROLE_OVERHEAD
system promptceil(system.length / 4) + ROLE_OVERHEAD
tool schemasceil(JSON.stringify(tools).length / 4) + BLOCK_OVERHEAD
未知块保守的结构性 JSON 价格

estimateMessageestimateHeaderestimateContent 都是导出的纯函数,因此调用方无需会话即可为单条消息定价。

事件与事件循环如何喂给它

这里没有专门的 "usage 事件"。meter 会旁听普通的会话事件:

事件在计量中的角色
request/header确立规范请求信封(system、tools、config)
step/start / step/end定界一个模型步骤(校验 assistant 事件属于某个打开中的步骤)
assistant/chunk原始流式块;一个 usage 块提供一个早期的 provider 样本
assistant/message终结该步骤;携带确定性的 usage(meter 的主要锚点)
surface 事件(user/messageassistant/message、……)折叠进按 token 定价的 surface

agent 循环(packages/core/agent-loop/src/agent.ts)通过 ctx.llm 流式接收块,分别以 assistant/chunk 追加,然后在步骤结束处以 assistant/message(带 usagesourceEventSeqs)追加——这正是 meter 回放的内容。meter 复用 BlockAssembler,从引用的 chunk 序列重新组装 provider 输出,以在没有第二次流的情况下对 provider 用量与启发式价格进行比较。

投影(用量出现的位置)

通过可选的 ctx.sessionProjections 注册表,meter 注册三个纯回放投影:

投影视图输出
Token 用量tokenUsage{ uncachedInputTokens, outputTokens, cacheReadTokens, cacheWriteTokens } 按 turn/step 去重后的总计
上下文压力contextPressure{ contextWindow?, pressureTokens?, projectedTokens? } —— 来自最新用量样本加 surface 变动的 prompt 侧占用
上下文构成contextBreakdown当前 surface 的按来源定价(instructions/catalog/snapshot/…… 各行)

tokenUsage 是 UI 读取累计模型用量所依赖的投影,contextPressure 则用于上下文窗口占用。两者都是"最后一个获胜"且累积时不重复计数:同一 turn/step 重复的 usage 会替换较早的值,而不是再次累加。

用量在 UI 中如何出现(简述)

  • packages/client/ui-settings-models 与会话头部 surface 使用来自 API gateway 的 session.modelssessionModelSelect,而非直接使用 meter。
  • trajectory/会话头部消费 tokenUsagecontextPressure 投影(在 mux 事件流上以 session/projection 帧交付,并由历史尾部的 projections 块播种)。

meter 本身在 host 侧;client 通过会话投影流水线以及转发的 settings/document-updated/llm/adapters-updated 失效事件看到它。

@deepseek-ai/dsh-token-meter
@deepseek-ai/dsh-llm(定义 TokenUsage
@deepseek-ai/dsh-session-projection(注册表)

延伸阅读

  • LLM 层 —— meter 回放的 usage StreamChunkTokenUsage,以及 assistant/chunk/assistant/message 事件。
  • DeepSeek provider —— 适配器 mapUsage 如何产出 meter 消费的不相交计数。
  • API gateway —— client 如何通过 session/projection 帧与 session.models 接收用量。
  • packages/llm/token-meter/src/index.ts —— TokenMetermeasure_sync、基线选取。
  • packages/llm/token-meter/src/estimate.ts —— 固定的启发式估算器。
  • packages/llm/token-meter/src/usage-projection.ts —— tokenUsage/contextPressure 投影折叠。