故障排查指南
来自 AI API 的 429 Too Many Requests:用退避重试
来自 AI API 的 429 意味着什么以及如何处理:带抖动的指数退避、Retry-After 和 agent 并发,附 Python 和 JavaScript 代码。

本页内容
来自 AI API 的 429 Too Many Requests 表示请求被拒绝,原因是你发送得太多太快,或者触达了账户上的限制。解决办法是等待并使用指数退避加抖动重试,在响应带有 Retry-After 头时使用它,并限制你的 agent 并行发送的请求数量。不要重试那种实际上是消费或余额限制的 429:等待并不能解决它。
本指南基于 OpenAI 和 Anthropic 的官方速率限制指南,两者均于 2026 年 10 月 22 日核对。两个页面都没有显示最后更新日期。
429 的含义#
OpenAI 将 429 描述为因超出速率限制而被拒绝的请求(rate_limit_error,当流量增长过快时带有代码 slow_down),或者因为达到了硬性消费限制。Anthropic 对 429 也使用 rate_limit_error。消费上限导致的 429 没有 retry-after 头,并且会持续失败,直到访问恢复。
有两个相邻状态容易与它混淆。OpenAI 的 503 server_ 和 Anthropic 的 529 overloaded_ 是过载状况,不是你的速率限制。它们通常用同样的重试循环处理,但它们说明的原因不同。
因此有三种情况,每种都需要不同的反应:
向右滚动表格 →
| 响应 | 原因 | 该做什么 |
|---|---|---|
| 429 速率限制 | 每分钟请求数或 token 数过多 | 等待,用退避重试 |
| 429 消费或余额限制 | 达到账户限制 | 不要重试;调整限制或充值余额 |
| 503 / 529 过载 | 提供商一侧繁忙 | 用退避重试,并设置上限 |
如果你的余额用完了,请阅读“token 不足”错误。对于超时和连接被拒绝等网络层故障,请参阅常见连接错误。
在猜测延迟之前先读取响应头#
OpenAI 在速率受限响应上记录了这些头:Retry-After,以及 x-、x-、x- 及其 -tokens 变体。其他 API 可能只发送其中一部分,或者一个都不发送,因此编写客户端时应在 Retry-After 存在时使用它,不存在时回退到你自己的时间表。
OpenAI 关于重试的指导,简而言之:
- 将
Retry-After视为最小等待时间,并在其上添加一个小的随机延迟。 - 如果没有它,使用带抖动的指数退避。
- 同时限制尝试次数和总重试时间。
- 不要在你自己的重试之上再叠加 SDK 的重试。
- 不要重试配额或计费错误。
- 失败的请求仍然计入每分钟限制,因此激进的循环会让情况更糟。
- 对于流式传输,在你已经消费了某个请求的输出之后,不要重放该请求。
Python:带抖动的退避#
官方 openai 包可用于任何 OpenAI 兼容端点。关闭 SDK 自身的重试(max_retries=0),以免它们与你的重试相乘。模型 id 来自 CheapTokens 文档;不同提供商的 id 不同。
import os
import random
import time
from openai import OpenAI, APIStatusError
client = OpenAI(
base_url="https://api.cheaptokens.dev/v1",
api_key=os.environ["CHEAPTOKENS_API_KEY"],
max_retries=0, # we retry ourselves
)
RETRYABLE = {429, 500, 502, 503, 504}
def chat_with_retry(model, messages, max_attempts=5, max_total=60.0):
started = time.monotonic()
for attempt in range(max_attempts):
try:
return client.chat.completions.create(model=model, messages=messages)
except APIStatusError as err:
last = attempt == max_attempts - 1
if err.status_code not in RETRYABLE or last:
raise
header = err.response.headers.get("retry-after")
if header and header.replace(".", "", 1).isdigit():
delay = float(header) + random.uniform(0, 1) # minimum wait + jitter
else:
delay = random.uniform(0, min(30.0, 2.0 ** attempt)) # full jitter
if time.monotonic() - started + delay > max_total:
raise
time.sleep(delay)没有头时,延迟的上限按 1、2、4、8 秒增长(上限为 30),而 random.uniform 会在其下选取一个随机值,这样许多 worker 不会都在同一时刻醒来。Retry-After 也可能是 HTTP 日期;上面的代码处理数字形式,否则回退到退避。
JavaScript:同样的循环#
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.cheaptokens.dev/v1",
apiKey: process.env.CHEAPTOKENS_API_KEY,
maxRetries: 0, // we retry ourselves
});
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export async function chatWithRetry(model, messages, maxAttempts = 5, maxTotalMs = 60000) {
const started = Date.now();
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await client.chat.completions.create({ model, messages });
} catch (err) {
const last = attempt === maxAttempts - 1;
if (!RETRYABLE.has(err.status) || last) throw err;
const header = Number(err.headers?.get?.("retry-after"));
const delayMs = Number.isFinite(header) && header > 0
? header * 1000 + Math.random() * 1000 // minimum wait + jitter
: Math.random() * Math.min(30000, 2 ** attempt * 1000); // full jitter
if (Date.now() - started + delayMs > maxTotalMs) throw err;
await sleep(delayMs);
}
}
}当它奏效时,一个速率受限的调用只是耗时更长,然后返回正常的补全。当它在最后一次尝试后仍然失败时,会抛出原始错误,这样你的代码可以记录它,或者把任务排队留待稍后处理。
Agent 应如何处理 429#
Agent 循环会发送许多短调用,并且多个 agent 或工具调用可能同时运行。仅靠重试是不够的;要从源头降低请求速率:
- 限制并发。 3–5 个并行调用的信号量是一个合理的起点;只有在错误保持罕见时才提高它。
- 共享一个限流器。 如果多个 worker 使用同一个 key,它们共享相同的限制,因此每个 worker 一个重试循环是不够的:为进程使用一个队列或一个信号量。
- 全局退避。 当一个调用收到带有延迟的 429 时,暂停整个池那么长时间,而不是只暂停失败的调用。
- 让请求更小。 每个请求的 token 更少意味着对每分钟 token 限制的压力更小:裁剪历史和工具输出。关于 token 都去了哪里,请参阅如何估算 AI agent 的成本。
- 在不可重试错误上停止。 401、400 或余额错误不会自行修复;快速失败并报告。
import asyncio
limit = asyncio.Semaphore(4) # at most 4 requests in flight
async def run(job):
async with limit:
return await call_model(job) # your async call with the retry loop above在 CheapTokens 上运行 agent 循环#
对于有许多短调用的 agent 循环,CheapTokens 提供 MiniMax-M2.7:任何时段一个价格,180K 上下文,每次回答最多 8,192 个 token,所有模型共用一个余额。输出价格为每 1M token $0.15,并且总计(输入和输出合计)比 OpenRouter 更便宜。它通过 OpenAI 兼容端点 https:/ 工作。上面的代码已经指向这个端点;从 CheapTokens 文档设置模型 id 并运行它。
500,000 输入、100,000 输出工作负载的实时价格:
| 模型 | 输入 $/1M | 输出 $/1M | 费用 | OpenRouter |
|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.014 | $0.0282 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.0175 | $0.0394 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.04 | $0.27 |
总计 500,000 个输入 token 和 100,000 个输出 token。每 100 万 token 的价格为当前价格,会随我们的资费一同变动;OpenRouter 列显示相同工作负载的参考价格。
查看所有价格、设置文档,并创建一个 key 来发送你的第一个请求。
如果仍然失败#
- 第一个请求就 429。 阅读响应体中的错误类型和消息:消费或账户限制的 429 会在重试时持续失败,直到限制被提高或访问恢复,而速率限制的 429 会在暂停后清除。
- 长循环中 429 持续出现。 你可能在叠加重试:SDK 重试乘以你的重试。将 SDK 设置为零,如示例所示。
- 错误在批次开始时成串出现。 逐渐提升,而不是一次性启动所有 worker;OpenAI 报告流量增长过快时会出现
slow_down。 - 流式调用中途失败。 在你消费了输出之后不要盲目重放;决定是重新开始整个回答,还是从已有内容继续。
- 响应中没有 Retry-After。 代码会回退到带抖动的退避,因此无需更改。



