DeepSeek Harness 工具全被 defer_loading:恢复会话 400 怎么修
旧会话前一天还好好的,今天打开后每条消息都返回 INVALID_REQUEST;新建一个会话,同样的模型、同样的工具,却完全正常。 这类「只坏历史会话」的 400,八成不是上下文太长,而是恢复时工具列表被全部标成了 defer_loading,请求里没有任何一个可加载声明。DeepSeek Harness(DSH)的 llm-deepseek 又采用 addition-only 策略,两者一叠加,工具集合就成了空集。
DeepSeek Harness 三个信号确认全量延迟
一句话结论:新建会话正常、旧会话必失败,且错误码是 INVALID_REQUEST 而不是带 token 数字的窗口错误,就先怀疑工具列表。 再做三个检查:
- 在恢复后的会话里发一句「你好」;预期仍然 400,说明与具体任务无关。
- 打开调试日志,对比恢复前后请求体的
tools数组长度;预期恢复后变成 0、或只剩一个占位项。 - 把同一会话切到另一个 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 检测到全量延迟时补一个同名的可加载占位,让请求重新合法。
- 从插件市场安装
@argszero/dsh-all-deferred-tools-guard;预期状态为 active。 - 打开插件配置,确认「检测阈值」为「全部工具都被延迟」而不是「任意工具被延迟」;预期正常会话不会被误补。
- 重启 DSH,打开中毒会话发一条消息;预期不再 400。
- 检查日志里是否出现一次 guard 注入记录;预期是插件生效,而不是模型碰巧成功。
- 如果会话里工具很多,观察首轮延迟;预期占位声明只补一个,不会显著变慢。
修法二:改投影逻辑,保留至少一个可加载工具
根治点在 projectToolUpdates,让它在全量延迟时保留一个工具不标 deferLoading,或把 addition-only 改成增量合并。
- 在
packages/llm/llm/src/content.ts的投影循环里,统计本次新增工具数;当全部都会被标记延迟时,跳过第一条的标记;预期至少保留一个可立即加载项。 - 或者改
models.ts:13的toolUpdate策略,从addition-only改为保留历史声明的合并模式;预期历史工具不会被删空。 - 重新构建,用一个带多轮工具调用的旧会话验证恢复;预期消息不再 400。
- 补测试:工具列表全延迟、部分延迟、无延迟三种情况各一条;预期三种都符合预期。
- 确认改动不会让正常会话的工具 schema 体积失控;预期请求体大小在可接受范围。
两种改法的取舍:保留一个可加载项改动小,但模型可能被迫先加载一个它并不需要的工具;改成合并策略更符合直觉,但会增大每轮请求体,长会话的 token 成本上升。
修法三:把会话迁到新会话
不想装插件也不想改代码时,用「导出关键消息 + 新建会话」的方式绕过中毒的工具投影,代价是丢掉部分上下文。
- 在旧会话里找到最近几轮的关键结论,复制到剪贴板或导出为 Markdown;预期关键结论完整保留。
- 新建会话,把关键背景粘进去;预期新会话从干净的工具列表开始。
- 如果旧会话必须保留,先用另一个 provider 打开并让它完成一次工具调用,再切回原 provider;预期工具列表被重建。
- 确认新会话连续两轮工具调用都正常;预期不再出现 400,再把旧会话归档。
DSH 插件修复后的验证与边界
一句话结论:至少用一个 10 轮以上的旧会话、一个新会话和一次 provider 往返,才能确认修复同时覆盖恢复与新建两条路径。 验证步骤:
- 准备一个包含 10 轮以上工具调用的旧会话,恢复后发三条消息;预期都不再 400。
- 新建会话做同样操作;预期没有因为修复引入新的工具缺失。
- 切换 provider 往返一次;预期工具列表能正确重建。
- 查看请求体大小;预期每轮 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,是因为恢复时工具列表被整体标记成 defer_loading,而 endpoint 采用 addition-only 策略,不在本次 tools 数组里的工具会被删掉。唯一没被延迟加载的工具一旦也被移除,请求里就没有可加载项,服务端直接返回 400。
DeepSeek Harness 的 defer_loading 是让模型先看工具摘要、需要时再加载完整 schema 的机制,本意是省上下文。恢复会话时 projectToolUpdates 给每条新增事件都标 deferLoading: true,而 addition-only 只保留本次显式声明的工具,两者叠加就把可加载集合清空了。
DeepSeek Harness 的旧会话可以先装社区 all-deferred-tools-guard 插件,它在 llm/stream 层检测到全量延迟时补一个同名 placeholder,让请求重新合法;也可以把关键消息导出到新会话。若两者都不可行,临时切到不采用 addition-only 的 provider,再发一条消息让工具列表重建。
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
来源
- #10163 — DeepSeek Messages 400s when a session's tool list becomes all-deferred· deepseek-ai(GitHub Discussions)
- DeepSeek Harness 官方仓库· deepseek-ai(GitHub)