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 輸出響應頭,請求體只要一個很短的輸入,不會消耗多少額度:
# 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 就聽它的,沒有再用重置時間,兩樣都沒有才用指數退避。幾個容易出錯的細節:
- 單位。
Retry-After按 HTTP 規範可以是秒數,也可以是一個 HTTP 日期。大模型 API 一般給秒數,但解析程式碼最好兩種都支援。 - 值為 0 或很小。有時會拿到
retry-after: 0,立刻重試大機率還是 429。給一個下限,比如 1 秒,再加隨機抖動。 - 頭缺失。一些閘道器、代理或 SDK 封裝會丟掉這個頭。這時退回到
x-ratelimit-reset-*,再不行就指數退避:1、2、4、8 秒,封頂 60 秒左右,每次加 0 到 1 秒的隨機量,避免多個程序同時醒來再一起撞牆。 - 重試次數要封頂。3 到 5 次後仍然 429,就不該繼續重試,應該報出來,交給上層處理或者排隊。
- 額度類 429 不重試。錯誤碼是
insufficient_quota或者資訊裡提到 billing、credit,直接失敗並提示,不然只是在浪費時間。
下面是一段 Python 的處理函式,不依賴特定 SDK:
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 數,限速器也要兩個桶一起算:
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 提示快取怎麼用才省錢。
- 把輕任務分到另一組模型。分類、格式化這類任務用輕量模型,它們通常有單獨的限額,主模型的 TPM 留給難任務。思路見 多模型路由怎麼做。
- 申請提高限額。多數平臺的限額隨用量等級提升,控制檯裡可以看到當前等級和升級條件。走正規渠道申請,不要靠多開賬號、輪換 Key 來疊加限額,這違反大多數服務條款。
429、529 和 503 有什麼區別?
這一節單獨列出來,是因為很多人把服務端過載當成自己超限,去改程式碼、降併發,其實問題不在自己這邊。
| 狀態碼 | 誰的問題 | 怎麼處理 |
|---|---|---|
| 429 | 你的呼叫超過了分給你的配額 | 按 Retry-After 等待,客戶端限速 |
| 529 | Anthropic 用來表示服務端整體過載(overloaded_error) | 退避重試,看狀態頁;降低自己的併發作用不大 |
| 503 | 服務暫時不可用,可能是過載、維護或閘道器後端異常 | 退避重試,看狀態頁 |
區分的關鍵是響應頭:429 時你的 remaining 通常已經見底;如果 remaining 還很充足卻報 429,或者大批不相關的使用者同時報限流,就更像是服務端在收緊或出了狀況。完整的錯誤碼含義和哪些該重試,可以對照 大模型 API 錯誤碼速查。
怎麼判斷是不是很多人同時被限流?
自己的限額沒問題卻持續 429,可以這樣查:
- 開啟 cdnping 的 掛了嗎 頁面,找到對應服務,比如 Claude 或 ChatGPT。頁面綜合了官方狀態頁、每 5 分鐘一次的 API 探測和使用者一鍵報告,報告型別裡有“限流”一項。如果最近一段時間“限流”報告明顯變多,說明不只是你一個人。你也可以順手提交一條。
- 看 API 狀態 頁,例如 /status/anthropic 和 /status/openai。它從 Cloudflare 邊緣每 5 分鐘探測官方 API 入口,並同步官方狀態頁事件,能看出入口本身是否異常。
- 讀官方狀態頁時注意事件等級和受影響元件,“部分模型錯誤率升高”這類 minor 事件經常就表現為 429 或 529 增多。怎麼讀狀態頁可以參考 怎麼看 OpenAI 狀態頁和 Claude 狀態頁。
如果你用的是 Codex 這類按訂閱計量的工具,遇到的“用量上限”和 API 的 429 是兩套機制,額度何時恢復要看平臺公告,可以參考 Codex 額度重置時間線。
一份可以照著走的檢查表
遇到 429 時,按順序檢查:
- 列印響應頭和錯誤體,確認是 RPM、TPM 還是額度。
- 錯誤碼是
insufficient_quota或提到 billing:停止重試,去控制檯查餘額和花費上限。 - 有
retry-after:按它等,值為 0 時至少等 1 秒並加抖動。 - 沒有
retry-after:讀x-ratelimit-reset-*或anthropic-ratelimit-*-reset,再不行用指數退避,封頂重試次數。 - 確認同一組織、同一 Key 下還有誰在呼叫,把它們算進總量。
- 檢查 SDK 自帶重試,避免重試疊加。
- 批次任務加雙桶限速,token 估算包含
max_tokens。 - 限額充足卻仍然 429:去 /down 看“限流”報告,去 /status 看探測和官方事件,確認是不是服務端的問題。