前言¶
在 DeepSeek Harness(DSH)里走 OpenAI 兼容网关时,遇到 429 Too Many Requests 并不罕见。网关往往在响应头里写明应等待多久,例如 retry-after: 20。DSH 侧的 dsh-llm-retry 本来支持读取 failure.providerRetryAfterMs,官方 DeepSeek 传输也会从响应头填充该字段;但经 llm-pi-ai(OpenCode、OpenRouter、vLLM、LiteLLM 等)时,@earendil-works/pi-ai 会把错误重抛为普通 Error,状态码与头信息丢失,dsh-llm-retry 只能退回本地退避(默认 initialDelayMs 500 ms,指数加倍并加抖动)。网关要求等 20 秒,客户端却可能在 500 ms 后再次请求,容易把软限流打成硬限流。
下面介绍维护者 PandaAQI 的插件 dsh-retry-after:在 fetch 边缘抓取响应头,再写回失败对象,让既有重试策略按服务商给出的时间等待,而不是猜退避间隔。
这是什么¶
dsh-retry-after 是 DSH 的 Cordis 插件,分类上归于模型推理相关能力。它不实现第二套重试循环,而是在 agent/request-error 上作为前置监听器,把已在网络层捕获的 HTTP 状态与 Retry-After / Retry-After-Ms 填回 failure,供 dsh-llm-retry 直接使用。重试预算、抖动、取消、llm/retry 事件及各 provider 的 retryPolicy 仍由 dsh-llm-retry 负责。
- 维护者:PandaAQI
- 目录页:https://www.skillhub.cn/plugins/PandaAQI/dsh-retry-after
- 源码:https://github.com/PandaAQI/dsh-retry-after
- 许可证:MIT(见仓库)
- 当前 package 版本:0.1.0;要求 Node.js
>=20,DSH>=0.1.1-rc.1
社区目录 SkillHub 与 DeepSeek / 幻方无官方从属关系;安装插件等于在你本机 DSH 配置中增加代码路径,应以仓库源码与许可证自行核对后再启用。
问题与数据流¶
无插件时,典型链路如下:
@earendil-works/pi-ai捕获服务商错误后new Error(...)重抛,状态与头被丢弃。dsh-llm-pi-ai用classifyPiAiError对错误字符串做模式匹配。dsh-llm-retry拿不到时间提示,使用本地退避。
有插件时,在头信息被丢弃之前的 fetch 边缘记录 (status, retry-after) 到环形缓冲区;dsh-agent-loop 的 agent/request-error 上,前置监听器把捕获结果写回失败对象:
| 捕获事实 | 写入字段 | 作用 |
|---|---|---|
| 响应状态 | failure.status |
诊断;不依赖网关正文语言 |
Retry-After / Retry-After-Ms |
failure.providerRetryAfterMs |
与 dsh-llm-retry 已有逻辑对齐 |
| 修正后的错误码 | failure.code |
避免仅含本地化 429 正文时被标为 PI_AI_ERROR 而无法进入重试列表 |
仓库内 mock 网关测试(retry-after: 2,连续 429 三次后成功)对比:无插件时第 1→2、2→3 次尝试间隔约 500 ms、1000 ms;有插件时两次间隔均约 2030 ms,与网关要求一致。
安装与启用¶
npm 包可用时,对指定 profile 安装并重启:
dsh plugin --profile tui add dsh-retry-after
包尚未上架 npm 时,README 给出的安装方式为:
dsh plugin --profile tui add github:PandaAQI/dsh-retry-after
安装后重启该 profile。bundle 中的 patch 会自动注册 retry-after 行,一般无需额外配置。确认是否已编入配置:
dsh --profile tui --dump-config | grep -A6 retry-after
典型用法:本地复现退避差异¶
README 提供可重复运行的演示:先起 mock 网关,再用 patch 启动 headless:
node test/mock-gateway.mjs & # 429 + retry-after: 2,连续三次
DSH_HOME=/tmp/dsh-retry-after-demo \
dsh --profile demo --patch ./test/mock.patch.yml headless "say hi"
上述命令需在克隆或下载后的插件仓库目录中执行;正文不复述 git clone,读者从 GitHub 获取源码后按 README 路径操作即可。
可选配置¶
配置项写在 profile 的 cordis.patch.yml 中 id: retry-after 的 config 下,均可选。默认值在多数场景下即可使用:
- id: retry-after
config:
windowMs: 15000 # 捕获的响应在多长时间内可用于匹配
reclassify: true # 用捕获的状态修正 PI_AI_ERROR/UNKNOWN
oversize: clamp # 请求等待超过 maxDelayMs 时的行为
matchUnmatched: true # 无 baseURL 的 provider 可借用同 origin 的失败记录
floorMsByCode: {} # 无头时的按码下限,如 { RATE_LIMIT: 5000 }
debug: false # 是否记录每次注解
oversize 与上游 dsh-llm-retry 的 maxDelayMs 交互:
clamp(默认):等待不超过maxDelayMs,继续重试。honor:透传服务商数字;过大时按上游设计结束重试。ignore:丢弃提示,仍用本地退避。
对没有 Retry-After 的 429,可为 RATE_LIMIT 等设置 floorMsByCode 下限,避免本地退避过短反复撞网关。
适用场景与注意¶
适合: 主要通过 llm-pi-ai 访问 OpenAI 兼容网关,且经常遇到 429/503 并带 Retry-After 的 DSH 用户;希望重试间隔与网关声明一致,而不是固定 500 ms 起跳的本地策略。
不必安装: deepseek-official 等原生线路已填充 providerRetryAfterMs,插件在该路径上通常为 no-op。
局限(摘自 README,部署前应知晓):
- 失败与捕获靠 origin(scheme + host + port)与 时间窗口 启发式关联,同一 origin 上并发或短时多失败可能错配;单 provider 单 origin 时通常准确。
- 并行子 agent 共享环形缓冲,可能用「较新一条」注解另一条失败,代价主要是等待时间非最优,而非完全错误的重试次数。
- 失败对象不可扩展时插件会克隆并替换
payload.failure;若未来 harness 冻结整个 payload,插件可能退化为无操作(debug会说明)。 - 仅适用于基于
fetch的传输;WebSocket 线路无 HTTP 响应可观测。 - 遵守
Retry-After拉长每次重试间隔,不能减少触发 429 的那次请求;需配合合理的上下文与调用频率。
若需要更多重试次数或不同策略,可考虑 dsh-retry-boost;需要切换模型或服务商时,可考虑 dsh-llm-fallbacks。上游在 pi-ai SDK 内保留头信息的讨论见 README 中的 pi / harness 链接。
结尾¶
dsh-retry-after 解决的是 pi-ai 线路上 HTTP Retry-After 被吞掉、导致 dsh-llm-retry 误用短间隔本地退避的问题:在 fetch 边缘捕获、在 agent/request-error 写回,与现有重试插件协作而非替代。安装命令以 README 为准;启用前请审阅源码与 MIT 许可证,并知悉插件以当前 DSH 进程权限运行。
- 社区目录:https://www.skillhub.cn/plugins/PandaAQI/dsh-retry-after
- GitHub:https://github.com/PandaAQI/dsh-retry-after