快速结论:当 OpenAI 兼容上游在 Open WebUI 已经转发 200 响应头之后断开连接时,/api/chat/completions 的 SSE 流会被静默截断——没有错误帧、没有 [DONE],客户端往往把它当成“正常结束的空回复”。优先排查上游(如 LiteLLM、Bedrock)是否在流中途断开,以及 Open WebUI 是否已包含对该透传路径的修复。
适用环境:Open WebUI dev @ 18a48cf;同一问题也在 v0.9.6 生产环境复现(Open WebUI → LiteLLM 1.98.0 → Bedrock)。操作系统:Ubuntu 24.04。安装方式:Git Clone。Issue 未提供 Python、CUDA、显卡、Ollama 版本信息。
最快修复方案:暂无确认的一步修复方案。该 Issue 标记为 confirmed issue,指向的修复 PR 为 open-webui/open-webui #29697,但在本地验证前应视为“可优先尝试”:升级到包含该 PR 的版本,或按 PR 思路在 stream_wrapper 捕获上游异常后,先写一个空行终止被截断的未完成事件,再补发错误帧与 [DONE]。
注意事项:Open WebUI 已确认:该透传路径转发的是原始网络 chunk,而不是完整行,因此失败前最后一个 chunk 可能停在事件中间;若直接把错误帧拼上去,会把错误帧粘在半行后面,产生格式错误的 SSE 事件。因此必须先补空行再发错误帧。另外,这只是在客户端侧“暴露”上游失败,并不修复上游断流本身;Issue 中该补丁尚未合并时,不要让任何命令或配置被当作已验证结论。
问题场景
用户在 Open WebUI 中配置了一个 OpenAI 兼容连接,请求经过 /api/chat/completions 透传,上游(例如 LiteLLM 1.98.0 后面的 Bedrock)在 Open WebUI 已经返回 200 响应头之后中断了流式响应。API 客户端收到的是一个突然停止的 200 event stream,而不是带错误的响应或正常的 [DONE] 结束标记。在实际部署中,这表现为三天内 156 次静默的空补全,客户端日志里看不到真正的上游错误。
报错原文
issue: upstream failure mid-stream truncates /api/chat/completions SSE with no error frame or [DONE]
data: {"choices":[{"delta":{"content":"1"}}]}
data: {"choices":[{"delta":{"content":"2"}}]}
curl: (18) transfer closed with outstanding read data remaining
--- curl exit=18 http=200 bytes=94
ClientPayloadError: Response payload is not completed:
原因分析
可能原因是:上游在流式响应中途断开后,aiohttp 抛出 ClientPayloadError、ServerDisconnectedError 或空闲 TimeoutError,而 backend/open_webui/utils/session_pool.py 中的 stream_wrapper 没有捕获这些异常。此时 Starlette 已经无法更改 HTTP 状态码,只能直接断开连接,于是客户端拿到一个 200 的事件流,但流中既没有错误帧,也没有 data: [DONE]。多数 OpenAI SDK 会把这种“正常结束但内容为空”的流记录为一次成功的空补全,导致上游错误在客户端侧不可见。
Issue 讨论中已确认:这是 /api/chat/completions 透传路径的问题,与聊天 UI 路径(process_chat 会捕获 TransferEncodingError 并展示给用户,如 #24559)不同;与 #13474 的 chunk 边界解析问题不同(后者在 dev 上已由 stream_chunks_handler 处理);与 #26776 无关。
环境排查
- 确认 Open WebUI 版本与安装方式:Issue 复现于
dev @ 18a48cf和v0.9.6,安装方式为 Git Clone。 - 确认上游链路:Open WebUI → LiteLLM 1.98.0 → Bedrock 是 Issue 中复现的生产链路。
- 确认操作系统:Ubuntu 24.04。
- 确认
backend/open_webui/utils/session_pool.py中stream_wrapper是否捕获 aiohttp 的ClientPayloadError、ServerDisconnectedError、TimeoutError。 - 确认透传路径是否直接转发原始网络 chunk(而非按行转发),这会影响错误帧的插入方式。
- Issue 未提供 Python、CUDA、PyTorch、显卡、Ollama 版本信息,这些项目无需作为本问题的必要排查项。
解决步骤
- 先区分问题路径:确认客户端走的是
/api/chat/completions透传,而不是聊天 UI 的process_chat路径。如果是聊天 UI 路径,参考 #24559 的现有处理方式。 - 用 Issue 中的复现方式确认问题:启动一个会在写入两个 SSE chunk 后调用
request.transport.abort()的假上游,再通过curl -sS -N -i -X POST .../api/chat/completions请求,观察是否出现截断、无[DONE]、curl退出码 18。 - 在
stream_wrapper中捕获上游异常(aiohttp 的ClientPayloadError、ServerDisconnectedError、空闲TimeoutError)。 - 捕获后不要直接追加错误帧:先写入一个空行,把失败前可能停在半行的事件终止掉,避免错误帧与未完成 chunk 粘连成格式错误的事件。
- 随后写入
data: {"error": {...}}错误帧,再写入data: [DONE],并以正常方式结束 chunked body。 - 如果不想自行改动,可优先尝试升级到包含 PR #29697 的版本;该 PR 尚未在 Issue 中被标记为合并验证。
验证方法
用同一条 curl 命令重新请求 /api/chat/completions:如果修复生效,上游中途断开时客户端应收到错误帧和 data: [DONE],curl 不再报 (18) transfer closed with outstanding read data remaining,也不再出现“200 但流突然停止”的情况。同时检查 API 客户端日志,确认此前被记录为成功空补全的请求现在能被识别为错误。
参考来源
open-webui/open-webui PR #29697
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![[Bug][ROCm]: DeepSeek V4 accuracy drops with MRV2 on MI350/MI355 when FULL_DECODE_ONLY graph](https://www.chat-gpts.plus/wp-content/uploads/2026/09/52644-e5cd4f06-768x403.jpg)

