QVeris
事故运行手册

OpenAI 限流回退:先退避,再换路由

处理 OpenAI 限流,设置有上限的重试、明确的回退规则和可追溯的服务商切换。

发布日期:2026年3月16日 · 由 QVeris 团队制作

深蓝色限流退避与故障回退概念图:客户端先经过定时重试控制器,再访问主服务器或经核验的备用路径。
遇到临时限流时,遵循 Retry-After,并使用有上限、带抖动的退避重试;仅按明确规则切换至兼容备用路径,并记录实际服务商与模型。

摘要

读取原生信号

可用时保留状态、请求 ID 以及重试或限制 Header。

优先排队或退避

若截止时间允许,等待可能比改变执行语义更安全。

使用合格备用项

切换前检查模型、工具、区域、策略、数据与预算。

防止放大

在一个请求预算下协调 SDK、网关与工作流重试。

429 响应选择

限流可能作用于项目、组织、用户、模型、Deployment、请求速率或 Token 预算。必须先判断限制范围,再确认另一条路由是否真正独立。同一项目配额下的第二个模型可能因相同原因继续失败;而不同 Deployment 即使可用,如果改变了上下文窗口、工具结构或数据驻留约定,也不能直接视为合格备用项。

应根据父请求截止时间与工作负载契约,在快速失败、有界队列、抖动退避或已验证备用项之间选择。交互请求通常更适合快速、可解释地返回;异步任务只有在队列等待时间仍处于服务目标之内时才适合排队。记录 429 的作用范围、供应商提示、所选分支与剩余预算,才能在事后解释这次决策。

限流响应路径

路径 最适合 选择前验证
快速失败 交互负载无法等待或替代。 返回带重试指导与请求 ID 的受控错误。
排队 截止时间与队列容量已知。 限制队列年龄、深度、取消与租户公平。
退避 同一端点预计在预算内恢复。 遵循提示、增加抖动并在父截止时间停止。
备用项 独立合格路由保留契约。 不得跳配额、绕策略或重复工具动作。

每条分支的保护

全局预算

在一个父请求下计算所有尝试、等待、Token 与成本。

幂等性

重试前去重工具调用与下游动作。

配额策略

跨每个密钥与供应商执行租户与项目预算。

审计轨迹

记录 429、决策、备用项、尝试与恢复结果。

演练 429 Runbook

正常请求成功并不能证明故障切换设计可靠。应使用与生产环境相同的 SDK、网关和 Worker 重试设置,在真实并发条件下进行演练。关键结果不只是“最终拿到了响应”,而是恢复过程没有放大尝试次数、没有改变答案契约,并且始终处于端到端截止时间内。

  • 在项目、模型和 Token 预算等不同范围注入带与不带重试提示的 429。
  • 验证队列深度与等待上限、抖动、取消传播和请求截止时间。
  • 确认备用路由保留必需工具、结构化输出、数据区域与安全策略。
  • 测试多条主路由同时失败,避免共享备用项成为新的瓶颈。
  • 事故结束后复盘告警、Trace 证据、重复抑制与受控恢复过程。

集中限流决策

网关应标准化 429,同时保留供应商原始错误信息,检查父请求预算,并选择快速失败、排队、退避或合格备用项。这样应用只需提交一份请求契约,不必在每个 SDK 集成中分别嵌入不同的重试循环;租户公平性、并发上限和熔断状态也能在各服务之间保持一致。

必须明确唯一的重试负责人。如果供应商 SDK、网关和任务 Worker 都各自重试两次,一次用户请求就可能扩散成大量计费调用,反而加剧限流。每一层都应传递共享 Attempt Count、绝对截止时间和 Trace ID;预算耗尽后返回受控结果,而不是再启动一轮隐藏重试。

生产规则:绝不能把额外密钥或供应商当作规避既有限额的方式。备用路由用于在保持契约的前提下提高韧性,而不是跳配额。

重试期间保护外部动作

QVeris 可在 Agent 工作流中调用外部 API、工具、服务与实时数据。模型请求本身可能可以重复,但它已经触发的工具动作——例如下单、发消息或更新记录——未必可以重放。因此,应把推理尝试和外部动作作为两个独立状态管理,而不是每次遇到 429 都重放整个工作流。

将父 Trace、Action ID 与幂等密钥传播到工具层,并持久化动作处于已规划、已提交、已确认还是已拒绝状态。恢复时从最后一个已验证状态继续;如果数据时效性重要,还应重新读取实时数据。这样可避免限流故障切换悄悄重复真实世界副作用,或基于过期证据继续执行。

只有分类 429 后才执行故障切换

429 可能表示短时突发、账户配额、单模型容量或策略限制。存在 Retry-After 时应遵守它,使用共享 Attempt Budget,并且只回退到通过相同能力与质量门槛的模型。

有界故障切换 Loop
async def call_with_fallback(request, routes, max_attempts=2):
    errors = []
    for attempt, route in enumerate(routes[:max_attempts], start=1):
        try:
            return await route.client.call(
                model=route.model,
                request=request,
                timeout=route.timeout,
                idempotency_key=request.id,
            )
        except RateLimitError as error:
            errors.append((route.name, "429", error.retry_after))
            if error.retry_after and error.retry_after <= 2:
                await asyncio.sleep(error.retry_after)
            continue
    raise AllRoutesLimited(errors)
  • 不要重试认证、无效结构定义或确定性策略失败。
  • 防止 SDK 与网关各自独立放大重试次数。
  • 记录路由、Attempt、Retry-After、延迟、最终结果与防重复密钥。

实施参考已根据官方资料核验:OpenAI 限流。

常见问题

429 总应重试吗?

不应。仅在截止时间、预算与供应商指导内重试。

可以切换供应商吗?

只能切换到独立合格且策略批准的路由。

如何阻止重试风暴?

使用单一重试负责人、全局并发限制、抖动与熔断。

官方资料与延伸阅读

OpenAI 限流后的故障切换:先有序退避,再考虑换路由 | QVeris Guides