@zhourenke/dsh-agent-rate-limit
English | 中文
Agent 循环速率限制器 — 通过拦截 LLM 流式调用管道并在请求之间添加自适应延迟,防止 TPM(每分钟令牌数)和 RPM(每分钟请求数)限制违规。
当模型供应商实施速率限制时(例如阿里云百炼对 deepseek-v4-flash 的 15,000 RPM + 1,200,000 TPM),Agent 循环每几步就可能触发限制,导致错误和中断。本插件通过以下方式解决此问题:
- 在 60 秒滑动窗口 中跟踪令牌使用情况
- 在每次请求前估算输入令牌数
- 实时统计输出令牌数(从流式块中)
- 在接近 TPM 或 RPM 限制时添加自适应延迟
- 速率限制错误时指数退避(自动重试,返回
{ kind: 'retry' })
工作原理
用户输入 → [agent/request] → [llm/stream*] → LLM API → [agent/request] → ...
↑
速率限制器在此处拦截
┌─ 滑动窗口 (60s FIFO) ──────────────────────┐
│ t0: +5000 tokens (输入) │
│ t5: +12000 tokens (输入) │
│ t12: +8000 tokens (输入) │
│ ... │
│ 当前窗口: 980,000 / 1,200,000 TPM │
│ 剩余: 220,000 tokens → 通过 │
│ 若接近限制 → 在下一次请求前延迟 │
└─────────────────────────────────────────────┘本插件拦截两个 Waterfall 事件:
| 事件 | 用途 |
|---|---|
llm/stream | 检查速率限制 → 必要时延迟 → 流式传输令牌 → 统计输出令牌 → 更新窗口 |
agent/request-error | 检测速率限制错误 (429) → 返回 { kind: 'retry' } 并指数退避 |
安装
本插件是一个 DSH 配置文件包(profile bundle)。目前唯一支持的安装方式是把插件文件夹直接放置到 DSH 配置文件的 node_modules 中,并在该配置文件的 dsh.profile.bundles 列表里注册。无需 npm link、无需 pnpm、无需访问 npm registry。
找到你的 DSH 配置文件
首先确定你使用的是哪个配置文件(profile):
# 列出所有可用的配置文件
Get-ChildItem "$env:USERPROFILE\.dsh\profiles" -Name常见的配置文件:web、tui、headless。配置文件目录为 $env:USERPROFILE\.dsh\profiles\<名称>\。
第 1 步 — 把插件文件夹复制进配置文件
# 若作用域目录不存在则创建
$target = "$env:USERPROFILE\.dsh\profiles\<名称>\node_modules\@zhourenke"
New-Item -ItemType Directory -Force $target
# 复制整个插件文件夹(package.json、cordis.patch.yml、lib/ 等)
Copy-Item -Recurse C:\path\to\dsh-agent-rate-limit "$target\"复制后的目录必须包含 package.json(含 dsh.bundle.patch)、cordis.patch.yml 和 lib/。
第 2 步 — 注册 bundle
编辑 $env:USERPROFILE\.dsh\profiles\<名称>\package.json:
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
+ "@zhourenke/dsh-agent-rate-limit"
]
}
}无需在 dependencies 中添加任何条目——DSH 启动时只按包名从配置文件的 node_modules 物理解析 bundle(dependencies 条目只对 pnpm install 有意义,本插件不依赖它)。
第 3 步 — 重启 DSH
下次启动 DSH 时插件会被加载。更新插件源码后重新复制文件夹(或使用 junction 以实现热更新),然后重启 DSH。
验证安装
重启 DSH 后,检查启动日志中是否有 [agent-rate-limit] 条目,确认速率限制器已激活:
dsh web 2>&1 | Select-String "agent-rate-limit"预期输出:
[agent-rate-limit] Plugin loaded. TPM: 1200000, RPM: 15000, factor: 0.8, window: 60000ms, retryOn429: true配置
| 键 | 默认值 | 说明 |
|---|---|---|
windowMs | 60000 | 滑动窗口大小(毫秒,60 秒)。 |
tpmLimit | 1200000 | TPM(每分钟令牌数)限制。默认值匹配阿里云百炼 deepseek-v4-flash。 |
rpmLimit | 15000 | RPM(每分钟请求数)限制。 |
safetyFactor | 0.8 | 安全系数(0.8 = 使用 80% 的限额,留 20% 缓冲)。 |
maxBackoffMs | 30000 | 最大退避延迟(毫秒,30 秒)。 |
retryOn429 | true | 为 true(默认)时,HTTP 429 响应被静默重试(自适应退避),对话全程丝滑。设为 false 则 429 错误会报给用户。 |
maxRetries | 5 | 每次突发中最多连续重试 429 的次数,超过后放弃重试并把错误报给用户。防止永久性错误(如账户配额真正耗尽)导致无限重试循环。 |
示例:针对不同供应商调整
# 在配置文件的 cordis 配置或 agent preset 中:
- id: agent-rate-limit
name: '@zhourenke/dsh-agent-rate-limit'
config:
tpmLimit: 2000000 # 对于其他供应商,2M TPM
rpmLimit: 5000 # 5K RPM
safetyFactor: 0.75 # 75% 利用率,25% 缓冲
retryOn429: true # 静默重试 429(推荐)
maxRetries: 5 # 连续 5 次 429 后放弃速率限制的工作原理
令牌估算
本插件使用启发式方法估算文本中的令牌数:
- CJK 字符(中文、日文、韩文):约 1.5 字符/令牌
- 其他字符(拉丁字母、数字等):约 3.5 字符/令牌
这是有意保守的——稍微多延迟一点也比触发速率限制要好。
错误恢复
检测到速率限制错误时(HTTP 429,阿里云百炼等供应商在 TPM 或 RPM 超限时的典型响应),本插件:
1. 记录错误并递增当前突发的重试计数 2. 返回 { kind: 'retry' } 告诉 Agent 循环透明地重试——用户完全看不到错误 3. 应用递增退避:2s → 4s → 8s → 16s → 30s(上限 maxBackoffMs),每次重试都降低对 API 的压力 4. 连续失败 maxRetries 次后放弃重试(默认 5 次),把错误报给用户。这防止了永久性错误导致无限重试循环——例如账户配额真正耗尽("Allocated quota exceeded, please increase your quota limit") 5. 请求成功时重试计数自动重置
若你想让 429 错误报给用户而不是静默重试,可将配置项 retryOn429 设为 false。
滑动窗口算法
滑动窗口维护一个 FIFO 队列,包含 { timestamp, tokens } 条目。在每次请求前:
1. 删除早于 windowMs(60s)的条目 2. 求和剩余令牌 = 当前 TPM 3. 统计条目数 = 当前 RPM 4. 如果 RPM ≥ 限制 → 延迟直到最旧条目过期 5. 如果 TPM ≥ 限制 → 延迟直到最旧条目过期 6. 如果 TPM + 估算输入令牌 ≥ 限制 → 延迟直到足够令牌过期 7. 如果有连续错误,应用退避延迟
速率限制检测
本插件通过检查错误中的以下信号来检测 HTTP 429 响应:
| 信号 | 示例 |
|---|---|
| HTTP 状态码 | statusCode: 429 |
| 错误码 | 429, RATE_LIMITED, QUOTA |
| "rate limit" 文本 | rate limit exceeded, rate_limit |
| "too many requests" | too many requests, please try again later |
| TPM/RPM 令牌 | TPM limit reached, token limit exceeded |
| "throttle" | request throttled, throttling |
| "quota" | Allocated quota exceeded(百炼) |
| 消息中的 "429" | 429: {...} |
匹配到任一信号时,插件默认(retryOn429: true)会以自适应退避透明地重试。
架构
┌─────────────────────────────────────────────────────────────────┐
│ dsh-agent-rate-limit │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ SlidingWindow(模块级状态) │ │
│ │ ┌─────────────────────┐ ┌───────────────────────────┐ │ │
│ │ │ windowEntries[] │ │ consecutiveErrors │ │ │
│ │ │ {timestamp,tokens} │ │ (指数退避) │ │ │
│ │ └─────────────────────┘ └───────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ctx.on('llm/stream', ...) ctx.on('agent/request-error')│
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ 1. 检查滑动窗口 │ │ 1. 检测速率限制 │ │
│ │ 2. 必要时延迟 │ │ 2. 返回 {kind:'retry'} │ │
│ │ 3. 统计输出令牌 │ │ 3. 记录错误 │ │
│ │ 4. 更新窗口 │ └─────────────────────────┘ │
│ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘依赖
@deepseek-ai/schemastery— 配置模式验证@deepseek-ai/cordis— 插件框架@deepseek-ai/dsh-invariants— DSH 不变性@deepseek-ai/dsh-llm— LLM 错误类型
许可证
MIT