AI Agent半夜集体罢工:一次模型配额耗尽的真实故障复盘

0 阅读10分钟

8月15日晚,我们7个AI数字员工agent并行开发企业级Skill共享平台,Harry一次性派发5条任务,10分钟后全部同时失败——状态finished,输出却是429配额耗尽,重试依然秒失败,模型名还显示unknown。

是任务没调起来还是模型配置出错?根因是7个员工共用的token-plan周配额套餐耗尽。本文复盘30分钟排查全过程与3条预防措施。

一、故障现象:5条任务集体"秒失败"

当晚故障的特征非常典型:任务不是"卡住",而是"瞬间失败",且错误信息高度一致。

我们的开发方式是7个专职agent并行作战:产品经理、项目管理员、开发、测试、运维、UI、前端,全部跑在开源Agent框架 QwenPaw 上,由协调agent Harry 统一派活。8月15日晚上,Harry 一次性派发了5条开发任务——服务端骨架、钉钉认证、API、CI流水线、前端骨架——全部转入后台并行执行。

结果出乎意料:任务提交后全部失败。更诡异的是,任务状态列显示的是 finished(执行完成),但执行结果里却是失败信息:

Task failed. Error: Quota exceeded for model unknown.
Reason: Your token-plan 1-week quota has been exhausted.
The quota will reset at 08-20 15:29:00 UTC.

这行报错有两个误导点:

  1. 模型名显示 unknown。看到这个字段,第一反应是"请求压根没带上模型名"——这让我们一度怀疑是任务调度配置的问题,方向差点跑偏。
  2. 状态是 finished。如果只看状态列,会以为任务"正常结束了",必须点开错误 dump 才能看到真正的失败原因。

当时我们还做了一件非常"符合直觉但没用"的事:反复重试。结果每次都是秒失败——注意"秒失败"这个特征,它说明请求在很短时间内就被服务端拒绝,不是超时、不是网络抖动,是明确的策略性拒绝。

https://wdcdn.qpic.cn/MTY4ODg1Nzk5MjcyNTg1OQ_661330_1FS4UvW8Sddbe5Fp_1787286728?w=1200&h=1408

故障时间线。从派发任务到修复完成约30分钟,其中反复重试浪费了约30分钟里的前30分钟。

二、排查过程:6步证据链定位真凶

整场排查的核心方法是"让证据说话":每一步都基于错误dump、配置文件和日志下结论,而不是靠猜。 先看整体决策流:

https://wdcdn.qpic.cn/MTY4ODg1Nzk5MjcyNTg1OQ_6190_szs7JdVXaoKvl7n__1787286730?w=1200&h=1458

排查决策流。第1步用错误dump区分"任务没调起来"和"调起来被拒绝",是整场排查的分水岭。

第1步:先看错误 dump——请求到底发出去没有?

初判怀疑任务没调起来,但错误 dump 显示请求已经走到模型 API,被服务端429拒绝——结论是任务调起来了,问题在配额。

当时的第一直觉是:任务秒失败 + 模型名 unknown,八成是配置缺失导致任务根本没跑起来。但打开错误 dump 里的异常栈后,判断立刻反转:

openai.RateLimitError: Error code: 429 - {'error': {'message': 'Your token-plan 1-week quota has been exhausted. The quota will reset at 08-20 15:29:00 UTC.', 'type': 'insufficient_quota', 'code': 'insufficient_quota'}}

关键信息在错误类型上:openai.RateLimitError、HTTP 429、insufficient_quota(配额不足)。这说明请求已经通过了框架的调度,走到了 OpenAI 兼容层(一个提供 OpenAI 兼容 API 的网关,统一转发到各家模型服务),然后被服务端拒绝。如果是任务没调起来,根本不会有这一层 API 响应。

这个 dump 是整个排查最重要的物证:它证明"任务调起来了,是配额问题"。

第2步:查全局配置——模型配置竟然不在配置文件里

查 config.json 和 agent.json,模型配置字段都是空 dict——模型配置不是写死在配置文件里的,而是运行时动态管理的。

既然确认是配额问题,下一个问题就是:模型配置到底在哪配的?我们翻遍了全局的 config.json 和 agent.json,结果两个文件里的模型配置字段全是空字典 {}。

这说明模型配置是平台运行时动态管理的(按 agent 维度下发),不在静态配置文件里。排查方向随之转向:"模型配置存在哪、怎么查"

第3步:查服务端日志——两条关键线索

日志里出现两条线索:①协调 agent 用的模型和数字员工不一样,②有 402 Insufficient Balance(余额不足)报错。

线索一来自平台日志的一行输出:

Returning agent-specific model for default: aliyun/qwen3.7-plus

注意关键词 agent-specific model——模型配置是"按 agent 区分"的。这行日志说明协调 agent(default)走的是 aliyun/qwen3.7-plus,和数字员工不是同一个配置。

线索二是日志里夹杂的 402 Insufficient Balance(余额不足)报错——说明有 provider 连账户余额都不够了,这不是孤立的 429 问题,而是多个 provider 的可用性同时出问题

第4步:逐个查询有效配置——真相大白

调用 GET effective 接口逐个查 8 个 agent 的有效模型:7 名数字员工全部绑定 aliyun/qwen3.8-max**(token-plan 周配额套餐),协调 agent 已被人工切走。**

平台提供了查询"生效配置"的接口:

# 查询某个 agent 实际生效的模型配置(scope=effective 表示取最终生效值)
GET /api/models/active?scope=effective&agent_id=<agent_id>

我们循环查了全部 8 个 agent(Harry + 7 名数字员工),结果触目惊心:

  • 7 名数字员工全部绑定 aliyun/qwen3.8-max,走的是同一个 token-plan 周配额套餐(云厂商按周售卖 token 额度的计费方式);
  • 协调 agent Harry 已经被人工切换到了别的模型,所以它没有挂;
  • 但 7 个员工没人跟着切

到这里根因已经浮出水面:这不是"没配模型",而是"7 个员工绑在同一个即将耗尽的配额套餐上"——配额一耗尽,就是集体罢工。

第5步:Provider 可用性排查——单一配额套餐就是单点故障

逐个验证可用 provider:aliyun 系三个 provider 共用同一套餐端点全部 429,dashscope 按量付费 key 为空且 402,deepseek key 有效——当时唯一可用。

为了让"为什么只有 deepseek 能切"这件事有据可查,我们把当时环境里所有 provider 的可用性拉了一张表:

| Provider | 端点 | 计费方式 | 故障时状态 | 排查结论 | | --- | --- | --- | --- | --- | | aliyun | token-plan.cn-beijing.maas.aliyuncs.com | token-plan 周配额套餐 | 429 配额耗尽 | 7 个员工绑定此套餐 | | aliyun-tmp | 同上 | 同上 | 429 | 与 aliyun 同一套餐端点 | | aliyun-tokenplan | 同上 | 同上 | 429 | 与 aliyun 同一套餐端点 | | dashscope | 按量付费 | 按量付费 | api_key 为空 + 402 余额不足 | 备用通道实际不可用 | | deepseek | api.deepseek.com | 独立 key | key 有效 | 唯一可用,切换目标 |

aliyun/aliyun-tmp/aliyun-tokenplan 三个 provider 看起来是三个选项,实际指向同一个套餐端点——配额耗尽时,三个"选项"一起死。

这张表解释了整场故障的结构性原因。配一张架构图看得更清楚:

https://wdcdn.qpic.cn/MTY4ODg1Nzk5MjcyNTg1OQ_9627_DAF4juQJIKJxqfus_1787286726?w=1200&h=1180

8 个 agent 各自持有 per-agent 模型配置,统一走 OpenAI 兼容层路由到不同 provider。7 个员工共用一条 aliyun 配额通道——这就是单点。

第6步:修复——循环 PUT,逐个切换 7 个 agent

修复动作是循环调用 PUT 接口,把 7 名数字员工逐个切到 deepseek,scope 必须是 agent(只改单个 agent),然后 GET 验证持久化。

关键点:因为模型配置是 per-agent 的,没有"一键全切"的入口,只能逐个切换。修复命令如下(agent_id 为示意,实际按平台的 agent 列表循环):

# 循环把 7 个数字员工 agent 切换到 deepseek
# scope=agent 表示只修改单个 agent 的模型配置,不影响协调 agent
for agent_id in product-manager project-admin developer qa ops ui frontend; do
  curl -X PUT "https://<qwenpaw-host>/api/models/active" \
    -H "Content-Type: application/json" \
    -d "{
      \"provider_id\": \"deepseek\",
      \"model\": \"deepseek-v4-flash\",
      \"scope\": \"agent\",
      \"agent_id\": \"$agent_id\"
    }"
done

# 切换后必须验证:逐个 GET effective 确认已持久化
for agent_id in product-manager project-admin developer qa ops ui frontend; do
  curl "https://<qwenpaw-host>/api/models/active?scope=effective&agent_id=$agent_id"
done

切换完成后,我们重提了3条核心任务,60秒后检查全部 running(之前是秒失败);3分钟、4.5分钟后又复查了两次,持续 running;再看服务端日志,确认切换后零配额错误

三、根因:为什么这次故障"必发生"

这次故障不是偶然,是"per-agent 模型配置 + 单一配额套餐 + 无可用 fallback"三重叠加的必然结果。

复盘下来,根因可以拆成三层:

  1. 配置层:模型配置是 per-agent 的。协调 agent 被人工切走时,7 个员工没有跟着切——"改一个不等于改全部"。
  2. 资源层:7 个员工共用一个 token-plan 周配额套餐,配额耗尽 = 集体罢工。aliyun/aliyun-tmp/aliyun-tokenplan 三个 provider 名字不同,指向同一个套餐端点,本质是同一个资源。
  3. 容灾层:没有可用的 fallback。dashscope 按量付费本来是最合理的备用通道,但当时 api_key 为空、余额还不足(402),等于保险丝烧了没得换。

任何一个层面堵住,这次故障都不会发生:比如巡检时发现员工还绑在旧套餐、比如 dashscope 通道提前配好 key、比如配额告警。

四、结果:3条任务恢复,CI 首次全绿

当晚3条核心任务全部恢复执行,最终产出6个 MR 全部合入 develop 分支,CI 流水线首次全绿。

从发现故障到修复完成约 30 分钟,最终数据:

  • 3 条核心任务恢复执行,全部跑完;
  • 产出 6 个 MR,全部合入 develop 分支;
  • CI 流水线首次全绿:build 25s + lint 50s + test 50s
  • 服务端日志零配额错误。

这次故障没有造成开发进度损失,但给我们上了一课:多Agent 系统的"单点"往往藏在模型配置层,而不是代码层。

五、三条预防措施:写给所有多Agent系统

这三条不是针对一次故障的补丁,而是任何跑着多个 agent 的团队都该内置的运维习惯。

1. 建立"逐个验证有效配置"的巡检习惯

per-agent 模型配置是常态,"改一个 agent"不等于"改全部 agent"。我们现在的做法是:任何模型配置变更后,批量 GET effective 接口逐个核对,而不是改完就默认生效。把"查生效配置"做进发布流程,就像上线前检查环境变量一样自然。

2. 任务失败先看错误 dump,再下结论

"任务没调起来"和"调起来被拒绝"是两类完全不同的故障,排查方向截然相反。这次如果没有错误 dump 里的异常栈,我们会一直在调度层找问题。证据比直觉可靠——哪怕报错里的模型名是 unknown,也不要被它带偏,要看完整 dump。

3. 关键供应商必须有 fallback

单一配额套餐 = 单点故障。配额耗尽和余额不足是云服务商的日常,任何关键供应商都要有一条按量付费的备用通道,哪怕贵一点,它是保险丝——这次如果不是 deepseek 的 key 一直有效,我们连切都没得切。


你的多Agent系统遇到过类似的集体罢工吗?欢迎在评论区分享你遇到的报错和排查经历——尤其是那些"看起来像配置问题,其实是配额问题"的坑。