imrcpan/dsh-retry:给模型请求换一套固定间隔的重试策略

前言

这篇文章介绍一个 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 和不稳定网络调优,失败码覆盖三类:

  1. 账号级失败会被重试。AUTH、QUOTA、ACCOUNT_QUOTA、ACCOUNT_TOKEN_INVALID、INVALID_CREDENTIAL 都在默认重试列表里。
  2. 网关噪声会被重试。Cloudflare 和非代理返回的 5xx 归为 SERVER,包括根本不是合法 JSON 的 HTML 错误页。
  3. 流损坏会被重试。断裂的 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 已够用,没必要换。

几个必须知道的行为:

  1. 插件以当前 dsh 进程的权限运行,能读到什么凭据就会按该进程的授权范围使用。安装前建议先检查 cordis.patch.yml、index.js 和 LICENSE,确认来源再启用。
  2. 重试链计数器放在内存里,按 session 一个。host 重启会结束 turn,不会丢失什么。只有 agent loop 的请求会被重试,直接调用 ctx.llm.stream() 的地方仍然是单次尝试。
  3. tail 是真的不限次数。当上游始终不恢复,turn 会一直停在 retrying 状态,直到被手工停止。
  4. 重试池子里的凭据和配额失败,前提是池子会在下一次 attempt 轮转。如果池子本身永久耗尽,就会每 20s 循环一次。
  5. executor 自己不会轮换账号,它只重新发起请求,所以中转站必须为下一次 attempt 选中另一个上游。
  6. 每一次 attempt 都是一次单独计费的 provider 请求。

结尾

dsh-retry 的价值不在于发明新的重试机制,而在于用一张固定时间表替换指数退避,并把 relay 场景下最常见的三类失败(账号级、网关级、流损坏)纳进重试范围,同时保证不产生重复输出。想直接看源码和配置,可以打开 https://github.com/imrcpan/dsh-retry ,或在 https://www.skillhub.cn/plugins/imrcpan/dsh-retry 查看目录条目。

生态背景补充一句:DSH 的理念是「一切皆插件」,社区目录是独立站点,与 DeepSeek 或幻方没有官方从属关系,使用时以插件自身来源为准。

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

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

小夜