dsh-messages-sanitizer:自動修復 DeepSeek Harness 會話裏的孤兒 tool_calls

前言

在 DeepSeek Harness 裏開發或加載插件時,一次工具調度崩潰(例如 Cannot read properties of undefined (reading 'prepare'))就會在會話日誌裏留下孤兒 tool_calls。之後每一輪請求都被 400 INVALID_REQUEST 拒絕,重試無效,會話卡死,只能放棄這個會話。

下面介紹的 dsh-messages-sanitizer 就是爲這個問題準備的:它在運行時自動把 messages 數組修回合法,讓對話繼續。

問題是怎麼發生的

OpenAI 兼容協議要求工具調用成對出現,且 tool 消息必須緊鄰其 assistant tool_calls 消息:

assistant  { content: ..., tool_calls: [{ id: "call_A", ... }] }
tool       { tool_call_id: "call_A", ... }   ← 必須緊跟,覆蓋每個 id

當一次工具調度在「記錄 assistant tool_calls / tool/call 之後、產出 tool 結果之前」崩潰時,會話日誌會留下一個沒有 tool 消息響應的孤兒 tool_calls。下一輪請求拼出的歷史是這樣的:

[..., assistant{tool_calls:[write]}, user{...}]        ← 非法

API 直接返回 400 INVALID_REQUEST,且重試時歷史原封不動,反覆被拒。如果崩潰後還有多次失敗重試,日誌裏還會留下多條重複的 user 消息,橫在孤兒 assistant 與注入點之間——這時連「補插 tool 消息」也無法滿足緊鄰約束。

這是什麼

dsh-messages-sanitizer 是一個 DeepSeek Harness 插件,作者 Leeminjing,MIT 許可證,當前版本 0.1.1。一句話定位:自動修復 messages 數組中無效的 tool_calls/tool 消息配對(孤兒 tool_calls),阻止 400 INVALID_REQUEST 導致的會話卡死。

同類故障在 DeepSeek Harness 官方倉庫也有跟蹤:Discussion #4843 描述了會話歷史中存在無配對 result、或 id/name/arguments 殘缺的 tool_calls 時 chat-completions 接口返回 400 的問題,官方從 harness 源碼層給出根因修復。本插件與那個修復是互補而非替代:它不修改 DSH 源碼,只在運行時自動矯正 messages 數組,相當於一個安全網。已污染的舊會話、或跑在仍帶該 bug / 工具調度崩潰的 harness 版本上的用戶,都能被自動救回並繼續對話,而不必等待 harness 發版。

四層修復機制

插件按處理時機分四層工作。

1、自動續跑(agent/status,主路徑)

工具調度崩潰後,趁 agent 回到 idle 時,把合成 error tool-result 送回報文箱並喚醒它。錯誤會原樣上報給 LLM,下一步換工具、重試還是向用戶報告,由 LLM 自己決定。這保證 AI 消息始終是最後一條,對話不卡死。

2、預防(agent/pre-step)

追蹤每個會話中「已聲明但從未被 tool/result 響應」的調用;下一輪請求構建前,把合成 error tool-result 消息插到該步消息最前面(覆蓋重啓後恢復的舊孤兒)。合成消息隨 decision.messages 以 user/message 事件落盤,deriveMessages() 從根上恢復合法,從源頭杜絕 400。

3、治癒(agent/request-error)

如果 API 仍因 tool_calls 配對/緊鄰違規返回 400(例如舊版本已污染的會話、或孤兒 assistant 後面已橫着過期消息),插件用 surface 替換完成修復,然後強制重試一次(重試基於修復後的日誌重建請求)。具體動作:

  1. 把懸空 assistant 消息改寫成不含 tool_calls 的版本(剝離無響應的調用);
  2. 把孤兒 tool 消息中和成純文本 user 消息;
  3. 恢復被誤剝但結果仍緊鄰的 assistant(還原 tool_calls,保留歷史工具上下文);
  4. 摺疊崩潰重試留下的重複 user 消息。

修復是冪等的:第二次遇到同一違規時無事可做,自然回退下游策略,不會無限重試。

4、兜底(llm/stream)

對每個請求做純數組矯正,包括配對、緊鄰重排、孤兒/重複丟棄、空 assistant 丟棄。循環構建的請求是凍結的,只告警不改寫;compaction、session-title 等自建 messages 的非凍結請求則直接原地替換。

安裝與啓用

先執行安裝命令,再重啓 harness 即生效(插件隨 profile 層棧自動加載):

dsh plugin --profile web add github:Leeminjing/dsh-messages-sanitizer

更新到最新版:

dsh plugin --profile web update dsh-messages-sanitizer

配置項只有一個:enabled(boolean,默認 true),是總開關。停用時,刪除 cordis.patch.yml 中的插入行,或改爲:

- insert:
    - id: messages-sanitizer
      name: 'dsh-messages-sanitizer'
      disabled: true

驗證

插件自帶測試,覆蓋純函數矯正、會話追蹤、請求失敗修復和真實 cordis/Session 集成:

cd dsh-messages-sanitizer
node --test        # 共 40 個用例

測試均用真實 @deepseek-ai/dsh-session 的 foldSurface / Session 校驗,包括真實崩潰序列的端到端模擬、污染日誌修復後的冪等驗證、surface 替換在真實 Session 上執行,以及請求失敗修復只在 tool_calls 配對 400 時干預、修復後強制重試一次且不會無限重試。

適用場景與注意事項

適合兩類人:一是在 DeepSeek Harness 上開發插件、遇到過工具調度崩潰導致會話卡死的開發者;二是手上還有已污染的舊會話想救回的用戶。後者不需要等 harness 發版,治癒路徑會在第一次請求失敗時自動剝離懸空調用並重試成功。

安裝和使用前注意幾點:

  1. 插件以當前 dsh 進程權限運行,安裝前應檢查源碼與許可證(MIT)。
  2. 要求 Node >= 18。插件是零構建的純 ESM,修改插件代碼後無需重新構建,重啓 harness(或讓 cordis HMR 重載)即生效。
  3. 運行期 peer 依賴 @deepseek-ai/cordis、@deepseek-ai/dsh-agent、@deepseek-ai/dsh-llm、@deepseek-ai/dsh-session、@deepseek-ai/schemastery,與 harness 運行時同源。
  4. 倉庫目錄下的 node_modules 是指向 harness 運行時 ~/.dsh/profiles/node_modules 的 junction,僅爲本地 node --test 提供依賴解析,harness 運行期不依賴它。
  5. 它與 DeepSeek Harness 官方 Discussion #4843 的源碼層修復互補而非替代:官方修復在 DSH 源碼里根治,本插件不修改 DSH 源碼、在運行時自動矯正 messages 數組,可救回已污染的舊會話。

結尾

dsh-messages-sanitizer 解決的是一個很具體的死局:一次工具調度崩潰,就讓整個會話再也無法繼續。它通過續跑、預防、治癒、兜底四層機制在運行時把 messages 數組修回合法,且修復冪等、不會引入新的循環問題。插件目錄頁在 https://www.skillhub.cn/plugins/Leeminjing/dsh-messages-sanitizer ,源碼與 README 見 https://github.com/Leeminjing/dsh-messages-sanitizer 。

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜