遥测与 Token 计量
本教程共 32 篇 · 第 19 篇 · 更新于 2026-08-15 · 约 7 分钟阅读
本节目标:弄清会话数据怎么导出做远程观测、哪些数据会离开本机,以及 dsh 怎么估算每次请求的 token 压力,用它做压缩和溢出重试的决策。
遥测:会话日志的导出副本
第 18 章讲过,会话日志是本地事实源。**session-telemetry(会话遥测)**是它的导出通道:把会话事件投影成记录,交给上报后端。它是一道能力缝(seam),核心服务键是 ctx.sessionTelemetry。
遥测拆成两个角色:
- 捕获协调器(
dsh-session-telemetry):负责捕获点、投影、脱敏瀑布和交接游标,不关心数据发到哪; - 服务提供方(
dsh-session-telemetry-otel):部署方加载的后端,本质是一条原样配置的 OpenTelemetry JS SDK 日志流水线。
遥测是可选能力,不在 agent loop 主干上,也不会给模型请求贡献任何内容。它和本地会话日志是两条路:日志留本地做回放与恢复,遥测出本机做观测与告警。
每条逻辑记录 SessionTelemetryRecord 分两个通道:
- ledger 通道:与会话日志事件一一镜像,带
session.id、event.type、event.seq和完整data副本; - ops 通道:承载
agent-error、shutdown这类没有日志归属的运维信号,刻意不带事件身份,防止被误当成可回放历史。
Note每个
(turn, step)只发出第一条assistant/chunk,作为「流已开始」的信号,其余分片在捕获时丢弃。传输里的 seq 缺口是常态,不是丢数据。
边界:dsh 只负责 emit()
遥测有一条边界公理:harness 的职责止于 emit()。批处理、重试、排队和丢失策略,全部属于上报 SDK。
后端接口只有三件事:
emit(record):非阻塞入队。它跑在session/event热路径上,慢了会拖累 agent loop;flush?():可选提示「轮次结束了」,让 SDK 刷一次导出;shutdown():排空队列到完全停稳,应用拆卸前调用。
投递是尽力而为的:游标标记「已交接」而不是「已送达」。记录可能丢失(崩溃、重载窗口),也可能重复(无游标地重新接管)。接收端要基于 (session.id, event.seq) 去重;ops 记录则刻意容忍重复。
Note所以遥测能观测事实,不能成为事实源。要恢复会话、要精确审计,回第 18 章讲的本机会话日志。
Tip接收端还能用
shutdown标记检测崩溃:正常关闭会发出这个标记,标记之后又出现新事件,说明遥测侧发生过重载。
脱敏瀑布:规则由部署方挂
每条记录在交给后端前,要经过 session-telemetry/record 瀑布事件。seam 自带零条规则:没挂监听器时,记录按捕获原样到达后端。
监听器通过变换 next() 的返回值堆叠;不调用 next() 直接返回,就替换下方全部逻辑;抛异常的监听器 fail-closed——扣下这一条记录,但不会打断 agent loop。
脱敏只作用于导出副本,权威会话日志永不改写。
OTel 后端与三种共享模式
官方后端 dsh-session-telemetry-otel 用 mode 决定数据怎么共享:
| mode | 行为 |
|---|---|
FULL | 每条投影记录立即交给 OTel SDK,含生命周期运维记录 |
FEEDBACK_ONLY | 每次记录反馈时,回放权威日志截至该事件的后缀,脱敏后上传 |
DISABLED | 默认值。不构造任何 SDK 流水线,数据不离开进程 |
FULL 是显式选择,配置片段:
- id: sessionTelemetry-otel
name: '@deepseek-ai/dsh-session-telemetry-otel'
config:
mode: FULL # 显式开启;默认 DISABLED
shutdownTimeoutMillis: 3000
exporter:
url: https://collector.example.com/v1/logs
headers:
authorization: !!js `Bearer ${process.env.OTLP_TOKEN}`
processor: {}
上传模式会组合标准的 OTel 流水线:LoggerProvider → BatchLogRecordProcessor → OTLP/HTTP 日志导出器。ledger 与 ops 记录分属两个 instrumentation scope,便于接收端分开处理。
WarningFULL 模式下,记录携带完整的
event.data:用户与 assistant 消息、工具参数与结果(命令输出、文件内容)、完整系统提示词与工具 schema、压缩摘要。没挂脱敏规则就导出,等于把这些原样送出去。
一个好消息:提供方凭据不会出现。API key 是适配器的构造参数,不是会话事件,结构上就不存在于日志和遥测里。
已挂载的后端通过 ctx.sessionTelemetry.sharing 披露策略(full / feedback-only / disabled),/feedback 命令的确认文本会告诉用户会话是否被共享。传输身份是匿名的:每个导出批次携带 service.name / service.version 和一个随机生成的 user.id(存在 $DSH_HOME/.anonymous-user-id,删掉文件即可重置)。
Warningrc 阶段包名与模式可能变化:
@deepseek-ai/dsh0.1.0-rc.6 之后的版本,配置写法以官方文档为准,装完可以跑dsh --version实测。
遥测数据能用来做什么
遥测导出的数据主要有三类用途:
- 调试与复现:ledger 记录镜像了会话事件,跨机器观察同一段会话的完整行为,定位只在特定环境出现的故障;
- 告警:每条记录在捕获时预映射严重级别(
error/warn/info),接收端零配置就能按级别告警,ops 通道专门承载agent-error这类运维信号; - 成本追踪:配合提供方返回的 usage 数据做用量汇总。注意遥测侧重「发生了什么」,token-meter 侧重「当前多紧」,两者都算不了历史账单——精确成本以模型提供方的计费为准。
Token 计量:测的是压力,不是账单
ctx.tokenMeter(@deepseek-ai/dsh-token-meter)提供当前请求压力的回放快照。一次 measure() 返回:
logRevision:生成这份计量时消费的持久事件数;baseline:计量锚点。usage表示最近一次成功的提供方调用与当前请求 envelope 一致,且总量不低于启发式锚点;estimated表示没有可复用的 usage 锚点,改用固定启发式规则估算;totalTokens:当前请求与响应的压力(非负);surfaceTokens:仅当前表层(surface)的启发式总量,等于所有节点价格之和;surfaceDeltaTokens:相对锚点的带符号增减;nodes:按位置排列的TokenSurfaceNode(seq+ 启发式 token 数)。
快照不可变,表层顺序有权威性:替换节点的持久 seq 可能高于排在它后面的节点。每次测量都要克隆这些节点,复杂度是 O(surface)。除了整表测量,还有 estimateMessage() 可以单独给一条模型可见消息定价。
TipToken 计量不是累计账单。compaction 替换会改变当前 surface 的 token 数,但不会重写历史累计用量。看「花了多少」要看提供方 usage,看「现在多紧」才看 token-meter。
计量结果用在哪
- 上下文压缩:compaction 前后的 token 数由 token-meter 估算并写入事件(见第 18 章);
- 溢出重试:收到 context window exceeded 错误时,compact-basic 监听
agent/request-error,先压缩再重试;压缩后检查replaceGeneration是否增加——没有产生新的 durable replacement,就保留原错误,不重试; - 流式重试:一次 step 不等于一次 HTTP 请求,内部有循环。stream 失败后发布
agent/request-error,中间件返回{ kind: 'retry' }就重建请求再调适配器;失败分片留在日志供诊断,但不提交为模型可见消息。
小结
- 遥测是导出副本:ledger 镜像会话日志,ops 承载运维信号;dsh 只负责
emit(),其余归上报 SDK。 - 脱敏靠
session-telemetry/record瀑布,规则由部署方挂,fail-closed 兜底,日志本身永不改写。 - OTel 后端三种模式:FULL 会带走完整事件数据(凭据除外),默认 DISABLED。
- Token 计量测当前压力:usage 锚点优先、启发式兜底,服务于压缩与溢出重试,不等于账单。