DeepSeek Harness 工具全被 defer_loading:恢复会话 400 怎么修

故障排查发布于 2026-10-20作者: DeepSeek Plugin 插件市场
DeepSeek HarnessDSHdefer_loadingINVALID_REQUEST工具调用会话恢复llm-deepseek
DeepSeek Harness 恢复旧会话后每轮 INVALID_REQUEST,原因是工具列表被全量标记 defer_loading、没有任何可加载声明;给出插件、配置与重建会话三种修法。

旧会话前一天还好好的,今天打开后每条消息都返回 INVALID_REQUEST;新建一个会话,同样的模型、同样的工具,却完全正常。 这类「只坏历史会话」的 400,八成不是上下文太长,而是恢复时工具列表被全部标成了 defer_loading,请求里没有任何一个可加载声明。DeepSeek Harness(DSH)的 llm-deepseek 又采用 addition-only 策略,两者一叠加,工具集合就成了空集。

DeepSeek Harness 三个信号确认全量延迟

一句话结论:新建会话正常、旧会话必失败,且错误码是 INVALID_REQUEST 而不是带 token 数字的窗口错误,就先怀疑工具列表。 再做三个检查:

  1. 在恢复后的会话里发一句「你好」;预期仍然 400,说明与具体任务无关。
  2. 打开调试日志,对比恢复前后请求体的 tools 数组长度;预期恢复后变成 0、或只剩一个占位项。
  3. 把同一会话切到另一个 provider(例如不采用 addition-only 的适配器)再发一条;预期能成功,基本锁定在工具声明层。

据讨论里的复现记录,projectToolUpdates 给每条 tool-addition 都标 deferLoading: true,而 llm-deepseek 的 models.ts:13 把 toolUpdate 设为 'addition-only',不在本次 options.tools 里的声明会被删掉(#10163)。工具列表的上下文管理可以对照 上下文插件。

为什么恢复会话会把 DSH 工具清空

一句话结论:恢复时工具变更被投影成「全部延迟加载」,而发送端只保留显式声明的工具,两个「只看本次」的规则叠加,唯一非延迟的工具也被移除。

拆开看是三步:

  • 会话日志里保存了历史工具变更事件。
  • 恢复时 projectToolUpdates 把这些事件重放,并统一打上 deferLoading: true。
  • 请求组装时 addition-only 删除不在 options.tools 中的工具,最后没有任何工具处于「可立即加载」状态。

模型侧看到的是一个只有延迟声明、没有实体定义的集合,服务端直接拒绝。

DeepSeek Harness 的三种修法

一句话结论:不改核心就装 guard 插件,能维护 fork 就改投影逻辑,只想马上继续干活就把会话迁到新会话。

修法一:装 all-deferred-tools-guard 插件

这是不改核心、恢复最快的方式,插件会在 llm/stream 检测到全量延迟时补一个同名的可加载占位,让请求重新合法。

  1. 从插件市场安装 @argszero/dsh-all-deferred-tools-guard;预期状态为 active。
  2. 打开插件配置,确认「检测阈值」为「全部工具都被延迟」而不是「任意工具被延迟」;预期正常会话不会被误补。
  3. 重启 DSH,打开中毒会话发一条消息;预期不再 400。
  4. 检查日志里是否出现一次 guard 注入记录;预期是插件生效,而不是模型碰巧成功。
  5. 如果会话里工具很多,观察首轮延迟;预期占位声明只补一个,不会显著变慢。

修法二:改投影逻辑,保留至少一个可加载工具

根治点在 projectToolUpdates,让它在全量延迟时保留一个工具不标 deferLoading,或把 addition-only 改成增量合并。

  1. 在 packages/llm/llm/src/content.ts 的投影循环里,统计本次新增工具数;当全部都会被标记延迟时,跳过第一条的标记;预期至少保留一个可立即加载项。
  2. 或者改 models.ts:13 的 toolUpdate 策略,从 addition-only 改为保留历史声明的合并模式;预期历史工具不会被删空。
  3. 重新构建,用一个带多轮工具调用的旧会话验证恢复;预期消息不再 400。
  4. 补测试:工具列表全延迟、部分延迟、无延迟三种情况各一条;预期三种都符合预期。
  5. 确认改动不会让正常会话的工具 schema 体积失控;预期请求体大小在可接受范围。

两种改法的取舍:保留一个可加载项改动小,但模型可能被迫先加载一个它并不需要的工具;改成合并策略更符合直觉,但会增大每轮请求体,长会话的 token 成本上升。

修法三:把会话迁到新会话

不想装插件也不想改代码时,用「导出关键消息 + 新建会话」的方式绕过中毒的工具投影,代价是丢掉部分上下文。

  1. 在旧会话里找到最近几轮的关键结论,复制到剪贴板或导出为 Markdown;预期关键结论完整保留。
  2. 新建会话,把关键背景粘进去;预期新会话从干净的工具列表开始。
  3. 如果旧会话必须保留,先用另一个 provider 打开并让它完成一次工具调用,再切回原 provider;预期工具列表被重建。
  4. 确认新会话连续两轮工具调用都正常;预期不再出现 400,再把旧会话归档。

DSH 插件修复后的验证与边界

一句话结论:至少用一个 10 轮以上的旧会话、一个新会话和一次 provider 往返,才能确认修复同时覆盖恢复与新建两条路径。 验证步骤:

  1. 准备一个包含 10 轮以上工具调用的旧会话,恢复后发三条消息;预期都不再 400。
  2. 新建会话做同样操作;预期没有因为修复引入新的工具缺失。
  3. 切换 provider 往返一次;预期工具列表能正确重建。
  4. 查看请求体大小;预期每轮 tools 声明没有膨胀到影响延迟。

避坑要点:

  • 不要靠反复重发消息「撞过去」,全量延迟是确定性的,重发只会稳定失败。
  • 插件补齐的是占位声明,不是真正恢复工具 schema;如果模型需要调用被延迟的工具,仍要等它主动加载。
  • addition-only 是有意设计,改成合并策略前先确认不会破坏历史工具清理逻辑。
  • 从中毒会话 fork 出的子会话可能继承同样的工具事件,修复后要单独验证。
  • 如果修复后首轮响应明显变慢,先确认 guard 只补了一个占位,而不是给每个延迟工具都补了声明。
  • 讨论里没有给出官方修复版本,0.2.1-rc.2 仍可复现;升级后以 release notes 为准,别把 DSH插件 的旧行为当成已修。

DeepSeek Harness 工具延迟常见问题

Q:DeepSeek Harness 打开旧会话后每轮 INVALID_REQUEST,新建会话却正常,为什么? A:DeepSeek Harness 旧会话每轮 INVALID_REQUEST,是因为恢复时工具列表被整体标记成 defer_loading,而 endpoint 采用 addition-only 策略,不在本次 tools 数组里的工具会被删掉。唯一没被延迟加载的工具一旦也被移除,请求里就没有可加载项,服务端直接返回 400。

Q:DeepSeek Harness 的 defer_loading 是什么,为什么会让工具列表变空? A:DeepSeek Harness 的 defer_loading 是让模型先看工具摘要、需要时再加载完整 schema 的机制,本意是省上下文。恢复会话时 projectToolUpdates 给每条新增事件都标 deferLoading: true,而 addition-only 只保留本次显式声明的工具,两者叠加就把可加载集合清空了。

Q:不想改源码,DeepSeek Harness 里怎么救一个已经中毒的会话? A:DeepSeek Harness 的旧会话可以先装社区 all-deferred-tools-guard 插件,它在 llm/stream 层检测到全量延迟时补一个同名 placeholder,让请求重新合法;也可以把关键消息导出到新会话。若两者都不可行,临时切到不采用 addition-only 的 provider,再发一条消息让工具列表重建。

Q:DeepSeek Harness 的 400 没说明工具列表问题,怎么排除上下文超限? A:DeepSeek Harness 的全量延迟 400 是 INVALID_REQUEST,且同一会话切到新会话就恢复;上下文超限通常带 token 数或 model window 字样。再检查恢复前后请求体里的 tools 数组长度,如果恢复后变成 0 或只剩 placeholder,就能确认。

常见问题

DeepSeek Harness 打开旧会话后每轮 INVALID_REQUEST,新建会话却正常,为什么?

DeepSeek Harness 旧会话每轮 INVALID_REQUEST,是因为恢复时工具列表被整体标记成 defer_loading,而 endpoint 采用 addition-only 策略,不在本次 tools 数组里的工具会被删掉。唯一没被延迟加载的工具一旦也被移除,请求里就没有可加载项,服务端直接返回 400。

DeepSeek Harness 的 defer_loading 是什么,为什么会让工具列表变空?

DeepSeek Harness 的 defer_loading 是让模型先看工具摘要、需要时再加载完整 schema 的机制,本意是省上下文。恢复会话时 projectToolUpdates 给每条新增事件都标 deferLoading: true,而 addition-only 只保留本次显式声明的工具,两者叠加就把可加载集合清空了。

不想改源码,DeepSeek Harness 里怎么救一个已经中毒的会话?

DeepSeek Harness 的旧会话可以先装社区 all-deferred-tools-guard 插件,它在 llm/stream 层检测到全量延迟时补一个同名 placeholder,让请求重新合法;也可以把关键消息导出到新会话。若两者都不可行,临时切到不采用 addition-only 的 provider,再发一条消息让工具列表重建。

DeepSeek Harness 的 400 没说明工具列表问题,怎么排除上下文超限?

DeepSeek Harness 的全量延迟 400 是 INVALID_REQUEST,且同一会话切到新会话就恢复;上下文超限通常带 token 数或 model window 字样。再检查恢复前后请求体里的 tools 数组长度,如果恢复后变成 0 或只剩 placeholder,就能确认。

相关术语

defer_loading
defer_loading 是 DeepSeek Harness 的工具声明优化,允许模型先看到工具名和摘要,真正调用前再加载完整参数 schema。它要求请求里至少保留一个可加载的工具声明,否则整个工具集合无法解析。— https://github.com/deepseek-ai/deepseek-harness/discussions/10163
addition-only
addition-only 是 llm-deepseek 适配器的工具更新策略,只保留本次请求显式声明的工具,不在列表里的历史工具会被移除。它与全量 defer_loading 叠加时会把可加载项清空。— https://github.com/deepseek-ai/deepseek-harness/discussions/10163
projectToolUpdates
projectToolUpdates 是 DeepSeek Harness 把会话中的工具变更投影成模型请求的函数,位于 llm 包的 content 模块。它给新增工具统一打 deferLoading 标记,是本次问题的起点。— https://github.com/deepseek-ai/deepseek-harness/discussions/10163

来源