DeepSeek Harness 切回 deepseek-official 必现 400:thinking 缺失怎么修

故障排查发布于 2026-10-20作者: DeepSeek Plugin 插件市场
DeepSeek HarnessDSHdeepseek-officialthinking400模型切换序列化
DeepSeek Harness 混合模型会话切回 deepseek-official 后每条消息都 400,提示 thinking must be passed back;本文解释历史缺块的原因并给三种修复方案。

同一个会话,用别的模型跑了几轮,再切回 deepseek-official,从此每条消息都返回 400,服务端只说一句 content[].thinking ... must be passed back。 新建会话完全正常,旧会话怎么重试都没用。这不是模型坏了,而是历史里有一段「有工具调用、没有思考块」的消息——DeepSeek Harness(DSH)的序列化器不会替它补,服务端就拒绝整段历史。

DeepSeek Harness 先确认是历史缺块,不是请求写错

一句话结论:新建会话正常、旧会话必失败,且报错点名 thinking / passed back,就是历史缺块;如果新会话也失败,先查模型配置和密钥。 再做三个快速核对:

  1. 在旧会话里只发一句「你好」;预期仍 400,说明与当前任务无关。
  2. 把会话切到另一个不要求 thinking 回传的 provider;预期能成功,基本锁定是适配器要求差异。
  3. 打开调试日志,找请求体里最后一条带 tool_use 的 assistant 消息;预期同一轮没有 thinking 或 reasoning 字段。

据讨论里的源码核对,llm-deepseek/src/serialize.ts:24-37 仅当持久消息已有 reasoning 块时才输出 thinking;本地 precheck :131-146 只校验 tool 配对,不看 thinking,所以问题漏到服务端才暴露(#10077)。模型切换的常规配置可以对照 模型 provider 配置。

历史是怎么被 DSH 混合路由投毒的

一句话结论:某几轮在关闭思考的模型上生成,持久消息里没有 reasoning;切回 deepseek-official 后,序列化器按「没有就不补」处理,服务端要求「有 tool_use 就必须有 thinking」,冲突就此产生。

触发链通常是这样:

  • 会话先在 provider A 上跑,A 不支持或关闭了思考,assistant 消息只有 tool_use。
  • 切到 deepseek-official,历史被原样带过去。
  • 序列化器看到这些轮次没有 reasoning,不输出 thinking。
  • 服务端检查到 tool_use 缺少配套 thinking,返回 400。
  • 之后的每条消息都带着这段历史,所以每次都失败。

压缩不是必要条件,但压缩后更容易丢 reasoning,属于加重因素。

DeepSeek Harness thinking 缺失的三种修法

一句话结论:要保留旧会话就装 guard 插件,能改代码就补序列化器,旧会话价值不高就迁到新会话。

修法一:用 thinking-roundtrip-guard 做合成或降级

这是不改核心、可配置粒度最细的做法,插件提供 synthesize、degrade、observe 三档,适合生产环境先恢复。

  1. 安装 @argszero/dsh-thinking-roundtrip-guard;预期状态为 active。
  2. 先切到 observe 模式,重放旧会话;预期日志里列出缺失 thinking 的轮次。
  3. 改成 synthesize 模式,让插件为缺失轮次补一个最小 thinking 块;预期下一轮请求不再 400。
  4. 再发一条消息;预期仍 200,如果模型行为异常,改用 degrade 模式让该轮不走 thinking 回传。
  5. 观察几轮后再决定长期保留哪一档;预期没有新的 400,回答质量可接受。

三档的选择:observe 只记录不修改,用来确认范围;synthesize 补占位 thinking,兼容性最好,但占位内容不参与真实推理;degrade 把该轮降级为普通消息,可能改变模型对历史的理解。

修法二:改 serialize.ts,在输出 tool_use 前补块

根治点在序列化器,让「带 tool_use 的轮次」在缺少 reasoning 时主动合成一个空 thinking,而不是静默跳过。 适合维护 fork 或提交上游。

  1. 在 serialize.ts 输出 assistant 消息前,检查该轮是否包含 tool_use;预期能识别出所有工具调用轮次。
  2. 如果有 tool_use 且没有 reasoning,插入一个最小 thinking 块(空文本或一句占位);预期每个工具轮次都带 thinking。
  3. 确保插入位置在 tool_use 之前,顺序符合服务端要求;预期请求能通过校验。
  4. 补测试:有 reasoning、无 reasoning、混合三种历史各一条;预期三种都能正确序列化。
  5. 重新构建,用一个跨 provider 的旧会话验证;预期不再需要插件。

合成块会进入后续请求,可能轻微影响模型判断,占位内容越短越好;如果服务端校验的是 thinking 签名而不是存在性,合成可能仍被拒;改动在适配器层,升级后可能被覆盖。

修法三:新建会话,迁移关键上下文

不想装插件也不想改代码时,把旧会话的结论迁到新会话,绕开被投毒的历史。 代价是丢掉工具调用链。

  1. 在旧会话里导出最近几轮的关键结论、文件路径和待办;预期工作状态以文字保留。
  2. 新建会话,把背景粘进去,明确当前模型是 deepseek-official;预期没有继承旧工具轮次。
  3. 先发一条纯文本消息,再发一条带工具的请求;预期两次都返回 200。
  4. 把旧会话归档;预期以后不会误点进去继续失败。

DSH 插件修复后的验证与避坑

一句话结论:一次 200 不算修好,必须在旧会话、provider 往返和新建会话三条路径上都不再 400。 验证步骤:

  1. 用一个「先 A 模型、后 deepseek-official」的旧会话发三条消息;预期都不再 400。
  2. 检查请求体,确认每个带 tool_use 的轮次都有 thinking;预期没有缺口。
  3. 切回 A 模型再切回来;预期历史没有被二次破坏。
  4. 新建会话做一次对照;预期修复没有影响正常请求。

避坑要点:

  • 不要在 400 后反复重发,历史不变,结果不会变。
  • 压缩前先确认 thinking 是否会被保留,否则修好一次又会被压缩打回原形。
  • 合成 thinking 不等于恢复原始推理,它只解决协议兼容,不恢复模型当时的思路。
  • 讨论里没有官方修复版本,0.2.1-rc.2 仍可复现;升级后以 release notes 为准,别把某个 DSH插件 的旧行为当成已修。

DeepSeek Harness thinking 回传常见问题

Q:DeepSeek Harness 切回 deepseek-official 就 400,新建会话却正常,为什么? A:DeepSeek Harness 切回 deepseek-official 后 400,是因为旧会话有些轮次在关闭思考的模型上生成,历史没有 thinking 块。deepseek-official 要求工具调用轮次把 thinking 原样带回,本地序列化只在历史已有 reasoning 时才输出,于是服务端判定缺块并 400;新建会话没有这段历史。

Q:DeepSeek Harness 的 thinking 块为什么会丢,是压缩把 reasoning 删了吗? A:DeepSeek Harness 的 thinking 块丢失不一定是压缩,更常见的是混合路由:某一轮用不支持或关闭了思考的 provider 生成,持久消息里本来就没有 reasoning。回到 deepseek-official 时序列化器不会凭空合成,历史就变成「有 tool_use、没有 thinking」。

Q:不想放弃旧会话,DeepSeek Harness 里怎么把缺失的 thinking 补回去? A:DeepSeek Harness 的旧会话可以装 thinking-roundtrip-guard 插件,在 llm/stream 层对缺失轮次做 synthesize 或 degrade;也可以改 serialize.ts,在输出 tool_use 前检查同轮是否有 reasoning。两种做法都要先备份会话,并用一条短消息验证不再 400。

Q:DeepSeek Harness 的 400 是本地报的还是服务端返回的,为什么本地没拦住? A:DeepSeek Harness 的这类 400 来自服务端,本地 precheck 主要校验 tool_use 与 tool_result 的配对,不看 thinking 是否齐全。要提前发现,需要在发送前对「带 tool_use 且 provider 要求 thinking 回传」的历史做一次显式校验。

常见问题

DeepSeek Harness 切回 deepseek-official 就 400,新建会话却正常,为什么?

DeepSeek Harness 切回 deepseek-official 后 400,是因为旧会话有些轮次在关闭思考的模型上生成,历史没有 thinking 块。deepseek-official 要求工具调用轮次把 thinking 原样带回,本地序列化只在历史已有 reasoning 时才输出,于是服务端判定缺块并 400;新建会话没有这段历史。

DeepSeek Harness 的 thinking 块为什么会丢,是压缩把 reasoning 删了吗?

DeepSeek Harness 的 thinking 块丢失不一定是压缩,更常见的是混合路由:某一轮用不支持或关闭了思考的 provider 生成,持久消息里本来就没有 reasoning。回到 deepseek-official 时序列化器不会凭空合成,历史就变成「有 tool_use、没有 thinking」。

不想放弃旧会话,DeepSeek Harness 里怎么把缺失的 thinking 补回去?

DeepSeek Harness 的旧会话可以装 thinking-roundtrip-guard 插件,在 llm/stream 层对缺失轮次做 synthesize 或 degrade;也可以改 serialize.ts,在输出 tool_use 前检查同轮是否有 reasoning。两种做法都要先备份会话,并用一条短消息验证不再 400。

DeepSeek Harness 的 400 是本地报的还是服务端返回的,为什么本地没拦住?

DeepSeek Harness 的这类 400 来自服务端,本地 precheck 主要校验 tool_use 与 tool_result 的配对,不看 thinking 是否齐全。要提前发现,需要在发送前对「带 tool_use 且 provider 要求 thinking 回传」的历史做一次显式校验。

相关术语

thinking 块
thinking 块是 DeepSeek 模型返回的思考内容,在工具调用轮次里需要随历史一起回传。服务端用它保持推理连续性,缺失时会拒绝请求。— https://github.com/deepseek-ai/deepseek-harness/discussions/10077
serialize.ts
serialize.ts 是 llm-deepseek 适配器把持久消息转换成请求体的模块,负责决定哪些 reasoning、tool_use、tool_result 进入本次请求。它只在历史已有 reasoning 时输出 thinking,不会主动合成。— https://github.com/deepseek-ai/deepseek-harness/discussions/10077
混合路由
混合路由是同一会话在不同 provider 或模型之间切换的做法。不同模型对 thinking 回传的要求不一致,切换后历史消息的字段完整性容易出问题。— https://github.com/deepseek-ai/deepseek-harness/discussions/10077

来源