tma1-ai/

DSH OTel

DeepSeek Harness 的遥测,就是标准 OpenTelemetry。

不需要 collector,不需要 sidecar,不用 fork DSH。它是一个标准的 DeepSeek Harness 插件,安装后每个 turn、每次模型调用、每次工具执行都会成为 GreptimeDB 中一行可查询的数据。

安装

dsh plugin --profile web add @tma1-ai/dsh-plugin-greptimedb

包内自带 bundle patch,一条命令即可完成安装。需要 pnpm 10 及以上版本。默认配置指向本地 GreptimeDB。

指向自有数据库,或先启动一个本地实例
$DSH_HOME/profiles/<name>/cordis.patch.yml
- id: greptimedb-otel
name: '@tma1-ai/dsh-plugin-greptimedb'
config:
endpoint: https://<host>/v1/otlp
database: <dbname>
username: <user>
password: <password>
本地 greptimedb
docker run -p 127.0.0.1:4000-4003:4000-4003 \
-v "$(pwd)/greptimedb_data:/greptimedb_data" \
--name greptime --rm greptime/greptimedb:v1.2.1 standalone start \
--http-addr 0.0.0.0:4000 --rpc-bind-addr 0.0.0.0:4001 \
--mysql-addr 0.0.0.0:4002 --postgres-addr 0.0.0.0:4003
greptimedb · sql
-- 最慢的工具调用,以及是哪个模型发起的。
SELECT tool.span_name,
chat."span_attributes.gen_ai.request.model" AS model,
tool.duration_nano / 1000000 AS ms
FROM opentelemetry_traces AS tool
JOIN opentelemetry_traces AS chat
ON chat.trace_id = tool.trace_id
AND chat."span_attributes.dsh.step" = tool."span_attributes.dsh.step"
AND chat.span_name LIKE 'chat%'
WHERE tool.span_name LIKE 'execute_tool%'
ORDER BY tool.duration_nano DESC
LIMIT 10;

chat span 和 tool span 共享同一个 trace 和同一个 dsh.step,关联它们只需要一次普通的 SQL join。时间戳取自 session 事件本身,而非插件处理该事件的时刻。

三种信号,一个插件

traces、metrics、logs。signals 接受任意子集,关闭的信号不会构建 exporter。

默认不导出任何内容

默认的 content: none 只导出结构和计数,不含 prompt、消息内容、工具参数和工具返回。

配置错误在加载阶段暴露

配置非法时插件在加载阶段失败并指出具体字段,而不是等到第一次导出才出错。

三种信号

每种信号包含什么

traces 描述结构,metrics 用于长期留存和不受采样影响的分位数,logs 保留原始 session 事件。

[01]

turn、chat、tool

turn span 是根节点,chat span 和 tool span 作为兄弟节点挂在其下,通过 dsh.step 关联。每个 chat span 都有确定的结束时间,崩溃时也是如此。

span 树
invoke_agent dsh turn/start → turn/end
├── chat deepseek-chat step/start → assistant/message
├── execute_tool bash tool/call → tool/result
└── chat deepseek-chat
[02]

Token 口径

DSH 的计数互不重叠:inputTokens 只统计未命中缓存的输入,缓存读和缓存写是独立字段。gen_ai.usage.input_tokens 是计费总量,因此插件把三者相加。

token 属性
gen_ai.usage.input_tokens = inputTokens
+ cacheReadTokens
+ cacheWriteTokens
gen_ai.usage.output_tokens = outputTokens
 
# 拆分口径仍然可查
dsh.usage.uncached_input_tokens
dsh.usage.cache_read_tokens
dsh.usage.cache_write_tokens
dsh.usage.reasoning_tokens
[03]

七个 Grafana 仪表盘

Overview、Cost、Sessions、Agent loop、Trace explorer、Log explorer、Metrics。仪表盘放在 grafana/,并附带一个同时启动 GreptimeDB 和 Grafana 的 compose stack,每个面板的查询都在 CI 中针对真实数据库校验。

localhost:3000 · overview
DSH OTel overview 仪表盘:turn 数、模型调用、计费 token、缓存命中率、延迟
[04]

一个 turn,逐个 span 看

每张表都可以继续下钻:trace id 打开该 turn 的 waterfall,session id 在 trace 视图和日志视图之间切换。

localhost:3000 · trace explorer
DSH OTel trace explorer 仪表盘
[05]

一条 session 事件一行

session、事件类型、turn、step 都是独立的列,按 session 过滤无需解析 JSON。

greptimedb · sql
SELECT session_id, event_type, turn, step, body
FROM dsh_logs
WHERE session_id = '...' AND event_type = 'tool/result'
ORDER BY timestamp;

Metrics

指标 instrument

与 traces 覆盖同一批活动,改用 PromQL 查询,用于更长的留存和不受采样影响的分位数。

Instrument类型维度
gen_ai.client.token.usageHistogramgen_ai.token.type(只有 input/output)、model、provider
gen_ai.client.operation.durationHistogramgen_ai.operation.name、model、provider
gen_ai.invoke_agent.durationHistogramgen_ai.operation.name
gen_ai.execute_tool.durationHistogramgen_ai.operation.name、gen_ai.tool.name
dsh.token.detailHistogramdsh.token.detail_kind(cache_read/cache_write/reasoning)
dsh.tool.invocationsCountergen_ai.tool.name、dsh.tool.outcome
dsh.turns / dsh.stepsCounter—

配置

常用的几个配置键

profile patch 会整体替换该行的 config 而不是合并,需要保留的字段必须全部重写。

键默认值说明
endpoint必填OTLP 基础 URL,/v1/{traces,metrics,logs} 后缀由插件自动追加。
databasepublic作为 X-Greptime-DB-Name 发送。
username / password无Basic auth,两者同时提供或同时留空。
signals全部三种traces、metrics、logs 的任意子集。
contentnone允许多少 payload 离开进程。
ttl180d插件创建的表的留存时间,也接受 forever。

批量、超时、service name 和目标表名都有默认值,完整列表见 README。

哪些数据会离开本机

由 content 决定

默认不导出任何 payload,需要放开时按 profile 显式配置。

模式导出内容
none (默认)结构和计数:事件类型、turn 与 step 编号、token 数、工具名、耗时、结果,以及错误的 name 和 code。
full增加用户和助手的消息内容、工具参数、工具返回。
full+prompt再增加 request/header:完整的 system prompt 和每个工具的 schema。

无论哪种模式,工具私有的 meta payload、失败请求的 message 和 stack 都不会导出。投影按白名单进行,插件未知的事件类型只导出其标识。

配合 TMA1

也可以直接指向 TMA1

TMA1 将 OTLP 代理到它自己管理的 GreptimeDB。改一行配置,DSH 就出现在它的 OTel GenAI 视图里。

flow 表天然对齐

TMA1 的 tma1_token_usage_1m、cost_1m、latency_1m、status_1m 都由 span_attributes.gen_ai.* 推导,而这个插件按约定填充这些字段,不需要额外配置。

cordis.patch.yml
endpoint: http://localhost:14318/v1/otlp

已知限制

接入生产前先看这些

完整清单见 README。

DSH 还没正式发布

在第一个 tag 之前它会随意改名和重新打包,因此 peer 版本范围锁定在 CI 验证过的版本上。

GenAI 语义约定还是实验性的

属性名来自 @opentelemetry/semantic-conventions/incubating,会跟着它变。

ttl 对指标表不生效

metric engine 的留存是物理表的属性,需要在物理表上单独设置(greptimedb#8951)。

导出是批量的

没有按 turn 的 flush,进程退出时仍在传输中的记录可能丢失。