DeepSeek Harness 动态工具更新:Adapter 与会话恢复

作者
DeepSeekAgent.io 编辑部
发布
更新

在 DeepSeek Harness 中安装、升级或移除插件后,当前 Session 的可用工具会发生变化。DSH 需要让模型知道哪些工具刚加入、哪些已经不可用;如果 Provider 路由不能正确表达这种“会话中途的工具更新”,一次正常的插件操作就可能变成持续性的 HTTP 400。

2026 年 10 月 21 日的社区报告在 v0.2.1-rc.2 上记录了这条链路:Agent Loop 把工具增删写成持久的 developer/message,DeepSeek Adapter 再把其中的 tool-addition 或 tool-removal 序列化进请求。上游接口拒绝该结构后,失败记录仍留在 Session 历史中;重试、重启甚至 /compact 都可能再次投影同一条记录。

这与 REQUEST_EXTENSION 不是同一种故障。后者发生在 HTTP 请求发出之前,常见原因是插件清单或 manifest 无法解析;动态工具故障则是请求已经构造并发往 Provider,但消息结构不被当前路由接受。

DSH 为什么要记录工具变化

Agent 开始一轮任务时,系统提示和工具定义构成了一个基线。如果运行过程中插件注册了新工具,直接在下一次请求中替换完整工具列表,会让模型无法区分“此前不可用”和“现在新增”。动态工具协议用显式变更描述差异:

上一轮工具集合
  → 插件安装、更新或卸载
  → 计算 additions / removals
  → 追加 developer/message
  → Adapter 投影为 Provider 请求

这套设计对长 Session 很有价值:模型可以保留此前上下文,同时理解工具注册表已经变化,也有机会保持稳定的 Prompt 前缀与 KV Cache。但它依赖 Adapter 与 Provider 对同一种中途变更协议达成一致。

故障发生在哪一层

社区复现中的 Agent Loop 会比较工具基线与当前名称集合。有差异时,它把 tool-addition 和 tool-removal 内容写入 Session。DeepSeek Adapter 随后将这些内容投影到消息列表,并在存在工具变更时加入对应的 Beta Header。

报告捕获到的请求片段类似:

{
  "role": "system",
  "content": [
    {
      "type": "tool_addition",
      "tool": { "type": "tool_reference", "name": "ops_bash" }
    }
  ]
}

Provider 返回的错误指向 tool_addition 所在位置。关键问题不是插件工具本身执行失败,而是工具更新消息在当前 API 路由上的表达不兼容。

从 Harness 设计看,这里至少有三道能力判断:

  1. Agent Loop 是否应该为当前 Prepared Call 生成动态工具记录;
  2. Adapter 是否声明支持 addition、removal 或两者;
  3. Provider 路由是否真正接受该消息结构与 Header。

只要声明能力与实际接口不一致,Session 就可能写入一条以后无法投影的持久记录。

为什么重启和换模型不一定有效

工具变化不是只存在于内存中的临时状态,而是写入 Session V4 日志。重启 DSH 后,历史仍会被重新投影。若模型切换仍走同一 Adapter,或者投影阶段在选择模型前就保留了那条 Developer Message,换模型也不会自动清除它。

社区的独立复现还显示,/compact 可能同样失败:压缩也要先构造包含历史的模型请求,因此会再次遇到不兼容记录。由此产生的用户体验是:

  • 插件更新前 Session 正常;
  • 更新后第一条消息立即返回 400;
  • 之后每次发送都得到相同错误;
  • 新建 Session 可能正常,因为新会话从当前工具基线开始,不需要重放中途变更。

最后一点是重要诊断信号,但不是绝对证明。仍应保存完整错误和 Provider 请求 ID,排除凭证、额度和普通上下文溢出。

哪些操作最容易触发

  • 在仍然打开的 Session 中安装带工具的插件;
  • 把插件升级到新增、重命名或删除工具的版本;
  • 停用插件或切换 Profile,使一批工具同时移除;
  • 插件热重载后注册的工具集合与 Header 基线不同;
  • 从旧版本恢复 Session,而当前版本的工具命名已经变化。

单纯打开插件市场不会产生问题;真正的触发条件是当前 Session 观察到工具集合发生变化,并把差异写入历史。

先判断是不是这一类 400

现象更可能的层级
报错包含 REQUEST_EXTENSION,无 Provider 请求 ID本地请求前置扩展
报错指向 tool_addition 或 tool_removal动态工具消息投影
插件更新后只有原 Session 失败持久工具变更记录
新旧 Session 都返回 401、402、429凭证、余额或限流
错误是 context_length_exceeded上下文窗口或压缩
新 Session 正常、旧 Session 重启后仍失败旧历史中存在不可重放结构

不要看到“插件更新后失败”就删除整个 Profile。REQUEST_EXTENSION、插件运行时崩溃、工具协议不兼容和 Provider 认证失败需要完全不同的处理。

安全恢复顺序

1. 冻结现场

停止自动续轮,不要继续安装或卸载更多插件。记录 DSH 版本、插件及版本、当前 Profile、模型路由、完整 HTTP 状态和 Provider 请求 ID。

2. 用新 Session 做最小验证

保持当前插件集合,新建一个 Session,只发送不调用工具的短消息,再执行一个只读工具调用:

  • 新 Session 正常:Provider 与当前工具基线基本可用,旧 Session 历史是重点;
  • 新 Session 也失败:检查插件注册、Adapter 路由和全局配置,不要急着迁移历史。

3. 导出旧 Session 的任务状态

能打开时先复制关键结论、未完成清单、修改过的文件与已经执行的副作用。不能继续请求模型时,使用 DSH 提供的导出或只读查看方式保留 Transcript。

4. 从摘要恢复工作

在新 Session 中写入人工检查过的短摘要,明确:目标、已完成工作、待办、关键路径、测试状态和禁止重复的外部操作。先做只读核验,再继续执行。

5. 保留原始数据

不要把直接修改压缩 Session 日志当作常规修复。社区报告中,删除或改写 Developer Message 后,即使日志本身可读,也可能因为 Projection Cache 与原字节状态不一致而从 UI 消失。原 Session 更适合作为审计记录。

如何避免再次发生

  1. 重要长任务期间不要更新插件。 在任务完成或创建检查点后再改变工具集合。
  2. 用隔离 Session 验证插件升级。 先确认工具注册、只读调用和模型路由都正常,再回到生产任务。
  3. 更新前导出工具清单。 对比新增、删除和重命名项,尤其关注一次变化几十个工具的插件。
  4. 为长任务保存外部状态摘要。 不让恢复能力完全依赖单个 Session 日志。
  5. 固定 DSH 与插件版本。 可复现任务不要在运行中追随浮动版本。

对 DSH 实现而言,更稳健的策略是在写入动态工具记录前检查 Prepared Call 能力;不支持时重新发送完整工具定义或丢弃增删块。Provider 拒绝动态工具消息后,系统也应允许后续投影忽略该记录,而不是让一次失败永久阻断 Session。

与 KV Cache 的关系

动态工具的另一个目标是减少每轮完整重写工具 Schema,稳定 Prompt 前缀并提高缓存复用。但缓存优化不能越过协议兼容性:

  • 支持中途工具更新的路由,可以发送差异并保持较稳定前缀;
  • 不支持的路由,应回退到完整工具列表或重新建立 Session 基线;
  • Adapter 不能仅凭模型名称假设网关实现了同一 Beta 协议。

因此排查时要记录完整路由,而不只是模型 ID。deepseek-flash 经过官方账号适配、Anthropic 兼容端点或第三方网关,实际接受的消息结构可能不同。

相关阅读

参考资料