← Back to home@liaoyuqing

dsh-llm-error-retry

用于在deepseek-harnees中报错时重试大模型请求,支持配置http状态,字段-值命中。

Stars
3
Language
JavaScript
Created
Sep 2, 2026
Updated
Oct 21, 2026
GitHub repo

Introduction

dsh-llm-error-retry

License: MIT latest release ci

大模型请求错误「休眠后重试」——DSH(DeepSeek Harness)Profile Bundle 插件,带 GUI 配置界面。

模型请求失败(如 429 限流)时,按规则命中 → 休眠配置时长 → 自动重新发起请求;每条规则独立配置休眠时长与最大重试次数,耗尽后错误如实上报。重试过程在会话框内以原生重试卡片展示(第 N/M 次 · 休眠 Xs);规则在 GUI「设置」页实时编辑,保存即生效、跨重启持久。

支持按 HTTP 状态码 / 机器码 / 返回字段=值 / msg~子串 四种形态命中报错(含 DashScope/阿里云百炼这类把状态码压平进 message 的适配器形态,以及 provider 只给一句文案、无 status 且 code 只到 PI_AI_ERROR 的形态)。

另外:模型输出达到 token 上限被截断时,自动注入一条「继续」让它接着输出,无需人工打断再发消息。

报错时重试大模型请求:配置错误码或响应字段 → 休眠 → 自动重发。 输出被截断时自动续写:不用手动打「继续」。

安装(一条命令)

dsh plugin --profile web add github:liaoyuqing/dsh-llm-error-retry

然后重启该 profile(bundle 层在启动时装配)。刷新 GUI 页面后,左下角 设置 → LLM 错误重试 即出现配置分区;备用配置页 http://127.0.0.1:3080/llm-error-retry。

发布到 npm 后可用 dsh plugin --profile web add dsh-llm-error-retry。

其他命令:

dsh plugin --profile web update                    # 升级到最新版
dsh plugin --profile web remove dsh-llm-error-retry # 卸载

profile 名按你的实际部署替换(桌面/网页版一般是 web)。安装本质是 pnpm 依赖 + dsh.bundle 声明自动注册为 profile 层,与 dshmarket、dsh-cost-meter 等社区插件一致;本包无构建脚本、零运行时依赖。

手动安装(不经过 pnpm 的兜底方式)

把仓库文件(host.v15.mjs、client.js、package.json)放到任意目录(如 ~/.dsh/plugins/llm-error-retry-ui/),再在 ~/.dsh/cordis.patch.yml 追加:

- insert:
    - id: llm-error-retry
      name: ./plugins/llm-error-retry-ui/host.v15.mjs
      config:
        rules:
          - code: '429'
            delayMs: 60000
            maxRetries: 10

web profile 默认 patchReload: live,保存即热挂载;刷新 GUI 页面加载设置分区(浏览器半由 DSH 的 client-modules 扫描 package.json 的 dsh.client 声明自动并入启动图,零构建)。

规则语法

「错误码 / 条件」支持四种形态,多条规则按顺序取第一条命中;每条规则独立 delayMs + maxRetries:

形态例子匹配目标
HTTP 状态码429、500、503failure.status;failure.code(429 / HTTP_429);message 中的独立数字(词边界,1429 不误伤)——覆盖 pi-ai/DashScope 把状态压平进文本的形态
机器码RATE_LIMIT、QUOTA、SERVER、TRANSPORT、TIMEOUT、EMPTY_RESPONSEfailure.code(忽略大小写,DSH 适配器的 provider 无关分类码)
字段=值code=insufficient_quota、type=rate_limit_exceededprovider 返回 JSON 的字段名与值,三层兜底:① failure.<字段> 标量属性 → ② failure.message 内嵌 {...} JSON 解析 → ③ message 宽松文本 "字段":"值"
msg~子串msg~finish_reason: error、msg~Provider returned an empty responsefailure.message 的子串(忽略大小写)。用于没有 status、code 又被归到通用桶的失败:provider 在 SSE 里以未识别的 finish_reason 终止流时,pi-ai 报成 message='Provider finish_reason: <值>'、code='PI_AI_ERROR',前三种形态都命中不了

约束:delayMs ∈ 1003600000;maxRetries ∈ 01000(0 = 命中即上报)。

行为细节:

  • 监听 agent/request-error waterfall,prepend: true 置于最外层:命中即接管,优先于内置 llm-retry 的快速指数退避;预算耗尽直接上报(不再回退到内置策略);未命中的失败原样放行。
  • 计数按「同一 Agent + 同一请求序列(provider/turn/step)」,新一轮请求自动重置;Agent 销毁即清理。
  • 服务端返回 Retry-After 且比配置更长时按 Retry-After 推迟,但最多只多等 honorRetryAfterMaxMs(默认 60s,在设置分区可改,0 = 完全忽略提示)。上游常回 Retry-After: 3600(一小时),无上限地照做会让会话整小时不动,还把你规则里写的休眠时长悄悄改掉。
  • 休眠可被用户中断/停止即时唤醒,会话卡片显示为已取消。

输出截断自动续写

模型输出达到 token 上限被截断时,插件自动注入一条「继续」,让它接着把话说完:

  • 为什么不能靠规则码命中:max-tokens 不是失败。pi-ai 把它映射成 { kind: 'max-tokens' },agent-loop 收到后结算为步骤结束原因,而 agent/request-error 瀑布只在 error/aborted 时触发——所以任何错误码规则都命中不了它。
  • 为什么必须在轮次收尾处接力:宿主把 max-tokens 记在轮次上且是粘性的(后续步骤正常完成也不能把它降级)。一旦粘住,宿主在每个后续步骤收尾时都会派发 agent/turn-stopping,并在没人投递新步骤时结束轮次——普通「工具调用后自动继续」正是在这里失效的。插件在这个钩子上补回宿主本该做的判断。
  • 两类接力分开计数:「截断 → 续写 → 又截断」受 maxContinues 封顶、续写前休眠 delayMs;「工具结果待回喂」是宿主本来就会做的那一步,不计入 maxContinues、不休眠、无上限(真实 agentic 轮常有几十上百步工具调用,共用预算会在第 5 步后把工具结果永远卡在对话外面)。
  • 跨轮自动重启:轮次仍以粘性的 max-tokens 收尾时(goal 驱动见该 reason 即 disarm,没人开新轮),只要本轮确有实质推进就等旧 driver 收敛后自动 followup 一条「继续」开新轮;连续重启上限 restartTurns,正常收尾即清零,无推进则停(防止模型只会复读「继续」时空转)。

对应配置(补丁行 config.maxTokens):

maxTokens:
  enabled: true      # 关掉则恢复原样:截断后等你手动继续
  text: '继续'        # 注入内容
  maxContinues: 5    # 每轮最多自动续写 5 次(只约束截断续写)
  delayMs: 3000      # 截断续写前休眠 3 秒,别打爆 provider
  restartTurns: 10   # 轮次以粘性 max-tokens 收尾时,最多连开 10 个新轮
  restartDelayMs: 1200  # 重开前等旧 driver 收敛的时间(不能省:running 阶段投递的唤醒会被丢弃)

首字超时

「输出截断自动续写」管的是「说到一半被截断」,首字超时管的是「一个字都不说」:请求发出后超过 firstTokenTimeout.timeoutMs 仍没有任何内容增量,就中断这次请求并报超时。

  • 什么叫「第一个字」:text-delta(正文)、reasoning-delta(思考)、tool-call-delta(工具调用)任一到达即撤销计时。只收到 block-start/usage 这类不含内容的结构帧不算——所以在计时窗口内一直有思考流出的推理模型不会误伤。
  • 接缝是 llm/stream 中间件(waterfall,global: true):包住下游流,在第一次拉取处起表。超时后停止拉取,并产出一个合成的终止 chunk { type: 'finish', reason: { kind: 'error', failure: { code: 'TIMEOUT', message: '首字超时:…' } } }。之所以不抛异常:中间的 waterfall 抛错会直接穿出 agent-loop(llm/src/index.ts 明确「middleware 失败仍抛出」),不经过 agent/request-error 瀑布,本插件的规则引擎就没机会重试;合成 finish 则走宿主正常失败路径——因此再配一条规则码 TIMEOUT 就能自动重试,否则它作为本轮错误上报,聊天区显示超时原因。
  • 只约束 agent 主请求:compaction / session-title 这类带 purpose 的调用不设限(压缩中途被打断会伤上下文),没有 sessionId 的零散调用也无从归属。
  • 已知限制:放弃消费不等于立刻断开连接。异步生成器停在 await 时 return() 是排队的,上游 HTTP 请求可能在 provider 自己响应或 pi-ai 的流空闲 watchdog(profile.streamIdleTimeoutMs)触发前继续挂着;本接缝拿不到也不该改宿主的请求对象(它被 Object.freeze 且带 agent-loop 的对象身份标记),无法注入自己的 AbortSignal。

对应配置(补丁行 config.firstTokenTimeout,或设置分区里的「首字超时」开关 + 秒数):

firstTokenTimeout:
  enabled: true
  timeoutMs: 180000   # 1000–3600000;长思考模型别设太小,否则正常请求也会被判超时

配置来源(优先级从高到低)

  1. GUI 界面保存:~/.dsh/llm-error-retry/rules.json(含设置分区与备用页,两处等价;跨重启持久,npm 升级不影响)。文件里除 rules 外还存 honorRetryAfterMaxMs(Retry-After 采信上限)与 firstTokenTimeout(首字超时)。
  2. ~/.dsh/cordis.patch.yml 中同 id(llm-error-retry)覆盖行的 config.rules、config.honorRetryAfterMaxMs 与 config.firstTokenTimeout。
  3. 本包内置默认(cordis.patch.yml):429 → 60s × 10。

运行快照与事件流水:~/.dsh/llm-error-retry/status.json(config.dataDir 可改数据目录)。

工作原理

  • 宿主半(host.v15.mjs):DSH 每次模型请求失败会派发 agent/request-error waterfall(payload 携带 failure{message,code,status?,providerRetryAfterMs?}、retryPolicy、signal)。插件命中规则后等待 delayMs,返回 {kind:'retry'} 让 agent 循环重新发起同一请求;并写入 llm/retry / llm/retry-started 会话持久事件(与内置重试同一契约、同一 retryId 贯穿整条链),聊天区原生卡片即时渲染。输出截断的自动续写走 agent/assistant-stream(记录每步 finish)+ agent/turn-stopping(在轮次收尾处补投一步)+ session/event(轮次收尾后跨轮重开)三个钩子;首字超时走 llm/stream waterfall(包一层异步生成器,到点产出合成的 TIMEOUT finish chunk)。
  • 浏览器半(client.js):DSH 的双拼包机制——package.json 声明 dsh.client.platform=web + exports["./client"],宿主 client-modules 扫描到后把 bundle 并入 GUI 启动图(window.__ModuleLoader__.load 注册协议,与官方打包产物一致),在浏览器端注册 settings.section 设置分区。数据面走宿主 webServer 路由 /llm-error-retry/api/config(同源 fetch)。

二次开发注意(DSH 平台怪癖清单)

  • 宿主半代码改动必须换文件名(host.vN.mjs,同步 package.json exports["."] 与补丁行 name):DSH 的 config-only HMR 只热更配置,不重载同路径 ESM 模块;只改 client.js 无需改名(每次重组重读字节)。
  • 宿主 HTTP 响应绝不自带 Content-Length(statusCode + setHeader + res.end):web profile 的 gzip 中间件(阈值 1024B)与 writeHead 钉死的 CL 冲突,压缩后浏览器会丢弃整个响应体。
  • webServer 用 ctx.inject(['webServer'], …) 异步等待 + register 失败轮询重试:冷启动时 HOME 补丁层先于 webserver 挂载;bundle 层同样存在新旧 fiber 交叠的 duplicate-route 窗口。headless profile 无 webServer → 仅无界面,重试逻辑不受影响。
  • 用户配置绝不能写进包目录(pnpm store 升级即替换)→ 数据目录在 ~/.dsh/llm-error-retry/。
  • 行为回归用「假 agent + 假 session」探针:ctx.waterfall('agent/request-error', payload, next) 触发真实监听链,session.append 用收集器承接,零污染真实会话。

常见问题

  • 怎么确认它在工作? 触发一次限流错误后:会话框出现「模型重试」卡片(第 N/M 次 · 休眠 Xs);~/.dsh/llm-error-retry/status.json 的 lastEvents 会依次记录 sleeping → retrying,waiting 计数即实时休眠数;宿主日志(llm-error-retry: 前缀)同步输出。输出被截断时事件里会记 continuation(已自动续写)、restart(已自动重开新轮),耗尽或无进展时记 continuation-exhausted / continuation-stalled / restart-exhausted / restart-stalled。
  • 会把不该重试的错误重试掉吗? 只有命中规则的才重试;未命中一律原样放行(内置策略行为不变)。规则命中后预算耗尽即上报,不会无限吞错。
  • 中断会怎样? 休眠等待可被用户中断/停止即时唤醒,卡片显示为已取消。
  • 升级/卸载后我的规则还在吗? 规则在 ~/.dsh/llm-error-retry/rules.json,与包无关;dsh plugin --profile web remove dsh-llm-error-retry 后想彻底清除数据再删这个目录即可。
  • dsh plugin add 卡住? 本包无构建脚本、零依赖,git 安装不需要 allowBuilds;若网络到 github.com:443 不稳可稍后重试或用 tarball 安装提示。

许可

MIT © 2026 liaoyuqing