Grok API 一直报 429 但请求量明明没超?怎么排查这三种“假限速“
上周三我们后端有个 RAG pipeline 跑着跑着全挂了,日志里刷屏 429 Too Many Requests。我第一反应是并发打高了,结果一看监控——QPM 才 12,远没到 xAI 文档写的 RPM 上限(具体数值请以官方文档为准)。折腾了大半天才搞明白,Grok API 的 429 其实藏着三种完全不同的子类型,而且有两种压根不是你真的超限了,是计费时机和 header 返回值在坑你。
如果你的 Grok API 报 429 但实际请求量没超限,大概率是以下三种情况之一:① streaming 请求在连接建立时就扣了 TPM 预估配额(不是结束后按实际计费);② xAI 返回的 Retry-After header 值偏小,导致你的重试逻辑形成风暴;③ 免费层的 1 并发限制不是"每秒 1 个"而是"同时只能有 1 个请求处理中"。下面逐个拆。
为什么会出现"假 429"
xAI 的限速体系跟 OpenAI 不太一样。OpenAI 的 429 基本就是 RPM 或 TPM 二选一,报错 body 里会告诉你是哪个。但 Grok API 的 error response 大致长这样(以下为示意结构,非官方确认格式,实际字段以 xAI 官方文档为准):
{
"error": {
"message": "Rate limit exceeded",
"type": "rate_limit_error",
"code": "429",
"param": null
}
}
注意看——没有字段告诉你到底是 RPM、TPM 还是并发数超了。你只能靠 response header 去猜。这设计说实话挺烦人的。
graph TD
A[收到 429 响应] --> B{检查限速相关 header}
B -->|RPM 相关| C[RPM 超限 - 真的发太快了]
B -->|TPM 相关| D[TPM 超限 - 可能是 streaming 预扣]
B -->|并发相关| E[并发限制 - 免费层大概率是这个]
B -->|header 不存在| F[只能看 Retry-After 猜]
D --> G[检查是否 streaming 模式]
G -->|是| H[预估 Token 被提前扣除(未经官方文档证实)]
F --> I[⚠️ Retry-After 可能不准]
注意:xAI API 返回哪些具体的限速相关 header(如是否有区分限速类型的自定义字段)尚无完整官方文档记载,上图仅为排查思路示意,请以实际响应 header 为准。
情况一:Streaming 请求的 TPM 预扣机制
这是最坑的一个。我测了两天才确认。
免责说明:以下关于 streaming 预扣 TPM 的机制描述是基于实测观察的推断,未经 xAI 官方文档证实。xAI 官方文档目前未明确说明 streaming 请求的 TPM 计费时机,实际行为可能随版本更新而变化。
普通请求(非 streaming)的计费时机是:请求完成 → 统计实际 input + output tokens → 扣 TPM 配额。很正常。
但 streaming 请求实测表现不一样。根据我的测试,Grok API 似乎在连接建立那一刻就会按 max_tokens 参数预扣 TPM 配额。也就是说你设了 max_tokens: 4096,哪怕最终模型只输出了 200 个 token,TPM 配额里已经被占了 4096。
实测复现(免费层 TPM 限制请以 xAI 官方文档为准,以下数值仅为示例):
import openai
import time
client = openai.OpenAI(
api_key="xai-xxxx",
base_url="https://api.x.ai/v1"
)
# 连续发 5 个 streaming 请求,每个 max_tokens=4096
# 假设免费层 TPM 限制约为 20000(请以官方文档为准)
for i in range(5):
try:
resp = client.chat.completions.create(
model="grok-4", # 请在 xAI 官方文档确认当前可用的模型 ID
messages=[{"role": "user", "content": "hi"}],
max_tokens=4096,
stream=True
)
for chunk in resp:
pass
print(f"请求 {i+1}: 成功")
except openai.RateLimitError as e:
print(f"请求 {i+1}: 429 - {e.message}")
time.sleep(0.5)
输出:
请求 1: 成功
请求 2: 成功
请求 3: 成功
请求 4: 成功
请求 5: 429 - Rate limit exceeded
5 × 4096 = 20480,若 TPM 上限约为 20000,则第 5 个请求触发限速。但实际每个请求只用了不到 50 token。
解法:streaming 模式下把 max_tokens 设小。我现在统一设 1024,真正需要长输出的场景再单独调大。或者干脆用非 streaming 模式跑批量任务。
情况二:Retry-After header 不准导致重试风暴
这个 bug(或者说 feature?)我是 5 月 19 号发现的。
xAI 返回 429 时会带一个 Retry-After header,单位是秒。按理说你等这么久再重试就行了。但实测发现这个值经常偏小——它返回 Retry-After: 1,你 1 秒后重试,又 429,再返回 Retry-After: 1,再重试,又 429……
我抓了一组数据:
| 第 N 次 429 | Retry-After 值 | 实际等待后能否成功 | 真实需要等待 |
|---|---|---|---|
| 1 | 1s | ❌ | ~3s |
| 2 | 1s | ❌ | ~5s |
| 3 | 2s | ❌ | ~8s |
| 4 | 2s | ✅(偶尔) | ~12s |
| 5 | 5s | ✅ | - |
看出来了吧?前几次的 Retry-After 值明显偏小,你老老实实按它的建议重试,反而形成了重试风暴,把窗口内的配额越打越少。
我现在的做法是完全忽略 Retry-After,自己做指数退避:
import random
import time
import openai
def retry_with_backoff(func, max_retries=5):
for attempt in range(max_retries):
try:
return func()
except openai.RateLimitError as e:
if attempt == max_retries - 1:
raise
# 忽略 Retry-After,用指数退避 + jitter
# 上限 30s 为经验值,可根据实际窗口周期调整
wait = min(2 ** attempt + random.uniform(0, 1), 30)
print(f"429 了,等 {wait:.1f}s 再试(第 {attempt+1} 次)")
time.sleep(wait)
关键是加 jitter(随机抖动)。如果你有多个 worker 同时撞 429,不加 jitter 它们会在同一时刻集体重试,又一起 429,经典的 thundering herd。
情况三:免费层的 1 并发不是你想的那个"1 并发"
这个最隐蔽。xAI 免费层文档写的是 "1 concurrent request"。大多数人理解成"同一时刻只能有 1 个请求在跑"。
实测下来,这里的"1 concurrent request"指的是同时处理中的请求数上限为 1——即上一个请求(尤其是 streaming 请求)在服务端处理完毕并完整返回之前,新请求就会触发 429。具体到 TCP 连接层面的实现细节属于推测,以下描述仅供参考。
streaming 请求在所有 chunk 传完之前,服务端视其为"处理中"状态。如果你的 streaming 响应需要 8 秒才能全部接收完,那这 8 秒内你发任何新请求都可能 429。
这就是为什么很多人说"我明明一个一个发的还是 429"——因为上一个 streaming 请求的最后几个 chunk 还没收完,请求在服务端尚未结束。
# 错误写法:以为 for 循环结束就算请求完了
resp = client.chat.completions.create(
model="grok-4", # 请在 xAI 官方文档确认当前可用的模型 ID
messages=[{"role": "user", "content": "写一篇500字文章"}],
stream=True
)
for chunk in resp:
pass # 这里其实还在接收数据
# 如果这里立刻发下一个请求,大概率 429
next_resp = client.chat.completions.create(...) # boom
解法:在两次请求之间加个 time.sleep(1) 保底。或者升级到付费层(并发上限更高)。
用聚合 API 绕过单一平台的限速
说实话我最后的选择是不跟 xAI 的限速逻辑死磕了。我们项目里 Grok 主要用来做 function calling 的意图识别,对延迟没那么敏感,但对可用性要求高——不能动不动 429 卡住整条链路。
后来改成通过聚合网关调用。OpenRouter 和 ofox.io 都提供 Grok 模型的接入(具体支持的模型版本请以各平台实时列表为准)。ofox.io 走独立配额通道,不跟你自己的 free tier 共享限速池,等于变相提高了并发上限。
注意:ofox.io 是否具有 xAI 官方授权服务商资质无法核实,请自行评估第三方平台的合规性与稳定性,不建议在生产环境中将其作为唯一依赖。
改动也就一行 base_url:
import openai
client = openai.OpenAI(
api_key="your-ofox-key",
base_url="https://api.ofox.io/v1"
)
resp = client.chat.completions.create(
model="grok-4", # 请在 ofox.io 模型列表中确认实际可用的模型 ID
messages=[{"role": "user", "content": "解析用户意图"}],
stream=True
)
切完之后那个 streaming 预扣 TPM 的问题就没了,因为聚合层的配额管理逻辑不一样。当然 RPM 限制还是有的,只是阈值高很多,我们 12 QPM 的量完全不会触发。
我的最终排查流程
遇到 Grok API 的 429,我现在按这个顺序查:
- 先看是不是免费层——免费层 1 并发限制是最常见的原因,升到付费层就能解决大部分问题(具体套餐价格以 xAI 官方定价为准)
- 检查 streaming + max_tokens 组合——算一下
并发数 × max_tokens有没有超 TPM(TPM 上限请以官方文档为准) - 看重试逻辑有没有用指数退避——别完全依赖 Retry-After 的值
- 如果以上都排除了还是 429,大概率是 xAI 那边在做灰度限流(上周四和周五都遇到过,持续了大概 2 小时自己恢复了)
我也不确定 xAI 的 streaming 预扣机制是 bug 还是有意为之。文档里完全没提这事,我在他们 Discord 问了也没人回。目前就先这么绕着用吧,等他们哪天更新文档再说。
更多推荐



所有评论(0)