Token 配额高峰时段:AI Agent 夜间 cron 的 RateLimit 实战

本文最后更新于 2026年8月14日 凌晨

凌晨 3:45,是我这套 AI Agent 系统的发刊时间。5 个 profile、12 个 cron 任务一起撞墙,new-api 直接 429。这篇文章记录从撞墙到稳定运行的全过程。

现象:半夜的 429 风暴

问题暴露得很快。某夜 3:55,韩梅梅的 blog-helper cron 报 HTTP 429: RateLimit,同一时段李雷的 ReShare 同步、AutoQuant 的回测 cron、日记 cron、Git 心跳 cron 全部失败。凌晨日志里一片 [429] Too Many Requests

排查时看到的现象:

1
2
3
4
5
2026-08-14 03:45:02 [blog-helper] dailysummary cron start
2026-08-14 03:45:11 [agentic-diary] cron start
2026-08-14 03:46:04 [reshare-sync] cron start
2026-08-14 03:46:33 [heartbeat-git] cron start
2026-08-14 03:47:12 [voc-review] cron start

5 个 cron 在 3 分钟内全部启动。new-api 代理层背后是 deepseek-ai/DeepSeek-V4-Flash 和 HauhauCS/Qwen3.6-35B-A3B-Uncensored 两条 channel。并发请求一多,上游 openai-compatible 接口直接限流。

根因:cron 时间集中 + 每个 cron 内又开多 Agent 并发

两个层级的并发叠加:

  1. cron 层:多个 profile 的 cron 默认在 03:45 触发,彼此只有分钟级偏差,实际效果等于并发启动。
  2. Agent 层:blog-helper 一个 draft 里会并发调 3 个 LLM(MoA 梯队),ReShare 同步单次请求会带多轮上下文,Heartbeat 心跳每次也要 LLM 判定。

两层相乘:30 分钟内 60+ 次 LLM 调用挤在同一条代理 channel 上。

修复一:错开 cron 触发时间

先解决最直接的叠加——把 cron 触发时间打散。原则是每个 profile 错开 5 分钟以上。

Hermes 的 cron 定义在 profile 的 cron.yaml 里。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 韩梅梅 blog 发布 — 原来 03:45,改到 03:50
- name: blog-publish
schedule: "50 3 * * *"

# 李雷 diary — 改到 04:05
- name: diary
schedule: "05 4 * * *"

# AutoQuant 回测 — 改到 04:30
- name: backtest
schedule: "30 4 * * *"

# Git 心跳 — 改到 05:00
- name: heartbeat-git
schedule: "0 5 * * *"

把 5 个 profile 的 cron 打散到 03:50 – 05:00 这一小时窗口内,峰值并发从 5 降到 1。

修复二:草稿 MoA 改为串行,单模型 Fallback

blog-helper 的 draft.py 默认会并发调用 3 个模型(MoA 梯队)。在夜间窗口窄的情况下,并发 MoA 等于把 1 个 cron 变成 3 个 LLM 请求。

在 config.yaml 里加 writing_mode: creative 之外,把 MoA 强制降级为串行单模型,让单次 draft 只占一条 channel:

1
2
3
4
5
6
# ~/.hermes/.blog-helper/config.yaml
llm:
fallback_base_url: "http://your-new-api-endpoint/v1"
fallback_model: "deepseek-ai/DeepSeek-V4-Flash"
fallback_key_env: NEW_API_KEY
writing_mode: creative

实际执行时用 --single 参数直接跳过 MoA,减少 2/3 并发。

修复三:LLM 端显式限速(client-side backoff)

单模型调用的代码里,遇到 429 后不立即重试,而是指数退避:

1
2
3
4
5
6
7
8
9
10
11
12
13
import time, random

def call_llm(request, max_retries=4):
for attempt in range(max_retries):
resp = requests.post(url, headers=headers, json=request, timeout=60)
if resp.status_code == 429:
backoff = min(2 ** attempt * 30, 300) # 最长 5 分钟
sleep = backoff + random.randint(0, int(backoff * 0.2))
print(f"RateLimit, 第 {attempt+1} 次重试,等待 {sleep}s")
time.sleep(sleep)
continue
return resp.json()
raise RuntimeError("LLM 持续限流")

关键点:退避时间要足够长。new-api 的上游一般是 RPM(每分钟请求数)限制,2^attempt * 30s 意味着 30s / 90s / 270s / 810s,4 次重试覆盖 15 分钟,足够越过限流窗口。

修复四:关键 cron 设优先级,允许低优先级失败

不是所有 cron 都要跑完。把 12 个 cron 分三级:

  • P0 关键:blog 发布、Git 心跳、AutoQuant 策略计算
  • P1 重要:diary、ReShare 同步、心跳
  • P2 可延迟:review、proofread 复读、封面图生成

P2 在遇到 429 时直接退出让路,让 P0 优先吃 token。用 cron 的 on_failure: skip_next 控制退避窗口。

效果

实施完四步之后,凌晨观察日志:

1
2
3
4
5
2026-08-14 03:50:02 [blog-helper] cron start
2026-08-14 03:52:17 [blog-helper] LLM call ok 4.2s
2026-08-14 04:05:01 [diary] cron start
2026-08-14 04:05:11 [diary] LLM call ok 1.1s
2026-08-14 04:30:01 [backtest] cron start

429 完全消失,5 个 profile 错峰跑完,最长 cron(blog draft)从 9 分钟缩短到 4 分钟(因为不再 MoA 并发)。

教训

  1. cron 时间是共享资源。多 profile 系统里,触发时间就是隐式同步点,必须显式错开。
  2. MoA 不是免费的并发。每个 Agent 的每一个阶段,都要算清楚 LLM 请求数再决定并发。
  3. 退避要用指数级。线性退避在 RPM 限流下几乎无解——30s/90s/270s 才有希望错开限流窗口。
  4. 低优先级要会主动让路。P2 任务失败不算失败,P2 撑住才导致 P0 饿死。

参考文献

  1. Hermes Agent 文档 — cron 配置与任务管理
  2. HTTP 429 Too Many Requests (MDN)
  3. new-api 官方文档 — 模型代理与 RateLimit 处理
  4. 本机事实库 ~/.hermes/.blog-helper/facts.md — ModelBase 服务器与 LLM channel 配置

Token 配额高峰时段:AI Agent 夜间 cron 的 RateLimit 实战
https://normdist.com/2026/08/14/ND-20260814-001-token-quota-peak-rate-limit-practice/
作者
小瑞
发布于
2026年8月14日
许可协议