---
title: "429 Too Many Requests 怎么办：大模型 API 限流排查"
description: "大模型 API 报 429 时，从响应头和错误体判断撞的是 RPM、TPM 还是额度，再按 Retry-After 退避重试。附 curl 命令、令牌桶限速代码，以及 429 与 529、503 的区分。"
url: https://cdnping.org/blog/llm-api-429-too-many-requests-fix
language: zh-CN
published: 2026-10-23
category: "排障指南"
tags: ["限流","错误码","OpenAI","Claude API","重试策略"]
source: cdnping.org
license: Free to quote and summarize with a link to the url above.
---
# 429 Too Many Requests 怎么办：大模型 API 限流排查

调用大模型 API 返回 429 Too Many Requests，意思是你在某个时间窗口里用掉的配额超了，服务端暂时不接这个请求。它通常不是故障，也不是网络问题。处理顺序是：先看响应头和错误体，确认撞的是请求数（RPM）、token 数（TPM）还是账户额度；如果是前两种，按 `Retry-After` 或重置时间等一会儿再重试，同时在客户端加限速；如果是额度用完，重试多少次都没用，要去充值或换套餐。

通用的 429 解释文章大多只讲“请求太频繁”，但大模型 API 多了一个维度：token。一次请求塞进十万 token 的上下文，RPM 远没到上限，TPM 却可能一下就满了。下面按排查顺序展开。

## 429 有哪几种来源？

大模型 API 的 429 大致分三类，处理方法完全不同：

| 来源 | 计量方式 | 典型表现 | 等待能否解决 |
| --- | --- | --- | --- |
| 请求数限额（RPM / RPD） | 每分钟或每天的请求次数 | 高并发、小请求时出现，比如批量分类 | 能，窗口过去就恢复 |
| token 限额（TPM / ITPM / OTPM） | 每分钟输入、输出 token 数 | 请求不多但上下文很长，比如 agent 读整个仓库 | 能，但要控制单次请求大小 |
| 额度或余额耗尽 | 账户余额、月度花费上限、订阅额度 | 不管请求多慢都报 429，或错误信息提到 quota、billing | 不能，要处理账户 |

还有一类容易混进来：有些平台对整个组织或项目统一计数，同一个 API Key 被几台机器、几个同事同时用，各自看起来请求都不多，加起来已经超了。排查时要把同一组织下所有调用方算在一起。

## 怎么从响应头判断撞的是哪一个？

最直接的办法是把一次请求的完整响应头打印出来。下面的命令用 `-i` 输出响应头，请求体只要一个很短的输入，不会消耗多少额度：

```bash
# OpenAI 格式接口，$MODEL 换成你账户可用的模型名
curl -sS -i https://api.openai.com/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"model\":\"$MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}],\"max_tokens\":1}" \
  | grep -iE '^(HTTP|x-ratelimit|retry-after)'

# Anthropic 格式接口
curl -sS -i https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d "{\"model\":\"$MODEL\",\"max_tokens\":1,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}" \
  | grep -iE '^(HTTP|anthropic-ratelimit|retry-after)'
```

正常请求也会带这些头，所以不必等到报 429 才看。两家的头含义如下，具体字段以官方文档为准，各家会调整：

| 响应头 | 含义 |
| --- | --- |
| `x-ratelimit-limit-requests` / `-tokens` | OpenAI：当前窗口的请求数、token 数上限 |
| `x-ratelimit-remaining-requests` / `-tokens` | OpenAI：窗口内还剩多少 |
| `x-ratelimit-reset-requests` / `-tokens` | OpenAI：多久后恢复，格式是时长，如 `1s`、`6m0s`、`20ms` |
| `anthropic-ratelimit-requests-limit` / `-remaining` / `-reset` | Anthropic：请求数限额，`reset` 是 RFC 3339 时间戳 |
| `anthropic-ratelimit-input-tokens-*`、`-output-tokens-*`、`-tokens-*` | Anthropic：输入、输出 token 分开计，以及合计 |
| `retry-after` | 建议等待的秒数，429 时最该看的一个 |

判断方法很简单：哪一项的 `remaining` 接近 0，撞的就是哪一项。`remaining-requests` 还剩很多而 `remaining-tokens` 是 0，就是 TPM；Anthropic 的输入 token 先见底，说明问题出在上下文太长，而不是输出太多。

错误体是第二个依据。OpenAI 格式的错误里，`code` 为 `rate_limit_exceeded` 是频率限额，`insufficient_quota` 是额度用完，后者虽然也是 429，但等待解决不了。Anthropic 的 429 错误体 `type` 是 `rate_limit_error`，`message` 会说明是哪类限额。Gemini 的 429 状态是 `RESOURCE_EXHAUSTED`，`details` 里可能带建议的重试间隔。聚合网关和中转服务会把上游的头原样透传、改写或者干脆去掉，所以经过网关时，先看网关自己的文档。

## Retry-After 该怎么用？

原则是：有 `Retry-After` 就听它的，没有再用重置时间，两样都没有才用指数退避。几个容易出错的细节：

1. 单位。`Retry-After` 按 HTTP 规范可以是秒数，也可以是一个 HTTP 日期。大模型 API 一般给秒数，但解析代码最好两种都支持。
2. 值为 0 或很小。有时会拿到 `retry-after: 0`，立刻重试大概率还是 429。给一个下限，比如 1 秒，再加随机抖动。
3. 头缺失。一些网关、代理或 SDK 封装会丢掉这个头。这时退回到 `x-ratelimit-reset-*`，再不行就指数退避：1、2、4、8 秒，封顶 60 秒左右，每次加 0 到 1 秒的随机量，避免多个进程同时醒来再一起撞墙。
4. 重试次数要封顶。3 到 5 次后仍然 429，就不该继续重试，应该报出来，交给上层处理或者排队。
5. 额度类 429 不重试。错误码是 `insufficient_quota` 或者信息里提到 billing、credit，直接失败并提示，不然只是在浪费时间。

下面是一段 Python 的处理函数，不依赖特定 SDK：

```python
import random, re, time
from email.utils import parsedate_to_datetime

def parse_duration(s):
    """解析 OpenAI 风格的时长，如 '6m0s'、'1.5s'、'20ms'。"""
    total = 0.0
    for num, unit in re.findall(r"([\d.]+)(ms|s|m|h)", s or ""):
        total += float(num) * {"ms": 0.001, "s": 1, "m": 60, "h": 3600}[unit]
    return total or None

def wait_seconds(headers, attempt):
    h = {k.lower(): v for k, v in headers.items()}
    ra = h.get("retry-after")
    if ra:
        try:
            sec = float(ra)
        except ValueError:  # HTTP 日期格式
            sec = parsedate_to_datetime(ra).timestamp() - time.time()
        return max(sec, 1.0) + random.random()
    for key in ("x-ratelimit-reset-tokens", "x-ratelimit-reset-requests"):
        sec = parse_duration(h.get(key))
        if sec:
            return max(sec, 1.0) + random.random()
    return min(2 ** attempt, 60) + random.random()

def call_with_retry(send, max_attempts=5):
    for attempt in range(max_attempts):
        resp = send()
        if resp.status_code != 429:
            return resp
        body = resp.text.lower()
        if "insufficient_quota" in body or "billing" in body:
            raise RuntimeError("额度或余额不足，重试无效：" + resp.text[:200])
        time.sleep(wait_seconds(resp.headers, attempt))
    raise RuntimeError("连续 429，已放弃")
```

`send` 是一个发请求并返回 `requests.Response` 的函数。如果用官方 SDK，它们一般自带重试，先查清楚默认重试几次，避免自己的重试套在 SDK 的重试外面，次数翻倍。

## 怎么在客户端提前限速，而不是等 429？

重试只是兜底。批量任务更好的做法是按限额主动控速，让请求几乎不触发 429。因为大模型同时限请求数和 token 数，限速器也要两个桶一起算：

```python
import threading, time

class DualBucket:
    """同时按每分钟请求数和每分钟 token 数限速。"""
    def __init__(self, rpm, tpm):
        self.cap = {"req": rpm, "tok": tpm}
        self.level = dict(self.cap)
        self.rate = {"req": rpm / 60, "tok": tpm / 60}
        self.t = time.monotonic()
        self.lock = threading.Lock()

    def acquire(self, est_tokens):
        est_tokens = min(est_tokens, self.cap["tok"])  # 单次超过桶容量时避免死等
        while True:
            with self.lock:
                now = time.monotonic()
                for k in self.level:
                    self.level[k] = min(self.cap[k], self.level[k] + (now - self.t) * self.rate[k])
                self.t = now
                if self.level["req"] >= 1 and self.level["tok"] >= est_tokens:
                    self.level["req"] -= 1
                    self.level["tok"] -= est_tokens
                    return
            time.sleep(0.05)

bucket = DualBucket(rpm=50, tpm=40_000)   # 示例数值，按你账户的实际限额填
# 每次请求前：bucket.acquire(输入 token 估算 + max_tokens)
```

几个实际要注意的地方：

- rpm、tpm 取实际限额的八到九成，给其他进程和估算误差留一点余量。
- token 估算要包含 `max_tokens`。有的平台在请求开始时就按 `max_tokens` 预占输出额度，把 `max_tokens` 设得远大于实际需要，会白白吃掉 TPM。
- 多进程或多机器要共用一个桶。单机内存里的桶管不住别的机器，可以放到 Redis 之类的共享存储里，或者把所有调用收到一个出口服务。
- 用响应头校准。每次响应后读 `remaining`，如果它比你的桶估计的少很多，说明有别的调用方在共用额度。

## 怎么从根上减少 429？

控速之后还频繁撞 TPM，就该从请求本身下手：

- 缩短上下文。agent 类工具容易把整个文件甚至整个目录塞进去。限制单次读入的内容，或者先摘要再提问。
- 用提示缓存。重复的长系统提示可以缓存。按 Anthropic 文档的说明，部分模型的缓存读取 token 不计入输入 token 限额，具体规则和写法见 [Claude API 提示缓存怎么用才省钱](/blog/claude-prompt-caching-save-cost)。
- 把轻任务分到另一组模型。分类、格式化这类任务用轻量模型，它们通常有单独的限额，主模型的 TPM 留给难任务。思路见 [多模型路由怎么做](/blog/multi-model-routing-save-cost)。
- 申请提高限额。多数平台的限额随用量等级提升，控制台里可以看到当前等级和升级条件。走正规渠道申请，不要靠多开账号、轮换 Key 来叠加限额，这违反大多数服务条款。

## 429、529 和 503 有什么区别？

这一节单独列出来，是因为很多人把服务端过载当成自己超限，去改代码、降并发，其实问题不在自己这边。

| 状态码 | 谁的问题 | 怎么处理 |
| --- | --- | --- |
| 429 | 你的调用超过了分给你的配额 | 按 Retry-After 等待，客户端限速 |
| 529 | Anthropic 用来表示服务端整体过载（`overloaded_error`） | 退避重试，看状态页；降低自己的并发作用不大 |
| 503 | 服务暂时不可用，可能是过载、维护或网关后端异常 | 退避重试，看状态页 |

区分的关键是响应头：429 时你的 `remaining` 通常已经见底；如果 `remaining` 还很充足却报 429，或者大批不相关的用户同时报限流，就更像是服务端在收紧或出了状况。完整的错误码含义和哪些该重试，可以对照 [大模型 API 错误码速查](/blog/ai-api-error-codes-400-to-529-cheatsheet)。

## 怎么判断是不是很多人同时被限流？

自己的限额没问题却持续 429，可以这样查：

1. 打开 cdnping 的 [挂了吗](/down) 页面，找到对应服务，比如 [Claude](/down/claude) 或 [ChatGPT](/down/chatgpt)。页面综合了官方状态页、每 5 分钟一次的 API 探测和用户一键报告，报告类型里有“限流”一项。如果最近一段时间“限流”报告明显变多，说明不只是你一个人。你也可以顺手提交一条。
2. 看 [API 状态](/status) 页，例如 [/status/anthropic](/status/anthropic) 和 [/status/openai](/status/openai)。它从 Cloudflare 边缘每 5 分钟探测官方 API 入口，并同步官方状态页事件，能看出入口本身是否异常。
3. 读官方状态页时注意事件等级和受影响组件，“部分模型错误率升高”这类 minor 事件经常就表现为 429 或 529 增多。怎么读状态页可以参考 [怎么看 OpenAI 状态页和 Claude 状态页](/blog/how-to-read-ai-status-pages)。

如果你用的是 Codex 这类按订阅计量的工具，遇到的“用量上限”和 API 的 429 是两套机制，额度何时恢复要看平台公告，可以参考 [Codex 额度重置时间线](/blog/codex-usage-limit-reset-timeline)。

## 一份可以照着走的检查表

遇到 429 时，按顺序检查：

1. 打印响应头和错误体，确认是 RPM、TPM 还是额度。
2. 错误码是 `insufficient_quota` 或提到 billing：停止重试，去控制台查余额和花费上限。
3. 有 `retry-after`：按它等，值为 0 时至少等 1 秒并加抖动。
4. 没有 `retry-after`：读 `x-ratelimit-reset-*` 或 `anthropic-ratelimit-*-reset`，再不行用指数退避，封顶重试次数。
5. 确认同一组织、同一 Key 下还有谁在调用，把它们算进总量。
6. 检查 SDK 自带重试，避免重试叠加。
7. 批量任务加双桶限速，token 估算包含 `max_tokens`。
8. 限额充足却仍然 429：去 [/down](/down) 看“限流”报告，去 [/status](/status) 看探测和官方事件，确认是不是服务端的问题。
