雨天小六

读懂 Codex(5.21):Rate Limit、Retry-After 与指数退避

· 更新于 2026-08-02 · 专栏:读懂 Codex

#Codex#Agent Runtime#Responses API#流式协议#软件架构

限流快照和重试延迟是两条不同数据流:前者持续告诉 UI 额度窗口,后者只控制一次失败后的等待。Codex 优先采用错误携带的 delay,否则使用带抖动的指数退避。

具体问题与启用条件

本节拆开 rate-limit telemetry、服务端等待提示和外层重试计数;429 用量耗尽等不可重试错误的分类在 5.22。

条件来源决定字段或状态对本机制的影响
HTTP Headerx--primary/secondary-、credits解析一个或多个 RateLimitSnapshot
WS 事件codex.rate_limits流中更新快照
failed eventcode=rate_limit_exceeded 且 message 含 seconds/ms解析 retry delay

协议、类型与状态所有权

状态或协议所有者生命周期关键不变量
RateLimitSnapshotSession state跨请求更新按 limit_id 保存额度视图
should_emit_token_count一次采样到工具 drain 后合并 usage 与 rate limit UI 更新
retry counterrun_sampling_request一次逻辑请求最多 stream_max_retries
retry delayCodexErr单次错误服务端值优先

机制调用链如下:

响应 Header 或 codex.rate_limits
→ parse snapshot
→ Session.record_rate_limits_info
→ 延后 TokenCount/limit UI 事件

失败 rate_limit_exceeded
→ 从 message 解析 seconds/ms delay
→ CodexErr.with_retry_delay
→ 外层检查 retryable/budget
→ delay 或 jittered backoff
→ 重建 Prompt 并重试
限流快照与 Token Usage 的合并展示
图 5.21-1:Session 先更新状态,等 Completed 和工具等待结束后统一发客户端事件。

机制怎样工作

Header parser支持默认 codex 限额族,也扫描其他 x-<limit>-primary-used-percent 前缀并规范化为 limit_id;每个快照可含 primary、secondary、credits、窗口分钟和 reset timestamp。WebSocket 的 codex.rate_limits 事件走专用解析器。Core 收到快照先更新状态,不立刻发 TokenCount,因为同一 Completed 还会带 usage;等 pending tools 结束后统一发,避免重复或在等待用户输入时错误显示进度。

response.failed 只有 code 为 rate_limit_exceeded 时尝试从 message 解析 s/second/ms;它产生 Retryable error 并保留 delay。重试函数先检查预算,再用 err.retry_delay(),没有才调用带抖动 backoff。注意 HTTP 429 若 body 表示 usage_limit_reached 会映射成不可重试 UsageLimitReached;一般 429 在底层请求重试耗尽后变成 RetryLimit,也不是无限外层重试。

Python 风格伪代码

这段伪代码保留生产实现中会改变结果的状态、分支和异步边界;认证 SDK、遥测字段和 Rust 所有权样板被折叠为明确的领域对象。

async def handle_rate_limit_event(snapshot: RateLimitSnapshot, sample: SampleState):
    await session.rate_limits.update(snapshot.limit_id or "codex", snapshot)
    sample.emit_token_count_later = True

async def retry_stream(error: CodexError, retries: int, maximum: int) -> int:
    if not error.retryable or retries >= maximum:
        raise error
    next_retry = retries + 1
    delay = error.retry_delay
    if delay is None:
        delay = jittered_exponential_backoff(next_retry)
    await ui.maybe_emit_reconnecting(next_retry, maximum)
    await asyncio.sleep(delay)
    return next_retry

def parse_wire_retry_delay(code: str | None, message: str) -> float | None:
    if code != "rate_limit_exceeded":
        return None
    return parse_seconds_or_milliseconds(message)

失败、取消与恢复

服务端 Retry-After 与本地退避的选择
图 5.21-2:等待策略只在错误可重试且预算未耗尽时运行。
故障或边界已发生的状态对上层的结果能否直接重试恢复动作
限流 Header 缺失/非法其他响应仍有效忽略对应字段不适用等待后续快照
Retry message 格式不认识错误仍 Retryable使用本地 backoff抖动退避
usage_limit_reached账户额度已耗尽UsageLimitReached + reset 信息等待额度恢复/用户处理
重试预算耗尽已等待和重发多次回退 WS 或返回错误由 5.12/5.22 决定

设计取舍与验证

服务端 delay 能减少无效流量,本地抖动避免大量客户端同步重试。把限流展示延迟到 usage 合并点减少事件噪声,却让 UI 不是在 Header 到达的瞬间更新。多 limit_id 支持增加解析复杂度,但能表达不同模型或产品额度窗口。

可验证契约证据方式预期结果
秒和毫秒 delay 都可解析try_parse_retry_after 单元测试Duration 精确匹配
多个 Header family 生成多个快照rate_limits parser 测试limit_id 稳定规范化
服务端 delay 覆盖 backoffresponses retry 测试等待采用错误携带值

Mini Codex 对照

Mini Codex 至少应把 RetryPolicy 和 RateLimitStore 分成两个对象。测试时注入 sleep 和随机源,避免真实等待;服务端 delay 要设上限,防止异常值永久挂起。

本节边界

本节只决定何时等和怎样展示额度。5.22 将 Transport、API、Provider 与模型语义错误归一为上层可判断的 CodexErr。

评论


← 返回文章列表