限流
先说结论:当前网关没有按 API Key 单独分配的请求额度,也没有按 userId 单独计数。入口请求频率按来源 IP 计数,模型并发按“渠道 + 模型”全局计数。你换一把密钥、换一台客户端,不会绕过模型级上限。
四层限制
| 层级 | 作用范围 | 默认值 | 超限表现 |
|---|---|---|---|
| 入口请求频率 | 来源 IP + official_llm | 120 次/小时,1000 次/天 | 429 RATE_LIMITED,带 Retry-After |
| 入口并发 | 来源 IP | 默认不额外限制 | 配置后才生效,429 RATE_LIMITED |
| 模型并发 | 渠道 + 模型,所有账号共享 | 由 model_pricing.max_concurrency 决定,未配置则不额外限制 | 先排队,排队超时后 502 CHANNEL_POOL_BUSY |
| 渠道凭证容量 | 单个渠道凭证 | 由凭证的 max_concurrency 决定 | 没有可用凭证时排队或 502 |
入口请求频率
默认值来自代码:
| 项目 | 默认值 | 配置项 |
|---|---|---|
| 每小时请求数 | 120 | OFFICIAL_LLM_IP_HOURLY_LIMIT |
| 每天请求数 | 1000 | OFFICIAL_LLM_IP_DAILY_LIMIT |
| 小时窗口 | 3600 秒 | IP_RATE_LIMIT_HOUR_WINDOW_MS |
| 天窗口 | 86400 秒 | IP_RATE_LIMIT_DAY_WINDOW_MS |
这些是代码默认值。运营可以通过系统配置 official_request_limits 调整全局规则或给单个 IP 设置覆盖,配置有约 5 秒缓存。实际以你收到的 Retry-After 和 retry_after 为准。
计数键是“来源 IP + official_llm”,不是 userId,也不是 API Key。同一个出口 IP 下多个账号或多把密钥会共享这一个桶。反过来,同一个人换网络出口,会被当成新的来源 IP。
所以本文不把它写成“每账号 120 次”,那不准确。
官方模型入口始终保留基础 IP 门禁,不受旧业务的 ENABLE_IP_RATE_LIMIT 开关影响。
哪些接口走这个桶
| 接口 | 是否走入口限流 |
|---|---|
POST /chat/completions | 是 |
POST /messages | 是 |
POST /web/search | 是 |
POST /web/fetch | 是 |
GET /models | 否 |
GET /images/models、GET /videos/models、GET /audio/models | 否 |
POST /images/generations、POST /videos/generations | 否 |
GET /videos/generations/:id | 否 |
POST /audio/transcriptions | 否(实时语音另有会话并发上限) |
图片、视频和音频文件转写目前没有挂独立的入口限流。它们仍然受余额预检、模型并发和渠道队列约束。实时语音有自己的并发会话上限。不要因为“没看到 429”就认为可以无限并发。
文本对话只有通过参数校验并进入执行的请求才计数;联网搜索和抓取在进入处理时计数。被 429 拒绝的请求不计数。
429 返回什么
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{
"success": false,
"code": "RATE_LIMITED",
"message": "请求过于频繁,请稍后再试",
"retry_after": 30
}
Retry-After 是秒数,和响应体里的 retry_after 一致。按这个时间等,不要立刻重试。
如果来源 IP 被运营加入黑名单,返回的是 429 IP_BLOCKED,不是 RATE_LIMITED。这种情况重试没有意义。
如果配置了入口并发,超过并发时也是 429,但 retry_after 通常是 5 秒。
模型级怎么限
model_pricing.max_concurrency 控制同一个“渠道 + 模型”同时有多少个任务处于 allocated 或 processing。这个计数不分账号,所有调用方共享。
- 字段为
null或没有配置:不额外限制模型并发。 - 字段有值:达到上限后,新请求进入渠道队列,不会直接失败。
- 排队超过
queue_timeout_seconds:返回 502CHANNEL_POOL_BUSY。 - 没有未尝试的可用凭证:返回 502
CHANNEL_POOL_EXHAUSTED。
queue_timeout_seconds 的默认值来自 channel_dispatch_defaults.queueTimeoutSeconds,代码默认是 90 秒;单个模型可以在 model_pricing.queue_timeout_seconds 覆盖。
模型并发之外,还有单个渠道凭证的 max_concurrency。一个模型可能配了多个凭证,总容量是这些凭证可用容量之和;单个凭证满了也会排队。
模型列表不会返回 max_concurrency。具体某模型的上限在模型详情或运营配置里看,文档不写死一个数字。
重试策略
推荐用指数退避加随机抖动,并且尊重服务端给的等待时间。
delay = min(cap, base × 2^attempt) + random(0, base)
如果响应里有 Retry-After:delay = max(delay, Retry-After)
| 状态 | 是否重试 | 建议 |
|---|---|---|
429 RATE_LIMITED | 可以 | 按 Retry-After 等;没有就给 1 秒起步,倍增,单次最多 60 秒,最多 5 次 |
429 UPSTREAM_REQUEST_FAILED,且 upstream_status: 429 | 可以 | 这是上游侧的限流,不是你的入口额度;同样退避,不要换一堆密钥猛打 |
502 CHANNEL_POOL_BUSY | 可以 | 说明模型队列满了;降低并发,等 5 到 30 秒再试 |
502 UPSTREAM_REQUEST_FAILED、503、504 | 可以 | 1 秒起步,倍增,单次最多 30 秒,最多 3 到 5 次 |
| 400、401、402、403、404、413 | 不要 | 改请求、密钥、余额、权限或资源,不要重试 |
409 REQUEST_IN_PROGRESS | 不要并发重发 | 等原请求结束;同一幂等键只保留一个在途请求 |
409 REQUEST_SETTLEMENT_PENDING | 不要重放 | 账单待核对,保留 request_id |
409 REQUEST_ALREADY_SETTLED | 不要 | 已经结算,重复调用没有意义 |
流式请求的重试边界
- 还没收到任何内容帧:可以用同一个幂等键重试。
- 已经收到部分内容:不要直接把整段请求再发一遍。先保留
request_id对账;重复请求可能产生第二笔真实调用。 - 客户端断开后,网关会停止继续等待上游。断开本身不是让你立刻重发的信号。
视频任务不要重新提交
视频任务 queued 或 processing 时,用 GET /videos/generations/:id 轮询,不要重新 POST /videos/generations。重新 POST 且换新幂等键会产生新任务和新扣费。
并发上不去怎么调
- 先确认是不是入口 429。看
code是RATE_LIMITED还是CHANNEL_POOL_BUSY。 - 如果是
CHANNEL_POOL_BUSY,说明模型队列满了。把客户端并发降到 2 到 4 起步,再逐步加。 - 如果排队经常接近 90 秒,说明并发已经超过模型容量,继续加客户端并发只会让更多请求超时。
- 如果是
CHANNEL_POOL_EXHAUSTED,不是你的并发问题,是渠道没有可用凭证,联系支持。 - 图片、视频和音频文件转写没有入口 429,不代表可以无限并发;模型队列和余额预检仍然会挡住。
不要把客户端并发直接设成几十或几百。模型级上限是全局共享的,你打满之后,自己的其他请求也会一起排队。
这不是 SLA
上面的数字是代码默认值和当前实现,不是可用性、延迟或吞吐承诺。运营可以调整限流配置,上游也可能有自己的限流。遇到 429 或 5xx,按退避策略处理,并以响应头为准。