dsh-retry-after:在 pi-ai 线路上把 Retry-After 交还给 DSH 重试策略

前言

在 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 配置中增加代码路径,应以仓库源码与许可证自行核对后再启用。

问题与数据流

无插件时,典型链路如下:

  1. @earendil-works/pi-ai 捕获服务商错误后 new Error(...) 重抛,状态与头被丢弃。
  2. dsh-llm-pi-ai 用 classifyPiAiError 对错误字符串做模式匹配。
  3. 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
羽毛球分组比赛记分
小程序二维码

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

小夜