DeepSeek Harness Hub
← 返回列表

jwilson411/dsh-otel

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
未验证

一个 DeepSeek Harness 函数插件,将一次会话导出为

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/1 · 已提供中文文档

DeepSeek Harness 插件:从会话日志中发出 OpenTelemetry span(turn / step / tool execute)。仅导出。

综合分
28.1
GitHub 分
28.1
用户评分
★ Stars
0
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add jwilson411/dsh-otel
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/20(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-tools@deepseek-ai/cordis
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

dsh-otel

一个 DeepSeek Harness 函数插件,将一次会话导出为
OpenTelemetry span:每一轮、每一步、每次工具调用各一个 span,链接
成一条 trace,发送到任意 OTLP/HTTP 收集器——Jaeger、Phoenix、Grafana
Tempo、OpenTelemetry Collector——或写入 stdout。

范围只有一个方向:把会话日志输出为 span。 harness
已经写下了所发生事情的持久记录;Jaeger 已经知道如何
绘制 trace。缺失的是翻译,而这正是本插件所做的全部。
这里没有任何东西在运行时观察 harness、缓冲、采样或写入
会话日志。

它默认关闭,它不把 OpenTelemetry SDK 作为运行时
依赖——OTLP/JSON 文档是手写的——并且它不把任何提示词、工具参数或工具结果放到 span 上。一个 span 携带名称、
id、时间信息,以及日志本身报告的 token 计数。

安装

dsh plugin --profile web add github:jwilson411/dsh-otel

dsh plugin 会在 $DSH_HOME/profiles/web 内转发给 pnpm,然后协调
profile:由于此包的清单声明了 dsh.bundle.patch,它会被
追加到 profile 清单有序的 dsh.profile.bundles 列表中,其
cordis.patch.yml 成为一个层。用同样的方式移除它,把 add 换成
remove 即可。

请注意,@deepseek-ai/dsh-tools 的 npm latest 标签仍指向较旧的
0.0.1-rc.1;0.1.1-rc.2 系列发布在 next 下。请显式固定版本,
而不要依赖该标签。

从 profile 自己的 cordis.patch.yml 中开启它——请注意,以 id 为目标的
补丁会替换该行的整个 config,因此请重新声明你打算保留的每个字段:

- id: otel
config:
exporter: otlp-http
endpoint: http://127.0.0.1:4318/v1/traces
sessionLog: ~/.dsh/sessions/current.jsonl
sessionId: sess-1

| 配置键 | 环境变量回退 | 默认值 |
|---|---|---|
| exporter | DSH_OTEL_EXPORTER | off —— 取值为 off、stdout、otlp-http 之一 |
| endpoint | DSH_OTEL_ENDPOINT | http://127.0.0.1:4318/v1/traces(仅由 otlp-http 使用) |
| sessionLog | DSH_OTEL_SESSION_LOG | 未设置 —— 未指定日志的调用会大声失败 |
| sessionId | DSH_OTEL_SESSION_ID | 未设置 —— 回退到日志的头部,否则为 unknown |

默认关闭是有意为之。 在未配置 exporter 的情况下,插件仍会
注册其工具,该工具仍会构建并统计 span——因此模型可以
回答“那次运行进行得怎么样?”——但没有任何东西离开进程。跨越
机器边界的遥测应当是某人主动开启的东西。

span

| span | 由谁开启 | 由谁关闭 | 父级 |
|---|---|---|---|
| dsh.session | 第一个带时间的事件 | 最后一个事件 | ——(根) |
| dsh.turn | turn/start | turn/end | dsh.session |
| dsh.step | step/start | step/end | 其所属的 turn |
| dsh.tool.execute | tool/call | 具有相同 callId 的 tool/result | 其所属的 step |

属性:

| 键 | 位于 | 含义 |
|---|---|---|
| session.id | 每个 span | 该 trace 所属的会话 |
| dsh.turn | turn、step、tool | 轮次编号 |
| dsh.step | step、tool | 该轮次内的步骤编号 |
| dsh.event.seq.start / dsh.event.seq.end | 每个 span | 该 span 开启和关闭时所对应的日志行 |
| dsh.turn.reason | turn | 来自 turn/end 的 data.reason,当日志报告了该值时 |
| tool.name、tool.call_id | tool | 该工具及其所响应的调用 |
| tool.error | tool | 结果是否报告了失败 |
| dsh.usage. | step | token 计数,逐字复制自 assistant/message 的 usage |
| dsh.span.unclosed | 任意 | 日志在此括号关闭之前就已结束 |

除非某个工具结果报告了错误,或某个轮次以读起来像失败的原因结束(error、failed、cancelled),否则状态为 OK。该映射刻意保持保守:遇到不熟悉的原因时报告为 OK,而不是去猜测。

dsh.usage. 是复制的,从不计算。没有文本被分词,没有任何东西被求和,日志未报告的计数就直接缺失。

Id 是派生出来的,不是生成的

trace id 是 sha256(sessionId) 截断为 32 个十六进制字符;span id 是 sha256("/") 截断为 16 个,其中 key 是该括号在日志中的自身坐标:

turn:1                      → the turn span
turn:1:step:2               → its second step
turn:1:step:2:tool:call_7   → the tool call inside that step

因此,将同一份日志导出两次会产生相同的树。如果第一次收集器宕机了,重新运行导出即可——你是在修复缺口,而不是复制它。如果任何地方都没有 session id,则 id 派生自字面字符串 unknown。

读取器能容忍什么

- 不是 JSON 的行会被计数,在 malformed_lines 中报告,并被跳过。尾部捕获到部分写入不会让你丢失整个 trace。
- 本插件不认识的事件类型会被忽略。
- 日志从未关闭的括号会在最后一个事件的时刻结束,并标记为 dsh.span.unclosed,而不是被丢弃——EOF 时仍在进行中的工具调用通常正是你所要找的那个。
- 上方没有 turn/start 的 step/start 会合成其轮次,因此树永远不会出现悬空的父节点。

针对 Jaeger 试一试

docker run --rm -p 16686:16686 -p 4318:4318 jaegertracing/all-in-one:latest
dsh-otel export --log ~/.dsh/sessions/current.jsonl --session sess-1 --exporter otlp-http --endpoint http://127.0.0.1:4318/v1/traces
UI: http://127.0.0.1:16686

otel_export 工具

| | |
|---|---|
| Cordis 插件 id | otel(cordis.patch.yml 中的行 id) |
| 注入 | tools —— 一个硬依赖;该插件会等待而不是降级 |
| 工具 | otel_export |
| 参数 | log(字符串,可选)、session_id(字符串,可选) |

log 默认为配置的 sessionLog,session_id 默认为配置的 sessionId,否则为日志头部所指定的 id。不带 log 的调用
从任一位置失败都会大声报错,而不是导出一个空 trace。

它返回 log、session_id、trace_id、exporter、endpoint、spans、
exported、turns、steps、tools、malformed_lines 和 status。
exported 是实际离开进程的 span 数量,因此当 exporter 关闭时它是 0,而计数仍然描述这次运行。

该工具读取一个文件,并且最多向你配置的 endpoint 发送 POST 请求。它不需要 API key。

CLI

从 shell 执行同样的导出。它除了 node: 和本包之外不导入任何东西,因此可以在未安装依赖的 checkout 上运行——当你需要的是 trace,而坏掉的是 harness 时,这很有用。

dsh-otel export --log  [--session ] [--exporter off|stdout|otlp-http]
[--endpoint ] [--service ] [--json]

stdout 只承载 OTLP 文档,不承载其他内容,因此重定向始终会得到一个有效文件;运行的报告输出到 stderr,以文本形式,或者在使用 --json 时以 JSON 形式。CLI 默认使用 stdout exporter——你在提示符下要求导出,所以打印它是它能做的最不令人意外的事情,而且它仍然不会向任何地方发送任何内容。

$ dsh-otel export --log ~/.dsh/sessions/current.jsonl > spans.json
session sess-1 → trace 4f8c…
14 span(s): 3 turn(s), 6 step(s), 4 tool call(s)
14 span(s) written to stdout
skipped unparseable log line(s): 92

不在范围内

- 托管产品。 这是一个文件读取器和一个 HTTP POST。
- UI。 Jaeger、Phoenix 和 Tempo 已经可以绘制 trace。
- 替换或注释 session log。 日志是事实来源,本包只读取它——没有 sidecar,没有 cursor 文件,没有状态。
- Langfuse 克隆。 没有 prompt 捕获,没有 evals,没有 datasets,没有评分。
- 运行时插桩。 span 来自事后的日志,而不是来自运行中的 turn 内部的 hook。

布局

package.json        manifest + dsh.bundle.patch — what makes this a bundle
cordis.patch.yml    the bundle's patch layer: one insert, one plugin row
src/session.js      reading the session log as JSONL, tolerating bad lines
src/spans.js        events → spans, with ids derived rather than generated
src/otlp.js         the OTLP/JSON document and the three exporters
src/export.js       one export: read, build, send, report
src/index.js        the plugin: name, inject, apply(ctx, config)
bin/                the CLI
test/               offline tests over a checked-in fixture log
package-lock.json   the pinned dependency tree npm ci installs in CI

测试

npm install
npm test

从构造上就是离线的。每个测试都读取已检入的
test/fixtures/session.jsonl;stdout exporter 写入注入的 sink,
otlp-http 通过注入的 fetch 发送 POST 请求,因此整个导出路径都被覆盖,
无需 collector、socket 或 key。

只有 test/plugin.test.js 需要一个依赖:它将插件注册到
存根上下文,并使用真实的
@deepseek-ai/dsh-tools 验证工具的结果,该依赖在 devDependencies 和
package-lock.json 中固定为 0.1.1-rc.2,以便针对一个已知 API 测试契约。其他所有内容——库、CLI 以及其他三个测试文件——都不导入 node: 和本包之外的任何内容。

CI(.github/workflows/ci.yml)在 Node 22 和 24 上,基于已提交的 lockfile,仅针对公共注册表运行 npm ci 和 npm test。它不需要任何凭据,测试套件也不会访问网络。

许可证

MIT——参见 LICENSE。

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群