前言¶
在 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 替換完成修復,然後強制重試一次(重試基於修復後的日誌重建請求)。具體動作:
- 把懸空 assistant 消息改寫成不含
tool_calls的版本(剝離無響應的調用); - 把孤兒 tool 消息中和成純文本 user 消息;
- 恢復被誤剝但結果仍緊鄰的 assistant(還原 tool_calls,保留歷史工具上下文);
- 摺疊崩潰重試留下的重複 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 發版,治癒路徑會在第一次請求失敗時自動剝離懸空調用並重試成功。
安裝和使用前注意幾點:
- 插件以當前 dsh 進程權限運行,安裝前應檢查源碼與許可證(MIT)。
- 要求 Node >= 18。插件是零構建的純 ESM,修改插件代碼後無需重新構建,重啓 harness(或讓 cordis HMR 重載)即生效。
- 運行期 peer 依賴
@deepseek-ai/cordis、@deepseek-ai/dsh-agent、@deepseek-ai/dsh-llm、@deepseek-ai/dsh-session、@deepseek-ai/schemastery,與 harness 運行時同源。 - 倉庫目錄下的 node_modules 是指向 harness 運行時
~/.dsh/profiles/node_modules的 junction,僅爲本地node --test提供依賴解析,harness 運行期不依賴它。 - 它與 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 。