dsh-stateful-responses:把全量重放換成增量續傳

前言

DSH 的 agent loop 每一輪都會把整段對話重新交給模型:system instructions、工具定義、歷史消息、工具結果,一個不落。對話變長之後,這一輪的輸入裏大部分是上一輪已經發過的內容——傳輸體積和 input 計費都重複承擔。對於 OpenAI Responses API 這類服務端有狀態的接口,上游本來就保存了上一次 response,客戶端本可以只發新增 item 和 previous_response_id 完成續傳。本文介紹的插件 zhexuany/dsh-stateful-responses 做的就是這件事:在進程內最後一層攔截髮往上游的請求,命中條件時改寫成增量請求,其餘情況原樣放行。

這是什麼

dsh-stateful-responses 是一個 DSH(dsh)bundle 插件,維護者是 zhexuany,分類爲模型推理,協議爲 MIT(LICENSE 見倉庫)。目標運行時是 DSH Harness / dsh 0.2.0-rc.2 及以上,運行環境要求 Node.js >= 22.19.0,無第三方依賴。它解決的問題是:dsh 調用 GPT 模型走 Responses API 時,每一輪都在全量重放會話,插件把這種重放替換爲基於 store + previous_response_id 的增量續傳。

需要說明的是,這是社區插件,收錄在第三方目錄 SkillHub 上,與 DeepSeek / 幻方沒有官方從屬關係。

它解決什麼問題

Responses API 的 continuation 機制前提是客戶端只發送新增 item 和上一輪響應 id。dsh 在這一層存在缺口:

  • llm-pi-ai 的 api: openai-responses 適配只做協議轉換,沒有接 continuation;
  • provider 註冊是獨佔的,插件無法從 provider 層接管;
  • 請求擴展只能往請求體里加字段,而「不再重放歷史」是會話循環的行爲,不是單個字段。

因此插件把改寫點放在進程內最後一層:globalThis.fetch。架構上是這樣一條鏈:

agent loop
  └── llm-pi-ai  (api: openai-responses)
        └── OpenAI SDK
              └── globalThis.fetch   ← dsh-stateful-responses 在這裏攔截
                    └── upstream  POST /v1/responses

內部模塊分工:index.js 是入口(name + apply(ctx, config),負責安裝/卸載攔截器);lib/interceptor.js 負責命中判定、請求改寫、響應追蹤、回退與降級;lib/chain.js 是有界 LRU,保存會話鏈;lib/items.js 負責 item 規範化與哈希;lib/sse.js 收集 SSE 終止事件;lib/config.js 做嚴格配置校驗;cordis.patch.yml 是 bundle patch,用 insert 行掛載插件。

選擇 fetch 層而不是 adapter 層的原因,README 裏給了四點:pi-ai 的 OpenAI SDK 每次請求都用全局 fetch 且 body 是 JSON 字符串,這是穩定的調用契約;不需要了解 pi-ai 內部實現,也不綁定其版本;插件卸載時可以完整恢復原 fetch;不改任何 provider 配置,安裝和回滾只是增刪一個 bundle。

工作原理

下面介紹插件運行時的判定與改寫邏輯,這部分來自 README。

命中條件。 只有同時滿足才處理,否則原樣放行:方法爲 POST;URL 以 /responses 結尾且命中配置的 baseURLs;body 是 JSON 字符串且含字符串 model 與數組 input;model 命中配置的 models(支持結尾 * 通配);調用方沒有自帶 previous_response_id。

會話鏈。 鏈的鍵由請求的穩定要素組成:

chain key = url + model + prompt_cache_key + hash(tools) + hash(instructions)

不同模型、工具集、system instructions 會自然分鏈;prompt_cache_key(dsh 會把會話 id 傳給 provider)讓不同會話不串鏈。鏈存放在有界 LRU 中(maxChains,默認 32),超出後淘汰最久未使用的鏈。

item 身份與前綴驗證。 每個 input/output item 先規範化再取 sha256,規範化會剔除上游可重新生成的易變字段:id、status、created_at、completed_at、annotations、logprobs、obfuscation。這些字段隻影響比較,真正發出的是未經修改的 delta 原文。只有當本次請求的 item 序列是鏈中序列的嚴格前綴擴展(前 N 項哈希逐一相同,且至少多出 1 項)才允許續傳;否則說明會話分叉、被編輯或換了上下文,插件丟棄該鏈並按全量發送。

改寫。 命中時把 body 改成等價的增量請求:

其餘字段(tools、instructions、reasoning 參數、採樣參數等)保持不變。

響應追蹤與鏈更新。 流式(SSE)時對 response.body 做 tee(),一份交給 pi-ai,另一份給內部收集器解析終止事件;非流式時通過 response.clone().json() 讀取。無論哪種形式,插件都不會修改模型返回給 dsh 的字節內容。只有響應滿足 store === true 且帶非空 id 時才更新鏈,鏈上的 items 由上游返回的 output 追加而成。

失敗與降級。 行爲如下表:

情況 行爲
完成響應 store !== true 該 baseURL 標記爲不支持,清空全部鏈,之後永遠全量(每個 baseURL 只提示一次)
響應 failed / cancelled 只丟棄當前鏈,不標記不支持
續傳請求非 2xx,且錯誤文本匹配 previous_response_id / previous response / not found 丟棄鏈,用原始全量 body 重試一次
續傳請求網絡錯誤(非用戶取消) 丟棄鏈,用原始全量 body 重試一次
解析、規劃、追蹤等內部異常 放行原請求,不打斷模型調用

安裝與啓用

插件是一個標準 dsh bundle。README 給出的安裝方式如下,先做前兩步:

  1. 把倉庫放到任意目錄,例如 ~/dev/dsh-stateful-responses;
  2. 在目標 profile(~/.dsh/profiles/<name>/package.json)中添加依賴與 bundle:
{
  "dependencies": {
    "dsh-stateful-responses": "link:/home/you/dev/dsh-stateful-responses"
  },
  "dsh": {
    "profile": {
      "bundles": ["…", "dsh-stateful-responses"]
    }
  }
}
  1. 在該 profile 目錄執行 pnpm install;
  2. 重啓 dsh(bundle 列表在啓動時讀取)。

配置

配置項與默認值如下表。插件默認不限定 baseURL 與模型(空數組 = 全部命中),生產使用時建議把 baseURLs / models 收窄到真正支持 Responses 狀態的上游與模型,避免向其它端點發送 store: true。

鍵 類型 默認 說明
enabled boolean true 關閉後不安裝攔截器
forceStore boolean true 改寫時強制 store: true
baseURLs string[] [] 只攔截這些 URL 前綴下的 /responses;空 = 全部
models string[] [] 只攔截這些模型 id;支持結尾 * 通配;空 = 全部
maxChains positive integer 32 最多保留多少條會話鏈
debug boolean false 輸出每次改寫 / 回退的決策日誌

在 cordis.patch.yml 中的示例:

- id: stateful-responses
  name: dsh-stateful-responses
  config:
    enabled: true
    forceStore: true
    baseURLs: ["https://api.openai.com/v1"]
    models: ["gpt-*"]
    maxChains: 32
    debug: false

配置是嚴格的:未知鍵或類型錯誤會在激活時輸出 warn,插件不會啓用,而不是靜默忽略。

排錯與可觀測性

debug: true 時會輸出四類決策日誌:rewriting request(本輪改寫爲增量)、chain updated(鏈已推進)、continuation rejected by upstream; retrying once with full history(續傳被拒並已回退)、response did not complete; chain dropped(失敗響應導致鏈丟棄)。每個 baseURL 只會出現一次的 info 級提示:

upstream does not retain responses (store is not true); using full history from now on

啓用時輸出 stateful Responses API interceptor installed;卸載時輸出 restoring global fetch 以及本次進程累計統計:requests / continuations / deltaItems / fullItems / fallbacks / unsupportedBases / errors / chains。沒有命中任何條件時,插件不產生日誌,也不改變請求。

適用場景與注意

適合的場景:dsh 通過 OpenAI Responses 協議(POST …/responses)調用支持 store + previous_response_id 的上游,且希望減少每輪全量重放帶來的傳輸與 input 計費。README 明確兩點邊界:僅覆蓋 OpenAI Responses 協議,llm-deepseek 使用的 Messages 協議沒有等價的 continuation 字段,不在覆蓋範圍內;上游必須能接受它自己返回過的 output item(包括 reasoning item 的 encrypted_content)在下一次請求中被帶回,否則前綴校驗失敗,插件退回全量,安全但沒有收益。

關於最壞情況,插件保證不會丟上下文:鏈只在上游返回過 store: true 且有 id 之後才存在;發出的 input 是本次請求比鏈多出來的原文;任何一環不成立就發全量,行爲與未安裝插件完全一致。插件不讀取、不緩存、不轉發任何憑據,鑑權頭仍由 OpenAI SDK 生成並原樣發送;日誌只包含模型名、item 數量、response id 等元數據。

部署時需要留意的權限問題:插件運行在當前 dsh 進程內,以該進程的權限訪問網絡與文件,安裝前應自行檢查源碼與許可證(MIT)。它只包裝 fetch,不引入外部服務,也不改變 provider 配置。

結尾

這個插件的價值在於把每輪全量重放換成增量續傳,同時把「不確定就回退」作爲硬約束,使最壞行爲與未安裝時一致。目錄頁在 https://www.skillhub.cn/plugins/zhexuany/dsh-stateful-responses ,源碼與 README 在 https://github.com/zhexuany/dsh-stateful-responses 。

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

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

小夜