dsh-fingerprint-show
一个 DeepSeek Harness(DSH)插件:捕获每次模型调用的提供方模型指纹(OpenAI 兼容的 system_fingerprint 字段),并在 Web UI 中每条 AI 回复的指标行末尾以纯文本显示。
功能
- 完整指纹文本 — 每条有指标行的 AI 回复末尾以纯文本追加该次调用实际的
模型指纹(如
… 127 tok/s · 0123456789abcdef0123456789abcdef),完整不截断、 无按钮、无图标、无复制交互; - 样式与原生一致 — 字体 14px、行高 24px、三级灰色
(
--dsw-alias-label-tertiary)、10px 圆点间距,与指标文本像素级统一; - 悬停同步 — 指纹与
时间 · 用时 · 首 token · tok/s指标文本同生同隐: 悬停或聚焦消息时一起淡入,移开一起消失(与框架原生行为相同的 80ms 过渡); - 历史保留 — 中途切换模型后,每条回复各自标注当时实际生效的部署;
- 悬停提示 — tooltip 显示模型 id、提供方与调用时间;
- 零侵入 — 不修改会话日志、不进入模型上下文、不影响 KV 缓存。
工作原理
DSH 自带的 DeepSeek 适配器把提供方 SSE 翻译成框架内部的 StreamChunk 协议,
响应级元数据(包括 system_fingerprint)在这一步被丢弃,因此 llm/stream
瀑布事件里看不到指纹。本插件在两个互补的接缝上工作:
- fetch 包装(主机侧)— 所有适配器最终都走全局
fetch。插件 tee 每个 匹配chat/completions的响应体,从 SSE 分支中提取system_fingerprint,另一分支原样交还适配器。 llm/stream瀑布监听(主机侧)— 提供(sessionId, model, purpose)。 每次流式调用恰好对应一个提供方请求,观测按「同模型优先、时间最近」关联到 进行中的瀑布调用。- HTTP 路由(主机侧)— 通过
ctx.webServer.register()在 Web 服务上暴露GET <pathPrefix>/api/observations?sessionId=…&limit=…。之所以不用自定义 会话事件:持久化读取层会拒绝未知事件类型且Session.append不支持ignorable标记;Remote 命名空间则需要构建期代码生成。 - 消息内文本(浏览器侧)— 客户端半区注册
conversation.chat.assistant-actions槽位条目(定稿 AI 消息的操作条, 与复制/分支按钮同区),按时间单调对齐观测与消息——每条消息取「开始于该 消息落盘之前、且未被更早消息消费」的最新观测——以纯文本渲染在指标行末尾; 可见性与指标文本同步(悬停显示)。样式完全镜像原生时间文本 (font-size:14px、line-height:24px、三级灰色、圆点仅右侧 10px 边距、左侧间距由行的 flexgap:10px提供)。
安装
前置条件:已安装 dsh CLI 与 pnpm。
git clone <this-repo> dsh-fingerprint-show
cd dsh-fingerprint-show
pnpm install # prepare 钩子自动构建 lib/
dsh plugin --profile web add <path-to-checkout> # 插件包目录本身的绝对路径,或 .
注意
add的参数是插件包目录本身。在仓库目录里执行add ./dsh-fingerprint-show会把链接指到不存在的子目录(pnpm 会照常记录依赖但 bundle 层不会生效)。 装好后用dsh --profile web --dump-config确认输出里有# == dsh-fingerprint-show一层。
重启 dsh web 后,发起一次对话;把鼠标移到 AI 回复上,指标行末尾会出现
· 完整指纹 文本。
插件只在 web profile 下激活(依赖其
webServer服务);headless profile 中保持休眠。也可以用开发模式快速试用:dsh web --patch ./cordis.patch.yml(需先把 patch 中name:改为指向src/index.ts的绝对路径)。
配置
在 profile 的 cordis.patch.yml 或 --patch 覆盖层中按 id 覆盖该行配置:
- id: fingerprint-show
config:
pathPrefix: /dsh-fingerprint-show # HTTP 路由前缀
matchPatterns: # 对请求 URL 做子串匹配
- chat/completions
maxObservations: 500 # 进程内环形缓冲容量
注意:patch 会整行替换 config,覆盖时请重述全部需要的键。
诊断
浏览器直接访问 /dsh-fingerprint-show/api/observations(同源)可查看捕获结果:
{
"observations": [ { "fingerprint": "…", "model": "…", "sessionId": "…", "time": 123 } ],
"stats": {
"matchedResponses": 2, // 被拦截的匹配响应数
"capturedFingerprints": 2, // 成功提取指纹数
"parsedChunks": 2, // 解析的 SSE 载荷数
"inflightCalls": 0, // 进行中的瀑布调用数
"totalStored": 2 // 缓冲中观测总数
}
}
若 matchedResponses > 0 而 capturedFingerprints = 0,说明提供方响应里没有
system_fingerprint(自建/代理端点常见);lastPayloadSample 会给出最近一次
无指纹响应的首个 SSE 载荷样本。
已知限制
- 并发同模型调用(如同时运行的 subagent)之间按「时间最近」归因,极端并发下 可能归属到相邻调用——但指纹本身标识的是模型部署版本,值始终正确;
- 辅助调用(会话标题、压缩)带
purpose标记,匹配时排除,不参与消息标注; - 指纹由提供方返回,非 OpenAI 兼容的自建端点可能不携带该字段(此时不显示);
- 只在有指标行(
timing)的消息上显示,无指标文本的消息不显示指纹。
开发
pnpm install
pnpm run build # esbuild → lib/index.js (Node ESM) + lib/client.js (浏览器 lazy-CJS)
pnpm test # 主机流水线 + 客户端 bundle 冒烟测试
客户端半区必须构建为 DSH 模块加载器的 lazy-CJS 工厂形式
(window.__ModuleLoader__.load({id, factory})),见 scripts/build.mjs。
License
MIT
No comments yet. Be the first to write one.