限流与并发
并发限制的语义、模型列表端点的限流,以及计费口径。
生成类端点没有 QPS 限制
/v1/chat/completions、/v1/images/generations、/v1/videos 这些生成类端点
不受每分钟请求数限制。约束它们的是并发数——同一时刻你能有多少个请求在途。
这两者的区别很重要。QPS 限制下,你应该在收到 429 后退避一段时间;而并发限制下, 正确的做法是等已有请求返回。盲目退避会让吞吐白白降低。
超限时返回:
{
"error": {
"type": "rate_limit_error",
"code": "concurrency_limit",
"message": "concurrency limit reached",
"request_id": "..."
},
"request_id": "..."
}HTTP 状态 429,且不带 Retry-After。
并发额度从哪来
按优先级取第一个命中的:
- 账号上的并发覆盖值(管理员单独配置)
- 套餐或用户组自带的并发额度
任一层配成 0 或未配置,即为不限并发。具体额度在控制台可以看到。
重试跨上游只占一个名额
一次请求内部如果发生了上游故障转移(网关自动换一家上游重试),整个过程只占用一个 并发名额,不会因为内部重试而多扣。
建议的客户端做法
用信号量控制并发,而不是靠捕获 429:
import asyncio
sem = asyncio.Semaphore(8) # 设为你的并发额度,或略低
async def call(payload):
async with sem:
return await client.chat.completions.create(**payload)这样把并发控制在客户端做掉,基本不会触发 concurrency_limit。真的收到了,短暂等待
(1 秒左右)后重试即可,不需要指数退避。
模型列表端点有 QPS 限制
/v1/models 和 /anthropic/v1/models 受 240 次 / 60 秒的用户级限流,固定窗口。
响应头:
| 头 | 说明 |
|---|---|
X-RateLimit-Limit | 恒为 240 |
X-RateLimit-Remaining | 本窗口剩余次数 |
Remaining 可能是负数
它是 限额 - 已用计数,而计数在判断之前就已递增,超限后会继续往下减。所以你可能看到
-3 这样的值。判断时用 <= 0 而不是 == 0。
超限响应体格式特殊(不是统一信封):
{"error": "rate limit exceeded", "scope": "user"}实践上这个额度足够启动时拉一次并缓存。不建议每次业务请求前都查模型列表。
请求体大小
| 端点类型 | 上限 |
|---|---|
对话类(/v1/chat/completions、/v1/responses) | 100 MiB |
| multipart 类(图像编辑、音频上传) | 32 MiB |
超限返回 413 + request_body_too_large。
传大图做视觉理解时注意:base64 编码会让体积膨胀约 33%,一张 30 MB 的原图编码后接近 40 MB。
超时
| 场景 | 上限 |
|---|---|
| 非流式请求 | 120 秒 |
| 流式请求 | 不受上面这条限制 |
| 图像端点内部轮询异步上游 | 120 秒 |
流式请求不受 120 秒总时长限制。 如果你的任务本来就慢(长文生成、深度推理), 用流式能绕开这个上限,顺便还能更早拿到首字。
流式另有几条独立的超时保护:首字超时、总时长超时、中途静默超时,触发时返回 504
并带对应的 code。详见错误码。
计费口径
按用量计费
计费维度取决于端点:
| 端点 | 计费依据 |
|---|---|
| 对话、嵌入 | 输入 / 输出 token |
| 图像 | 输出图片张数 |
| 视频 | 上游返回的实际时长(秒) |
| 语音合成 | 请求中的输入字符数 |
语音合成这条值得单独注意:它按你发出去的字符数算,而不是上游报告的用量。所以在 发请求前你就能准确预估成本。
视频完成时才计费
POST /v1/videos 提交时不计费,只做一次余额预检。等任务成功完成后,按上游返回的
实际秒数结算。
任务失败不计费,无需申请退款。
上游 4xx 不计费
上游返回 4xx 时,网关透传错误但不产生费用。
服务维护期
实例进入排空(drain)状态时,所有非健康检查路径返回:
{"error": "instance draining"}HTTP 状态 503。这是短暂的滚动更新窗口,重试即可恢复。注意这个响应体也不遵循统一 错误信封。
错误码
完整错误码表,以及哪些该重试、哪些不该。
查询余额与额度 GET
一次返回所有可能让下一个请求被拒绝的因素:账户余额、订阅套餐的配额窗口、 以及当前 Key 自己的花费上限。 这三者存放在不同的地方(余额在数据库、套餐配额在 Redis 计数器、Key 花费是 对调用日志的聚合),拆成三个接口只会让每个客户端都调三次再拼起来,所以合并 在这里。 ### 金额一律是字符串 账本精度是 `numeric(20,8)`,用浮点数往返会丢掉在「已花费」和「上限」之间做 比较时才会显现的位数。所有金额字段都是十进制字符串,单位见 `currency`。 ### 套餐字段何时缺席 `plan.has_active_plan` 为 `false` 时,其余套餐字段一并省略,表示当前按余额 计费。配额计数器所在的 Redis 短暂不可用时也会走这个分支——此时余额仍然准确, 接口不会整体失败。 `api_key` 在调用方使用 OAuth 设备令牌(CLI / 桌面端登录)时为 `null`: 那不是 API Key,没有 Key 级别的上限可报。