WebSocket 是性能路径,不是完成任务的前提。握手返回 426 会立即回退;普通可重试流错误耗尽预算后也会回退。回退标志属于 ModelClient Session,后续 Turn 都直接使用 HTTP SSE。
具体问题与启用条件
本节只说明传输降级,不把 Provider 错误、模型错误和重试等待混在一起;完整错误分类见 5.21—5.23。
| 条件来源 | 决定字段或状态 | 对本机制的影响 |
|---|---|---|
| 初始条件 | Provider 支持 WS 且 fallback 未激活 | 优先尝试 WS |
| 直接信号 | HTTP 426 Upgrade Required | 不消耗流重试预算,立即 HTTP |
| 累计信号 | retryable error 且 retries >= stream_max_retries | 激活 Session 级回退 |
协议、类型与状态所有权
| 状态或协议 | 所有者 | 生命周期 | 关键不变量 |
|---|---|---|---|
| disable_websockets AtomicBool | ModelClientState | 整个 Session | 首次激活后保持 true |
| retry counter | run_sampling_request | 一次逻辑采样/传输阶段 | 回退后重置为 0 |
| WebsocketSession | ModelClientSession | Turn/可转移 | 回退时整体重置 |
机制调用链如下:
优先 WS
→ 426:FallbackToHttp
→ 或 retryable stream error
→ 在预算内退避重试 WS
→ 预算耗尽 try_switch_fallback_transport
→ 原子禁用 WS + 清连接/previous 基线
→ 重置 retries
→ 同一 Prompt 改走 HTTP SSE
机制怎样工作
握手 426 表示端点不接受当前 WebSocket 升级,客户端立即返回 FallbackToHttp,不再做注定相同的 WS 重试。其余可重试错误先由外层循环按 stream_max_retries 处理;达到上限时,try_switch_fallback_transport 调用 Session 级 force_http_fallback,并用空 WebsocketSession 替换当前状态。
回退后,采样循环将重试计数置零并返回继续,下一次 stream() 因 responses_websocket_enabled() 为 false 直接建立 SSE。这个原子标志位于共享 ModelClientState,因而新 Turn 也继承降级决定。回退时发 Warning;release 构建通常隐藏第一次瞬态 WS retry 的 StreamError,减少无意义闪烁,但不会隐藏真正的回退通知。
Python 风格伪代码
这段伪代码保留生产实现中会改变结果的状态、分支和异步边界;认证 SDK、遥测字段和 Rust 所有权样板被折叠为明确的领域对象。
async def open_model_stream(client: ModelTurnSession, prompt: Prompt):
if client.parent.websocket_enabled:
try:
return await client.open_websocket_stream(prompt)
except UpgradeRequired:
client.activate_http_fallback()
return await client.open_sse_stream(prompt)
async def retry_or_fallback(state: RetryState, error: Exception):
if state.retries >= state.max_retries and state.client.can_activate_fallback():
await state.ui.warning(f"Falling back to HTTPS: {error}")
state.client.activate_http_fallback() # 清 WS 与 previous state
state.retries = 0
return
if state.retries < state.max_retries:
state.retries += 1
await asyncio.sleep(error.retry_after or jittered_backoff(state.retries))
return
raise error
失败、取消与恢复
| 故障或边界 | 已发生的状态 | 对上层的结果 | 能否直接重试 | 恢复动作 |
|---|---|---|---|---|
| 426 握手拒绝 | 未发送模型请求 | 立即 HTTP | 不重试 WS | 激活粘滞回退 |
| WS 可重试错误未耗尽 | 连接可能已重置 | 仍在 WS 路径 | 是 | 退避后重连/重发 |
| WS 重试耗尽 | 重试预算用完 | Warning + HTTP | 按 HTTP 新预算 | 清 previous 基线 |
| HTTP 也失败 | fallback 已保持 | 返回分类错误 | 依 HTTP 错误 | 不会自动恢复 WS |
设计取舍与验证
Session 级粘滞回退避免每个 Turn 重复支付失败握手与重试成本,代价是网络条件恢复后本 Session 不会自动升级回 WS。更激进的探测恢复需要冷却计时、并发探针和重复请求保护,当前实现选择稳定完成任务。
| 可验证契约 | 证据方式 | 预期结果 |
|---|---|---|
| 426 只尝试一次 WS | 启动预热 + 首 Turn 请求计数 | 一次 GET、一次 HTTP POST |
| 预算耗尽后同请求转 HTTP | 故意让 WS 失败 | 初次+N 次 WS 后一个 POST |
| 第二 Turn 不再试 WS | 连续两个 Turn | WS 次数只来自第一 Turn |
Mini Codex 对照
Mini Codex 应让 fallback flag 属于长期客户端,并在降级时清除连接、last request 和 last response。可以不隐藏首个重试通知,但不能在每个 Turn 重置回退。
本节边界
传输层现在能稳定交付统一事件。5.13 开始进入事件语义:Created、Item、Delta 与 Completed 怎样组成一次响应。
评论
登录后即可评论