前言¶
這篇文章介紹一個 DeepSeek Harness 插件,叫 dsh-retry,維護者是 imrcpan。它解決的是智能體開發中一個很具體的問題:模型請求算不上貴,但不穩定,尤其是走中轉站(relay)或共享上游池的時候,一次 401 或限流就把整個 turn 帶崩了。
DeepSeek Harness 本身在 agent step 邊界上有重試能力,自帶的 executor 叫 @deepseek-ai/dsh-llm-retry,它只暴露指數退避:initialDelayMs、maxDelayMs、jitterRatio。這套曲線對中轉場景並不友好——等一次 401,可能要退避到幾十秒,而下一次請求換一個上游賬號很可能就成功了。dsh-retry 就是替換掉這條曲線,改用一張固定的時間表。
這是什麼¶
一句話定位:dsh-retry 是一個 DSH 插件,它替換系統內置的重試 executor,把模型請求的重試間隔從指數退避改成固定調度,並針對中轉端點、共享賬號池和不穩定網絡做了適配。
維護者:imrcpan。分類:模型推理。許可證:MIT。版本:0.1.0。倉庫:https://github.com/imrcpan/dsh-retry ,目錄頁:https://www.skillhub.cn/plugins/imrcpan/dsh-retry 。
下面是它的重試等待時間表:
| 第幾次重試 | 1 | 2 | 3 | 4 | 5 | 6 及以後 |
|---|---|---|---|---|---|---|
| 等待時間 | 2s | 5s | 5s | 5s | 10s | 20s,不限次數 |
前 5 次由 schedule 數組控制,之後統一走 tailDelayMs,每次 20s,不設上限。相比指數退避,這條曲線對短暫抖動反應更快,對永久失敗的上游也只是每 20s 重試一次。
核心功能¶
固定間隔的重試調度¶
原 executor 使用指數退避,等待時間會不斷翻倍直到上限。dsh-retry 改成顯式調度:2s、5s、5s、5s、10s,之後每 20s 重試一次且不設上限。理由是中轉站的失敗是按賬號發生的,不是按請求發生的:某個後端返回 401 或配額耗盡,下一個 attempt 換到另一個賬號很可能成功,所以等待應該短而固定,而不是越爬越高。
針對中轉端點和不穩定上游的失敗分類¶
README 明確說明這個 executor 針對 relay endpoint 和不穩定網絡調優,失敗碼覆蓋三類:
- 賬號級失敗會被重試。
AUTH、QUOTA、ACCOUNT_QUOTA、ACCOUNT_TOKEN_INVALID、INVALID_CREDENTIAL都在默認重試列表裏。 - 網關噪聲會被重試。Cloudflare 和非代理返回的 5xx 歸爲
SERVER,包括根本不是合法 JSON 的 HTML 錯誤頁。 - 流損壞會被重試。斷裂的 SSE 幀歸爲
MALFORMED_RESPONSE。
另外 HTTP_* 是前綴規則,覆蓋所有未分類的 4xx,所以 HTTP_404 這類錯誤會被重試而不是直接結束 turn。
重試不產生重複輸出¶
被重試的 step 在同一個開放 turn 內、針對同一份 durable history 重新執行。模型看不到重試,它只收到被重試 step 重建後的請求,所以不會有半截答案被重複寫一遍。
事件與界面¶
每次計劃中的重試會追加一條 durable 的 llm/retry 事件,等待結束後追加 llm/retry-started。Chat 界面會渲染形如 Retrying model request (2/5) . 4s 的一行,可展開查看失敗原因和延遲;Trajectory 面板會列出 attempt、錯誤碼和延遲。第 6 次及以後的重試會以 unlimited 形式展示。沒有 toast 彈窗,失敗嘗試的中間輸出不會被保留。
安裝與啓用¶
插件是一個 DSH bundle:package.json 裏聲明瞭 dsh.bundle.patch,等於 ./cordis.patch.yml。標準安裝會自動掛載新的 executor,並把內置的 llm-retry 下線,不需要手寫 patch。
安裝命令(來自 README):
dsh plugin --profile web add github:imrcpan/dsh-retry
裝完要重啓 profile,因爲組合關係是在啓動時讀取的。重啓後這一行會出現在 Plugins 下,其中的 config 可以編輯。
安裝後插件做了什麼¶
掛載的 layer 插入 dsh-retry executor 並禁用 llm-retry:
- insert:
- id: dsh-retry
name: "dsh-retry"
config:
schedule: [2000, 5000, 5000, 5000, 10000]
tailDelayMs: 20000
retryableCodes: [ ... ] # 完整列表見 cordis.patch.yml
- id: llm-retry
disabled: true
禁用 llm-retry 這一步很關鍵:兩個掛載的 executor 會爭搶同一個失敗,並且各自寫重試鏈狀態。如果 profile 覆蓋了這個設置,要保持它處於 disabled。
從源碼目錄安裝¶
如果用的是 checkout,需要作爲 bundle 安裝,這樣 manifest 纔會生效:
dsh plugin --profile web add /absolute/path/to/dsh-retry
也接受普通的 file: 依賴,但那樣不會應用 bundle layer,需要手工把上面那段 patch 複製到 profile 的 cordis.patch.yml:
{
"dependencies": {
"dsh-retry": "file:./plugins/dsh-retry"
}
}
配置¶
config 下有三個鍵:
| key | 默認值 | 含義 |
|---|---|---|
schedule |
[2000, 5000, 5000, 5000, 10000] |
第 1..n 次重試前的等待毫秒數;數組長度就是有上限的頭部 |
tailDelayMs |
20000 |
schedule 用完之後每次重試的等待 |
retryableCodes |
見下 | 值得重試的失敗碼 |
retryableCodes 裏以 * 結尾的條目是前綴規則。HTTP_* 會命中 HTTP_404、HTTP_409、HTTP_422 以及其他未分類狀態碼。裸 * 會被拒絕,因爲它會命中所有失敗。
默認會重試的失敗碼¶
| code | 典型成因 |
|---|---|
EMPTY_RESPONSE |
響應完成但沒有內容塊 |
RATE_LIMIT |
HTTP 429 或 provider 的限流錯誤 |
SERVER |
HTTP 5xx,包括網關和 Cloudflare 的 5xx |
TIMEOUT |
請求或流超時 |
TRANSPORT |
連接重置、socket 錯誤、流被截斷 |
PI_AI_ERROR |
pi-ai adapter 無法分類的上游錯誤 |
AUTH |
HTTP 401 或 403;走共享賬號池時換一個賬號可能成功 |
QUOTA、ACCOUNT_QUOTA |
餘額或賬號配額耗盡 |
ACCOUNT_TOKEN_INVALID |
已存賬號 token 被 401 拒絕 |
INVALID_CREDENTIAL |
池子裏格式錯誤的憑據條目 |
MALFORMED_RESPONSE |
SSE 幀損壞或事件類型不匹配 |
HTTP_* |
未分類的 4xx,例如 HTTP_404、HTTP_409、HTTP_422 |
默認不重試的失敗碼¶
| code | 原因 |
|---|---|
CONTEXT_WINDOW_EXCEEDED |
compaction 負責恢復,重試會形成循環 |
INVALID_REQUEST |
請求本身錯了,換賬號也救不了 |
NO_ADAPTER、UNSUPPORTED_REASONING_EFFORT |
配置錯誤 |
MISSING_CREDENTIAL |
該路由沒有配置憑據 |
ACCOUNT_SIGN_IN_REQUIRED |
需要人工登錄 |
ABORTED |
取消必須保持終態 |
UNKNOWN |
外部錯誤的兜底 code,無上限重試不安全 |
如果部署環境的判斷和默認值不一樣,可以把這些 code 加進 retryableCodes。
適用場景與注意¶
適合誰:使用中轉站、共享賬號池,或上游明顯不穩定的 DSH 部署。這類失敗是按賬號分散的隨機噪聲,固定短間隔重試收益最大。如果走的是穩定直連,內置 executor 已夠用,沒必要換。
幾個必須知道的行爲:
- 插件以當前 dsh 進程的權限運行,能讀到什麼憑據就會按該進程的授權範圍使用。安裝前建議先檢查
cordis.patch.yml、index.js和 LICENSE,確認來源再啓用。 - 重試鏈計數器放在內存裏,按 session 一個。host 重啓會結束 turn,不會丟失什麼。只有 agent loop 的請求會被重試,直接調用
ctx.llm.stream()的地方仍然是單次嘗試。 - tail 是真的不限次數。當上遊始終不恢復,turn 會一直停在 retrying 狀態,直到被手工停止。
- 重試池子裏的憑據和配額失敗,前提是池子會在下一次 attempt 輪轉。如果池子本身永久耗盡,就會每 20s 循環一次。
- executor 自己不會輪換賬號,它只重新發起請求,所以中轉站必須爲下一次 attempt 選中另一個上游。
- 每一次 attempt 都是一次單獨計費的 provider 請求。
結尾¶
dsh-retry 的價值不在於發明新的重試機制,而在於用一張固定時間表替換指數退避,並把 relay 場景下最常見的三類失敗(賬號級、網關級、流損壞)納進重試範圍,同時保證不產生重複輸出。想直接看源碼和配置,可以打開 https://github.com/imrcpan/dsh-retry ,或在 https://www.skillhub.cn/plugins/imrcpan/dsh-retry 查看目錄條目。
生態背景補充一句:DSH 的理念是「一切皆插件」,社區目錄是獨立站點,與 DeepSeek 或幻方沒有官方從屬關係,使用時以插件自身來源爲準。