在这里插入图片描述

DeepSeek Harness 中的 429 限流:重试次数增大与 RetryPolicy 配置陷阱

1. 关于 DeepSeek Harness

DeepSeek Harness(简称 dsh)是 DeepSeek AI 开发的开源 Agent 运行时框架。采用 Cordis 插件架构(一切皆插件),支持 Web UI 和 CLI 两种交互方式。当前处于开发者预览阶段,迭代迅速。

npx @deepseek-ai/dsh web   # 启动 Web UI,默认 http://127.0.0.1:3080

2. 问题

生产环境遇到模型 API 限流导致的请求失败:

RetryAfter: 1064ms
Failure: 429 {"message":"rpm exhausted","type":"quota_exceeded_error","code":"8"}

DSH 默认 DEFAULT_MAX_RETRIES = 2(定义于 pi-ai/dist/utils/retry.js)。对于高频调用场景,2 次重试在指数退避的初始阶段即耗尽,请求在 1-2 秒内失败。

3. 配置架构

3.1 适配器路由

settings.yaml 中的 agent-default-model 决定了请求的适配器路由:

agent-default-model:
  provider: provider-a
  model: model-x

provider 字段作为路由 key,决定请求发往哪个适配器:

provider 值适配器配置文件
存在于 llm-pi-ai.providersllm-pi-aisettings.yaml
deepseek-officialllm-deepseekcordis.patch.yml

关键: 必须先确认模型走哪个适配器,再改对应的配置文件。改错文件不会生效。

3.2 Provider 配置插槽

llm-pi-ai 适配器的 Provider 配置结构:

llm-pi-ai.providers.<provider>:
  - apiKeyEnv        # 凭证环境变量名
  - api              # API 协议(openai-completions / openai-chat)
  - baseURL          # 端点
  - retryPolicy      # 重试策略(可选)
  - models[]         # 模型声明列表

每个 provider 的配置是独立命名空间retryPolicy 只影响当前 provider 的请求。

4. 配置 RetryPolicy

4.1 配置项

retryPolicy:
  mode: normal                          # 重试模式
  maxRetries: 12                        # 最大重试次数(默认 2)
  retryableCodes:                       # 可重试的错误码列表
    - RATE_LIMIT
    - SERVER
    - TIMEOUT
    - TRANSPORT
    - EMPTY_RESPONSE
  backoff:
    initialDelayMs: 5000                # 初始退避延迟
    maxDelayMs: 30000                   # 最大退避延迟(指数退避上限)

4.2 参数语义

  • mode: normal — 标准退避模式,Retry-After 响应头优先级高于 backoff 计算值
  • maxRetries — 重试次数的硬上限,与 retryableCodes 共同构成重试判定
  • backoff.initialDelayMs — 首次重试前的延迟基数
  • backoff.maxDelayMs — 指数退避的上限,超过此值的退避延迟会被截断

4.3 指数退避算法

DSH 的退避实现采用 capped exponential backoff:

delay = min(initialDelayMs × 2^(attempt-1), maxDelayMs)

initialDelayMs=5000, maxDelayMs=30000

AttemptDelayCumulative
15,000ms5s
210,000ms15s
320,000ms35s
430,000ms (capped)65s
530,000ms95s
630,000ms125s
30,000ms
1230,000ms~6min

4.4 配置热加载

settings.yaml 采用热加载机制——DSH 在运行时通过文件系统 watch 检测变更,无需进程重启,修改后立即生效。

5. 陷阱:错误分类导致重试失效

配置了 maxRetries: 12 之后,遇到 429 限流仍然不重试,直接报错:

429: {"message":"Allocated quota exceeded, please increase your quota limit.","type":"invalid_request_error","code":"insufficient_quota"}

5.1 重试判定流程

Request → Failure
  → classifyPiAiError(message)     ← 错误分类
    → isQuotaExceededError(message) ① 优先匹配 quota
    → /429|rate.?limit/i            ② 其次匹配 429
  → retryableCodes.includes(code)  ← 重试判定
    → true    → recover()          ← 指数退避后重试
    → false   → next()             ← 直接终止,抛出终态错误

5.2 根因:错误分类的优先级反转

classifyPiAiErrordsh-llm-pi-ai/lib/index.js)的实现:

function classifyPiAiError(message) {
    if (isQuotaExceededError(message)) return QUOTA_EXCEEDED_CODE;  // priority 1
    if (/\b429\b|rate.?limit/i.test(message)) return "RATE_LIMIT";  // priority 2
    // ...
}

isQuotaExceededErrordsh-llm/lib/index.js)的判定正则:

/\b(?:quota|usage[\s_-]+limit)[\s_-]+(?:exceeded|exhausted|reached)\b/i

当错误消息中出现 quota_exceeded_errorquota exceeded 等词面时,函数返回 QUOTA_EXCEEDED_CODE = "QUOTA"与 HTTP 状态码无关。match 发生在判定 429 之前。

5.3 重试判定短路

dsh-llm-retry/lib/index.jsrecover 方法:

if (!policy.retryableCodes.includes(failure.code)) return next();

retryableCodes 默认值不包含 QUOTA。因此,即使 maxRetries 配置为 12,一旦错误被归类为 QUOTA,重试判定在第一步就短路,直接调用 next() 抛出终态错误。

6. 解决方案

retryPolicy.retryableCodes 中显式声明 QUOTA

retryPolicy:
  mode: normal
  maxRetries: 12
  retryableCodes:
    - RATE_LIMIT
    - SERVER
    - TIMEOUT
    - TRANSPORT
    - EMPTY_RESPONSE
    - QUOTA                     # 让"配额类"429 也参与重试
  backoff:
    initialDelayMs: 5000
    maxDelayMs: 30000

注意:显式声明 retryableCodes 会覆盖默认值,因此需要将其他可重试错误码一并列出。

7. 设计缺陷与改进建议

7.1 错误分类的优先级反转

QUOTA 判定优先于 RATE_LIMIT,导致 429 限流被错误归类为终态错误。合理的做法是将具体的 HTTP 状态码匹配(429)置于语义匹配(quota)之前,或提供可配置的分类优先级。

7.2 retryableCodes 默认值不完整

QUOTA 码在语义上属于可重试的临时错误,应纳入默认可重试列表。

7.3 providerRetryAfterMs 的静默丢弃

Retry-After 响应头值大于 maxDelayMs 时,normal 模式直接调用 next() 放弃重试,无日志、无告警。

8. 调用链与代码路径

层级组件职责
配置宿主settings.yamlllm-pi-ai.providers.<provider>.retryPolicy用户配置入口
Schema 验证pi-ai/dist/types.d.tsmaxRetries?: number类型约束
适配器dsh-llm-pi-ai/lib/index.jsclassifyPiAiError错误分类
错误分类dsh-llm/lib/index.jsisQuotaExceededErrorQUOTA 判定正则
重试控制dsh-llm-retry/lib/index.jsrecover重试判定 + 退避执行
退避算法pi-ai/dist/utils/retry.jsDEFAULT_MAX_RETRIES = 2默认值 + 指数退避计算

9. 参考代码位置

  • node_modules/@deepseek-ai/dsh-llm-pi-ai/lib/index.jsclassifyPiAiError
  • node_modules/@deepseek-ai/dsh-llm/lib/index.jsisQuotaExceededError (±L298), QUOTA_EXCEEDED_CODE = "QUOTA"
  • node_modules/@deepseek-ai/dsh-llm-retry/lib/index.jsrecover 中的 retryableCodes.includes
  • node_modules/@earendil-works/pi-ai/dist/utils/retry.jsDEFAULT_MAX_RETRIES = 2
  • C:\Users\user\.dsh\settings.yaml — 用户配置
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐