Skip to content

fix(stream): 流中断时干净收尾,避免拼接错乱回复 - #92

Open
Sunsh1neY wants to merge 1 commit into
Sophomoresty:mainfrom
Sunsh1neY:fix/stream-graceful-close
Open

Sunsh1neY wants to merge 1 commit into
Sophomoresty:mainfrom
Sunsh1neY:fix/stream-graceful-close

Conversation

@Sunsh1neY

Copy link
Copy Markdown

问题

通过 HTTP 代理访问上游时,流式响应偶发中途断开(日志表现为 [SSL] record layer failure,常见于代理复用/回收 keep-alive 连接后)。此时 generate_stream 的重试逻辑会整段重新生成,而已流式输出的内容无法撤回,导致两种用户可见的故障:

  1. 回复说一半就停:重新生成的内容与已输出前缀不一致,触发 Gemini stream content changed during retry,重试耗尽后流被静默丢弃(无 finish_reason、无结束标记);
  2. 前后矛盾的拼接回复:若重新生成的内容恰好以已输出文本为前缀(同 prompt 开头通常一致),校验放行后继续输出——客户端最终显示的是两次不同生成的拼接,逻辑跳跃、前后矛盾。

修复

  • generate_stream:一旦有部分内容已输出,跳过重试并干净结束流。客户端收到格式完整的响应(finish_reason: "stop" + data: [DONE]),可自行发起续写;
  • _get_httpx_client:禁用 keep-alive 连接复用(Connection: close),避免复用已被代理/上游关闭的连接导致流中途 SSL 断裂。代价为每请求一次额外的 TLS 握手,对话场景无感。

测试

  • 现有 18 个测试用例全部通过(pytest tests/ -q);
  • 实际环境复现验证:代理链路抖动触发中断时,日志记录 Stream interrupted after partial output (N chars), ending cleanly,客户端拿到正常收尾的流;连续 3 次长回复流式请求均干净结束,不再出现重试拼接。

上游连接经代理中断(如 "[SSL] record layer failure")后,原逻辑会整段
重新生成并重试。由于已流式输出的内容无法撤回,重试要么因前缀校验失败
("Gemini stream content changed during retry")导致流被静默丢弃——客户端
看到回复说一半就停;要么在重试内容恰好以已输出文本为前缀时,把两次不同
的生成拼接成一条前后矛盾的回复。

- generate_stream: 一旦有部分内容已输出,跳过重试并干净结束流,客户端收到
  格式完整(finish_reason + [DONE])的响应,可自行请求续写
- _get_httpx_client: 禁用 keep-alive 连接复用(Connection: close),避免
  复用已被代理/上游关闭的连接导致流中途 SSL 断裂

现有 18 个测试全部通过。
luxizai pushed a commit to luxizai/gemini-web2api that referenced this pull request Sep 11, 2026
此前带工具的流式请求被整体降级为非流式:generate() 整段生成后再包装成
SSE 一次性发出,agent 类客户端(ZCode/dsh 等)每轮都带工具定义,因此
永远看不到流式效果。

新增工具流式路径:正文 delta 实时转发;检测到 ```tool_call 围栏开始
标记(含跨 delta 边界的情况)后停止转发、转入缓冲,流结束后解析为
OpenAI 流式 tool_calls delta 事件(id/name/arguments),finish_reason
正确置为 tool_calls。

附带修复:围栏解析失败(生成中断导致围栏未闭合、JSON 不完整等)时,
原实现会在 parse_tool_calls 中静默丢弃整个工具调用——模型的调用意图
凭空消失,agent 反复重试形成死循环。现在缓冲的原始文本会作为正文补发
给客户端,不再有内容凭空丢失。

- 纯文本回复:完全实时流式(仅末尾 11 字符延迟到流结束防围栏标记跨界)
- 工具调用回合:正文部分流式,仅 tool_call JSON 部分等流结束后解析
- 现有 18 个测试全部通过,新增 3 个测试覆盖工具解析/残缺回退/纯文本
- 与 Sophomoresty#92(流中断优雅收尾)配合体验最佳;无硬依赖——重试耗尽时本路径
  的异常分支会记录 Stream error 并结束,与既有降级路径行为一致
luxizai pushed a commit to luxizai/gemini-web2api that referenced this pull request Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant