DeepSeek Harness 插件

apex-mochen/dsh-reasoning-only-guard

Star 数 ★ 0 分类 会话与消息 收录于 2026-09-16

防止「只有推理、没有正文」的一轮把整个会话变成不可用。某一轮没有可见文本也没有 tool-call 时,assistant 消息会以空内容落盘,此后该会话的每一次请求都会被网关以 "content or tool_calls must be set" 拒绝,会话彻底死亡且无法再从对话里找回。本插件只注册一个 llm/stream waterfall 监听器,且仅当这一轮没有任何可见产出时,在终止 finish 之前注入一小段文本,使落盘消息永远不为空。附带 DSH 自带 mock 造不出的夹具(严格只有推理的 SSE 服务,因为 llm-mock-server 不允许空 successText,且 reasoning_success 总会再补一段正文),以及把真实落盘会话经 DSH 自己的 serializeMessages 重放的端到端复现。属预防而非修复。零依赖、单文件、不访问进程与文件系统。

安装

# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)

dsh plugin --profile web add github:apex-mochen/dsh-reasoning-only-guard

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWEDERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yamlallowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。

README

防止「只有推理、没有正文」的一轮把整个会话毒死。

当某一轮既没有可见正文、也没有工具调用时,本插件会注入一小段文本块, 让落盘的 assistant 消息永远不为空

它防的是什么

模型有时会把回答全部放在推理通道里,于是这一轮既没有 text、也没有 tool_call。 这条 assistant 消息就以空 content 落盘,之后该会话的每一轮都会重放它。 而网关会拒绝「既没有 content 也没有 tool_calls」的 assistant 消息 (content or tool_calls must be set)—— 于是从那一刻起,这个会话的每次请求都失败, 会话永久不可用,里面的工作也无法再从对话里访问。

这个失效模式在 DSH 源码里就有记录,出自 packages/llm/llm-deepseek/src/serialize.ts

// Text-less turns send "" — NEVER null. ... Reasoning-ONLY turns (the model
// can answer entirely in the reasoning channel, e.g. a v4-flash greeting): the
// live API rejects null-content/no-tool_calls assistant messages with a 400
// ("content or tool_calls must be set"), and since the message sits durably in
// the session log, a null here bricks every later turn of that session.
content: text,

这段注释说的是 null,而当前实现发的是 ""对那个检查而言,空串同样等于"没设" —— 这也是社区核实报告认为该缺陷在 master 上仍然存在的原因。见 DSH 讨论 #6520 第 2 条, 那里同时记录了目前唯一的规避办法:解开 session.v3.jsonl.zstd, 手工把那条 reasoning-only 记录的 content 改成占位文本,再压回去。

本插件的确切定位(不夸大)

  • 是预防,不是修复。 它让空消息从一开始就不产生; 它无法救回已经中毒的会话 —— 那条记录已经在日志里了,修它意味着改写会话存储,本插件有意不碰。
  • 依据是「社区核实报告 + 上面的源码」。没有亲自复现端到端的 400; 我直接验证的是守卫机制本身(15 项单元测试)以及插件在 waterfall 链中参与正确(社区运行时验证器)。
  • 不是核心修复。 干净的修法应该在适配器里。而 DSH 目前不接受外部 PR (CONTRIBUTING.md 原文:"we are currently unable to accept external PRs"), 所以插件是当下够得着的介入点。

安装

dsh plugin --profile web add github:apex-mochen/dsh-reasoning-only-guard

装完重启该 profile 即可,无需其它配置。

配置

- id: dsh-reasoning-only-guard
  config:
    placeholder: '[本轮没有可见输出]'      # 默认是一句较长的说明
    includeFailedTurns: false            # 是否也守卫 error/aborted 轮
    enabled: true                        # 设为 false 可保留安装但停用
选项 类型 默认 含义
placeholder string 说明性句子 注入的占位文本,避免 assistant 消息为空
includeFailedTurns boolean false 是否在 error / aborted 结束时也注入
enabled boolean true 停用守卫而不卸载

怎么确认它生效了

dsh --profile web --dump-config | grep reasoning-only-guard

插件会作为独立节点出现。它的行为边界很窄,便于核对: 只有当整轮没有任何可见内容时,才会在终止的 finish 分块紧前面 补上「文本块的 block-start / text-delta / block-end」这三块。

设计取舍

为什么用 llm/stream,不用 agent/request agent/request 解析出的是 LlmCallConfig,只有 provider / model / 采样参数,不含 messages —— 它影响不了落盘内容。

为什么不直接改 messages。 监听器确实能拿到 GenerateOptions.messages,但请求在派发前被深冻结packages/llm/llm/src/index.ts 里的 deepFreeze(structuredClone(...))request-freeze.spec.ts 的用例标题就是 "freezes nested messages at dispatch"), 而 llm/streamnext() 又不收参数 → 既不能就地改、也不能替换。 就地修改冻结对象不是修复,是埋雷。

为什么返回值才是入口。 llm/stream 是 waterfall,监听器返回的 AsyncIterable 就是调用方消费的流 —— 包一层是被支持的介入方式,也是唯一还能影响这一轮内容的位置。

为什么注入在 finish 之前。 累加器是边流边记的:注入在终止 finish 之后,有读不到的风险。 守卫本身不缓冲 —— 每个分块立即透传,只在看到 finish 时才补那三块。

为什么 next() 必须无条件只调一次。 waterfall 监听器漏调或重复调用 next(),会静默吞掉 agent 的默认行为 —— 这是这个生态唯一的红线。单元测试断言"恰好调用一次",运行时验证器则端到端检查整条链。

为什么默认不动失败轮。error / aborted 轮里写正文会歪曲事实;报告中的缺陷是正常结束的 reasoning-only 轮。

为什么零依赖。 这个插件位于每一轮的请求路径上。一个文件、只用 Node 内置能力、没有别的东西需要审计。

安全提示

安装 DSH 插件等于授予它进程级权限。 插件被加载进宿主进程,不受沙箱限制

本插件的目标是可审计,而不是"请相信我":

  • 零依赖:实现就是 lib/index.js(约 180 行),没有任何 import
  • 不碰进程、文件、网络:不 spawn、不读、不写、不请求
  • 没有定时器:它只是包一层别人交给它的异步迭代器
  • 不会给正常轮编内容:只有当整轮没有可见文本、也没有工具调用时才注入占位; 它从不修改、也从不丢弃收到的任何一个分块
  • 一口气能读完:lib/index.js

和已有插件的关系

发布时目录里没有任何插件覆盖这个失效模式(按 reasoning-only、空 content、会话报废等描述检索)。 相邻的插件守的是别的 wire 层问题 —— 例如 dsh-tool-call-guard 中性化「arguments 非法 JSON 的 tool call」—— 本插件用同一种形状处理另一个缺陷。

兼容性

  • DSH 0.1.x(peer:@deepseek-ai/cordis ^4.0.1
  • Node.js 20+
  • 只注册 一个 waterfall 监听器(llm/stream),不贡献任何工具

许可

MIT

内容来自项目 README(GitHub)↗

链接

同类插件

查看整个分类 →

社区评论

评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。