雨天小六

读懂 Codex(5.9):WebSocket 预热与连接复用

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

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

启动预热不仅做 TCP/WebSocket 握手,还发送一份 generate=false 的完整 Responses 请求并等待 Completed。成功后的连接、请求基线和 Response ID 可以被首个真实 Turn 消费;失败则不阻断 Session。

具体问题与启用条件

本节聚焦 startup prewarm 的构造、超时和交接。普通增量请求判断在 5.10,previous response 生命周期在 5.11。

条件来源决定字段或状态对本机制的影响
Provider/SessionResponses WebSocket enabled调度完整预热
无 WebSocket仅 auth prewarm不构建工具和 warmup request
首个 Regular Turn消费 startup handle获得 Ready/Unavailable/Cancelled

协议、类型与状态所有权

状态或协议所有者生命周期关键不变量
Startup handleSession启动到首个 Regular TurnAbortOnDrop 且只消费一次
预热 Turn/StepContext预热任务任务内使用真实模型与工具快照
预热 ModelClientSession预热任务→首 Turn交接后属于 Turn保留连接与 last response
预热 inference trace禁用 trace预热请求不冒充真实模型推理

机制调用链如下:

Session 初始化
→ 若 WS 可用则 spawn startup prewarm
→ 创建 startup TurnContext/StepContext 与 ToolRouter
→ 用空 Input 构造真实 Prompt
→ response.create(generate=false)
→ 等待 Completed
→ 首 Turn 在剩余超时内 resolve
→ 交接预热 ModelClientSession
启动预热到首个 Regular Turn 的交接
图 5.9-1:预热等待 Completed 后把同一 ModelClientSession 交给首 Turn。

机制怎样工作

预热发生在 run_turn 之前,因此必须自行捕获 StepContext、构建工具 Router,并用 Base Instructions、工具规格和空 Input 构造 Prompt。metadata 的 request kind 标为 Prewarm。prewarm_websocket 发送 response.create,将 generate 设为 false,并一直读到 Completed;这样 last request 和 last response receiver 都已建立。

首个 Regular Turn 解析 handle 时,超时不是从“开始等待”重新计时,而是用总预热 timeout 减去任务年龄,避免一个已经拖很久的启动任务再占用完整等待窗口。取消会 abort task;失败、join failure、超时都成为 Unavailable,正常 Turn 另行建连。若预热看到 426,它会直接激活 HTTP 回退,首 Turn 不再重复 WebSocket 握手。

Python 风格伪代码

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

async def startup_prewarm(session: Session) -> ModelTurnSession:
    turn = await session.make_startup_turn()
    step = await session.capture_step_context(turn, CancellationToken())
    prompt = build_prompt(input=[], tools=step.router.visible_specs,
                          instructions=session.base_instructions)
    client = session.model_client.new_turn_session()
    stream = await client.websocket_stream(prompt, generate=False, kind="prewarm")
    async for event in stream:
        if isinstance(event, Completed):
            return client
    raise StreamError("prewarm ended without completed")

async def consume_prewarm(handle: PrewarmHandle, turn_cancel: asyncio.Event):
    remaining = max(0.0, handle.timeout - handle.age())
    try:
        return await wait_task_or_cancel(handle.task, remaining, turn_cancel)
    except (TimeoutError, Exception):
        handle.task.cancel()
        return None  # 正常 Turn 继续

失败、取消与恢复

预热结果和首个真实请求的复用分支
图 5.9-2:Ready 只提供复用机会;真实请求仍需严格比较才能发空增量。
故障或边界已发生的状态对上层的结果能否直接重试恢复动作
预热超时任务可能部分建连Unavailable正常 Turn 可重试abort 预热任务
首 Turn 取消handle 尚未交接Cancelledabort 并结束 Turn
预热流错误连接/基线不可信Unavailable正常请求重新建立
HTTP 426Session 已禁 WS预热视为可继续不再试 WS首 Turn 直接 SSE

设计取舍与验证

发送 generate=false 比仅握手更贵,但能预先验证请求方言、工具 payload 和服务端状态,并为 previous response 建立基线。它增加启动并发任务和工具构建成本,所以被设计为尽力优化而不是正确性前置条件。禁用 inference trace 防止回放把预热误认为一次用户采样。

可验证契约证据方式预期结果
预热请求带 generate=false捕获首个 WS frame请求包含完整逻辑字段且不生成回答
预热 426 使首 Turn 直接 HTTPfallback 集成测试只有一次 WS 握手
预热失败不阻断 Turn让 warmup 失败后提供正常响应Turn 仍完成

Mini Codex 对照

Mini Codex 可先实现“仅握手”版本,但它不能声称支持预热 Response ID 复用。完整版本应返回同一个 Turn session,并用总 deadline 而不是第二个独立超时。

本节边界

预热已交给首 Turn。下一节说明何时可把完整请求压缩成 previous_response_id 加 Input delta。

评论


← 返回文章列表