gpt-5.6-sol 一直报 429 但 gpt-5.5 正常怎么办?两个限流桶要分开退避才行

gpt-5.6-sol 一直报 429 但 gpt-5.5 正常怎么办?两个限流桶要分开退避才行

上周三我在 Cline 里跑一个代码的 pipeline,把模型从 gpt-5.5 切到 gpt-5.6-sol 之后,同样的请求量,429 像机关枪一样往外蹦。一开始以为是额度用完了,充了钱发现还是一样。折腾半天才搞明白:gpt-5.6-sol 的 RPM(每分钟请求数)和 TPM(每分钟 Token 数)是两个独立的限流桶,而且这两个桶的阈值比 gpt-5.5 低不少。退避逻辑必须分桶处理,不能一个 sleep 打天下。另外 429 其实藏着两种完全不同的子类型:rate_limit_exceeded(等一等就好)和 insufficient_quota(钱花完了,重试一万次也没用),混淆这俩是最常见的浪费时间的坑。

为什么 gpt-5.6-sol 比 gpt-5.5 更容易触发 429

先看一个实际收到的报错:

复制代码
openai.RateLimitError: Error code: 429 -
{'error': {'message': 'Rate limit reached
for model `gpt-5.6-sol` on tokens per min
(TPM): Limit 10000, Used 9800, Requested
500. Please try again in 1.8s.',
'type': 'tokens', 'code':
'rate_limit_exceeded'}}

关键信息都在这条报错里了。type: 'tokens' 说明触发的是 TPM 桶,不是 RPM 桶。消息里直接给了三个数字:Limit 10000 / Used 9800 / Requested 500,还告诉你等 1.8s。注意 error.type 的实际值可能因 SDK 版本和代理层而有差异,建议以实际响应为准。

但问题来了------同样的账户 Tier,gpt-5.5 的 TPM 上限可能是 30000,gpt-5.6-sol 只给 10000。OpenAI 不同模型的速率限制是独立配置的,新模型上线初期限制通常更紧。你可以在 platform.openai.com/account/limits 看到自己当前 Tier 下每个模型的具体数字。

graph TD A[你的请求] --> B{429 错误} B -->|type: tokens| C[TPM 桶超限] B -->|type: requests| D[RPM/RPD 桶超限] B -->|code: insufficient_quota| E[余额不足] C --> F[读 x-ratelimit-reset-tokens<br/>等待后重试] D --> G[读 x-ratelimit-reset-requests<br/>等待后重试] E --> H[去充值,重试无效]

429 的两种子类型:先分清再动手

这步最重要,搞反了后面全白费。

子类型 error.type error.code 该怎么办 重试有用吗
速率超限 tokens 或 requests rate_limit_exceeded 按响应头等待后重试 ✅ 有用
配额耗尽 invalid_request_error insufficient_quota 去 platform.openai.com 充值 ❌ 重试无意义

注意:配额耗尽时,OpenAI 实际返回的 error.type 是 "invalid_request_error","insufficient_quota" 出现在 error.code 字段,两者不要混用。

实际报错长这样(配额耗尽版):

复制代码
openai.RateLimitError: Error code: 429 -
{'error': {'message': 'You exceeded your
current quota, please check your plan and
billing details.', 'type':
'invalid_request_error', 'code':
'insufficient_quota'}}

注意看------这条没有"Please try again in Xs",因为等多久都没用。第一次遇到的时候很容易傻乎乎地让重试循环跑好几轮,每轮等几十秒,白白浪费时间才报最终异常。

RPM 桶和 TPM 桶:两套响应头,两套退避策略

OpenAI 在每个响应(包括 429 响应)里塞了两组限流头,分别对应 RPM 和 TPM:

响应头 含义
x-ratelimit-limit-requests RPM 桶总容量
x-ratelimit-remaining-requests RPM 桶剩余
x-ratelimit-reset-requests RPM 桶距重置的剩余时间(字符串,如 "6m0s")
x-ratelimit-limit-tokens TPM 桶总容量
x-ratelimit-remaining-tokens TPM 桶剩余
x-ratelimit-reset-tokens TPM 桶距重置的剩余时间(字符串,如 "1.8s")

特别注意:x-ratelimit-reset-requests 和 x-ratelimit-reset-tokens 返回的是相对时间字符串 (如 "1s"、"1.8s"、"6m0s"),而不是 Unix 时间戳或整数秒数。不能直接把它塞进 time.sleep(),需要先解析。下面是一个简单的解析函数:

python 复制代码
import re

def parse_reset_time(reset_str: str) -> float:
    """把 '1.8s' / '6m0s' / '1m30s' 解析成秒数(float)"""
    if reset_str is None:
        return 0.0
    minutes = re.search(r'(\d+)m', reset_str)
    seconds = re.search(r'([\d.]+)s', reset_str)
    total = 0.0
    if minutes:
        total += float(minutes.group(1)) * 60
    if seconds:
        total += float(seconds.group(1))
    return total

gpt-5.6-sol 的坑在于:这两个桶的阈值都比 gpt-5.5 低,而且是独立触发的。你可能 RPM 还有余量,但 TPM 已经爆了------反过来也一样。所以退避策略不能只看一个维度。

先把这两个值读出来。在 openai-python v1.x 中,RateLimitError 的响应头通过 e.response.headers 访问(e.response 是一个 httpx.Response 对象):

python 复制代码
import openai

client = openai.OpenAI()
python 复制代码
print("RPM remaining:",
    resp.headers.get(
        "x-ratelimit-remaining-requests"))
print("TPM remaining:",
    resp.headers.get(
        "x-ratelimit-remaining-tokens"))
reset_tokens_str = resp.headers.get(
    "x-ratelimit-reset-tokens")
print("TPM reset in:",
    parse_reset_time(reset_tokens_str), "秒")

具体数字因 Tier 而异,去 platform.openai.com/account/limits 自己查一眼最准。

方案一:手动指数退避 + jitter(最小依赖)

最基础的做法,不引入额外库。关键改动:先判断 insufficient_quota 直接抛出不重试;读取响应头中的实际重置时间,取响应头等待时间与指数退避时间的较大值,再叠加 jitter 避免多个并发请求同时醒来撞车。

python 复制代码
import openai, time, random, re

def parse_reset_time(reset_str):
    if reset_str is None:
        return 0.0
    minutes = re.search(r'(\d+)m', reset_str)
    seconds = re.search(r'([\d.]+)s', reset_str)
    total = 0.0
    if minutes:
        total += float(minutes.group(1)) * 60
    if seconds:
        total += float(seconds.group(1))
    return total

client = openai.OpenAI()
python 复制代码
for attempt in range(6):
    try:
        resp = client.chat.completions.create(
            model="gpt-5.6-sol",
            messages=[{"role": "user",
                        "content": "hi"}])
        break
    except openai.RateLimitError as e:
        if "insufficient_quota" in str(e):
            raise  # 没钱了,别重试
        # 从 e.response.headers 读取实际重置时间
        # (openai-python v1.x,e.response 为 httpx.Response)
        headers = getattr(
            getattr(e, 'response', None),
            'headers', {}) or {}
        header_wait = max(
            parse_reset_time(
                headers.get(
                    "x-ratelimit-reset-tokens")),
            parse_reset_time(
                headers.get(
                    "x-ratelimit-reset-requests")))
        backoff = 2 ** attempt
        wait = max(header_wait, backoff)
        wait += random.uniform(0, wait * 0.5)
        time.sleep(wait)

random.uniform(0, wait * 0.5) 是比例式 jitter------在等待时间基础上随机叠加 0~50% 的偏移。没有它的话,你开 10 个并发,第一轮全等 1s,第二轮全等 2s------它们永远同时醒来同时撞限流墙,根本退不开。

方案二:tenacity 装饰器(生产环境推荐)

手写 for 循环容易漏边界条件。tenacity 可以简化重试逻辑:

python 复制代码
from tenacity import (retry,
    wait_exponential_jitter,
    stop_after_attempt,
    retry_if_exception)
import openai
python 复制代码
def is_retryable(e):
    # insufficient_quota 不重试;其他 RateLimitError 重试
    return (isinstance(e, openai.RateLimitError)
            and "insufficient_quota" not in str(e))

@retry(
    wait=wait_exponential_jitter(
        initial=1, max=60, jitter=5),
    stop=stop_after_attempt(6),
    retry=retry_if_exception(is_retryable))
def call_sol():
    return openai.OpenAI().chat.completions\
        .create(model="gpt-5.6-sol",
                messages=[{"role": "user",
                            "content": "hi"}])

这里有两点需要说明:

  1. is_retryable 已内置 :直接用 retry_if_exception(is_retryable) 替代 retry_if_exception_type,同时过滤掉 insufficient_quota,不需要再单独处理。

  2. jitter 参数含义 :wait_exponential_jitter 的 jitter=5 表示在指数退避基础上额外叠加 0~5 秒的均匀随机量 ,而不是按比例抖动。这与方案一的 random.uniform(0, wait * 0.5)(比例式 jitter,随请求量增大而增大)策略不同------前者抖动幅度固定,后者抖动幅度随退避时间等比放大。高并发场景下比例式 jitter 通常分散效果更好;如果并发数不多,固定 jitter 已经够用。另外,wait_exponential_jitter 的 multiplier 默认值为 1,退避曲线相对平缓,生产环境如果需要更激进的退避可以适当调大。

方案三:用聚合网关分散限流压力

上面两个方案解决的是单 Key 下的退避问题。如果业务量确实大,单一 Key 的 RPM/TPM 桶迟早不够用,这时候可以考虑通过 API 聚合平台把请求分发到多个通道。

OpenRouter 和 ofox.io 都支持 OpenAI 兼容协议,改一个 base_url 参数即可接入;OpenRouter 对部分模型收取溢价,ofox.io 宣称 0% 加价对齐官方价格(商业声明,建议自行核实当前定价)。配置示例如下:

python 复制代码
# OpenRouter 示例
client_or = openai.OpenAI(
    api_key="your-openrouter-key",
    base_url="https://openrouter.ai/api/v1")

# ofox.io 示例
client_fox = openai.OpenAI(
    api_key="your-ofox-key",
    base_url="https://api.ofox.io/v1")
python 复制代码
resp = client_fox.chat.completions.create(
    model="gpt-5.6-sol",
    messages=[{"role": "user",
                "content": "这段代码"}])

这类聚合网关的好处是后端有多条通道做负载均衡,单条通道触发限流时自动切到下一条,业务层不用关心退避逻辑。OpenRouter 也能做类似的事,看你更在意手续费还是模型覆盖面。具体后台功能和定价策略建议以各平台官网当前说明为准。

一个容易忽略的前置检查

社区 QA 里有人提到:如果你用的模型名不存在,某些 SDK 版本或代理层可能把 404 包装成 429 或其他错误码。排查 429 之前,先跑一下这个确认模型名有效:

python 复制代码
import openai
models = openai.OpenAI().models.list()
sol_exists = any(
    m.id == "gpt-5.6-sol" for m in models)
print(f"gpt-5.6-sol exists: {sol_exists}")

如果返回 False,那你的 429 可能根本不是限流问题。

常见问题 FAQ

Q: 429 报 insufficient_quota 和 rate_limit_exceeded 到底怎么区分?

看 error.code 字段。insufficient_quota 是账户余额/配额耗尽,去 platform.openai.com 充值;rate_limit_exceeded 是速率超限,等一等或降低并发就行。最直观的区别:后者的错误消息里会给 "Please try again in Xs",前者不会。另外注意,配额耗尽时 error.type 是 "invalid_request_error","insufficient_quota" 出现在 error.code,不要把两个字段的值混用。

Q: 怎么判断是 TPM 还是 RPM 触发了 429?

看 error.type。'tokens' 就是 TPM 桶爆了,'requests' 就是 RPM 或 RPD 桶爆了。错误消息里也会明确写 "tokens per min (TPM)" 或 "requests per day (RPD)"。实际值可能因 SDK 版本和代理层有差异,以实际响应为准。

Q: gpt-5.6-sol 的限流阈值具体是多少?

因 Tier 而异,OpenAI 没有公开每个模型每个 Tier 的完整限流表。只能在 platform.openai.com/account/limits 看自己账户的实际值,以官网显示为准。

Q: 多个并发请求同时重试怎么办?

加 jitter。指数退避的等待时间上叠加一个随机偏移量,让并发请求的唤醒时间错开。不加 jitter 的指数退避在高并发下基本等于没退避。具体可以用比例式(random.uniform(0, wait * 0.5))或固定量(tenacity 的 jitter=5),两种策略的区别见方案二的说明。

Q: 升级 Tier 需要什么条件?

Tier 不是单纯靠充值金额决定的,账户存在天数也是必要条件。根据 OpenAI 官方文档,各 Tier 门槛大致如下:

  • Tier 1:支付 $5 且账户存在 ≥ 7 天
  • Tier 2:累计消费 $50 且账户存在 ≥ 14 天
  • Tier 3~5:更高消费门槛 + 更长账户存在天数(具体数字 OpenAI 历史上多次调整,以 platform.openai.com/docs/guides/rate-limits 官网当前值为准)

也就是说,光充钱不够,账户还得"养"够对应天数才能升级。OpenAI 可能随时调整这些门槛,务必以官网为准。

小结

gpt-5.6-sol 报 429 大概率不是代码有 bug,而是这个模型的限流桶比 gpt-5.5 紧很多。排查顺序:先确认模型名存在,再区分 insufficient_quota 还是 rate_limit_exceeded,然后读响应头判断是 RPM 桶还是 TPM 桶(注意重置时间是字符串格式,需要解析),最后针对性地退避。生产环境用 tenacity + jitter,业务量大就上聚合网关做多通道负载均衡。目前 gpt-5.6-sol 的限流就是比 gpt-5.5 紧,先把退避逻辑写对,比什么都强。

相关推荐
菜_小_白2 小时前
codex
linux·vscode·ai
孙启超3 小时前
【FDE开发指南】第 5 课:中国市场的 FDE
人工智能·ai·职场技能
空心木偶☜3 小时前
Langgraph操作时常见的错误
python·ai·ai编程·langgraph
c萱4 小时前
AI产品经理——03Prompt Engineering提示词工程
ai·prompt·aigc·产品经理·ai编程·ai-native
xuhe24 小时前
Codex解决新模型无法使用/model选择的问题
linux·ai·codex
我才是银古8 小时前
AI 啃 DWG图纸:从二维到三维
ai·cad·dwg
codigger8 小时前
把 AI Agent 养在自己电脑上:从本地部署到远程接管的一份完整思路
ai·编程·#人工智能·#agent
空心木偶☜8 小时前
LangGraph
ai·ai编程·langgraph
空心木偶☜9 小时前
langgraph加上“循环”和“记忆”
ai·ai编程·langgraph