Skip to content

[Bug] Codex Responses WebSocket (ctx_pool) is unstable: frequent disconnects, 1013 busy, continuation unavailable #1769

@wzian2682-droid

Description

@wzian2682-droid

问题描述

在使用 Codex Desktop 对接 sub2api 的 OpenAI Responses 链路时,连续会话的稳定性和连续性存在明显问题,主要分为两类:

  1. WS (ctx_pool) 经常中途断流/重连
  2. HTTP /responses` 也会出现“像重新回答/上下文重复”的感觉
    这不是“没有启用 WS”的问题,而是在已经启用后,实际运行中仍会频繁暴露会话恢复与续接上的不稳定。

当前现象

A. WebSocket 链路

客户端经常出现:

  • stream disconnected before completion: websocket closed by server before response.completed
  • 持续 Reconnecting...
  • 回答未完成就中断
    对应服务端日志中常见:
  • 1013 upstream websocket is busy, please retry later
  • context deadline exceeded
  • upstream continuation connection is unavailable; please restart the conversation
  • preferred connection unavailable
  • �roken pipe

B. HTTP /responses 链路

即使不使用 WS,也仍然会感觉:

  • 某些 turn 像“重新回答了一遍”
  • 连续对话的上下文衔接不如预期
  • 有时会出现明显的“重复解释 / 像重新开了一轮”体验

关键代码/行为判断

1. WS (ctx_pool) 存在自动恢复 / 重放逻辑

从 openai_ws_forwarder.go 和 openai_gateway_service.go 看,当前实现中存在这些恢复行为:

  • ingress_ws_prev_response_recovery ... action=drop_previous_response_id retry=1
  • ingress_ws_preflight_ping_recovery ... action=drop_previous_response_id_retry

econnect_prev_response_recovery ... action=drop_previous_response_id retry=1

  • ingress_ws_function_call_output_prev_infer ... action=set_previous_response_id
    也就是说,当 continuation / previous_response_id 续接失败时,当前实现会尝试:
  • 自动回填 previous_response_id
  • 或者在某些失败场景下去掉 previous_response_id 后重试当前 turn
    这会带来一个副作用:

当客户端期望“继续上一轮”时,服务端实际上可能把当前 turn 以“新一轮 create”形式再发一次,导致用户体感上出现上下文重复、重新解释、重复回复。

2. HTTP /responses 会主动过滤 previous_response_id

从 openai_gateway_service.go 看,当前实现明确有这样一条规则:

仅在 WSv2 模式保留 previous_response_id,其他模式(HTTP/WSv1)统一过滤。
也就是说,在 HTTP /responses` 下,客户端即使带了 previous_response_id,也会被删除。
这会导致:

  • 连续会话不能按 OpenAI Responses 的原生方式续接
  • 上游更像收到一条新的请求而不是上一轮的 continuation
  • 从体感上看,就会像“重新回答”“上下文不够连贯”“有时像重复”

3. HTTP 流式终止不完整时,也会放大重复感

HTTP 流式路径里还能看到:

  • stream usage incomplete: missing terminal event
  • stream usage incomplete after disconnect
    这类问题本身不一定由服务端重放整轮,但它很容易诱发上层/客户端再次发起请求,最终让用户感觉像“回答又来了一遍”。

我认为当前最值得优化的方向

A. 给 WS 恢复逻辑增加更明确的开关/模式

现在看起来虽然已经有:

  • gateway.openai_ws.ingress_previous_response_recovery_enabled
    但还不够,因为某些 preflight_ping_recovery 路径仍会走 drop_previous_response_id_retry。
    建议:
  1. 增加一个更清晰的总开关,例如:
    • 完全禁止“去掉 previous_response_id 后重试当前 turn”
  2. 把 previous_response_not_found recovery 和 preflight_ping recovery 分成独立开关
  3. 提供 fail-close 模式:
    • 一旦 continuation 失效,就直接报错给客户端,而不是尝试改写当前 turn 再重放

B. HTTP 模式不要无条件删掉 previous_response_id

当前 HTTP /responses` 之所以会有“像重复回复”的体感,一个重要原因就是它直接过滤了 previous_response_id。
建议:

  1. 不要对 HTTP 一刀切删除 previous_response_id
  2. 至少提供配置开关:
    • 允许在兼容路径中透传 previous_response_id
  3. 如果确实因为某些上游不兼容需要过滤,也应该做成可控策略,而不是硬编码默认删除

C. 优化 ctx_pool 下 preferred connection 生命周期

从日志看,很多断流最终都落到:

  • preferred_conn_unavailable
  • continuation connection is unavailable
    建议重点排查:
  1. preferred connection 何时被提前释放/标记 broken
  2. preflight ping 失败后是否可以更保守地恢复,而不是直接改写 turn 内容
  3. continuation 丢失时,是否应把错误原样返回,而不是 silently 改写成新建请求

结论

当前 sub2api 的 OpenAI/Codex 连续会话问题,不只是“WS 不稳定”这么简单,而是:

  • WS 有较强的自动恢复/重放逻辑,可能导致上下文重复或像重新回答
  • HTTP 又会主动过滤 previous_response_id,天然削弱原生 continuation 能力
    这两个方向叠加后,用户就很容易感受到:
  • 长对话连续性不稳定
  • 有时中途断流
  • 有时像重新回答/重复回复
    希望作者能从“会话连续性”角度系统梳理一下,而不只是把它当成单点网络不稳定问题。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions