Stream Closed Before Completed
四种成因,逐跳定位法
流式响应中途断掉,报错原文是 "stream closed before completed"——常见成因有四种,压根不是同一类问题,逐跳排查才能找对地方。
四个关键事实
四种常见成因
上游限流帧、超时链路、客户端主动断开、改写层吞掉终结事件——表现相似,根因完全不同。
定位方法的核心
客户端到模型服务之间往往经过多跳(客户端→网关→负载均衡→上游),必须逐跳排查才能找到断点发生在哪一跳。
终结事件是否到达
很多流式协议靠一个终结事件(如 [DONE])标记正常结束;如果中途没看到它就断了,说明流被提前切断而不是正常完成。
最常被忽略的一种
客户端、网关、负载均衡器往往各自设置了不同的超时时间;只要有一跳的超时短于模型实际生成时间,流就会被那一跳强行切断。
四种成因分别是什么
第一种,上游限流帧:模型服务在生成过程中因为触发限流,主动插入一个中断信号提前结束这次生成,客户端看到的就是流还没走完就断了。第二种,超时链路:请求要经过客户端、代理、网关、负载均衡器等多跳,只要其中任意一跳设置的超时时间短于模型实际生成所需时间,那一跳就会主动切断连接,即使模型那边其实还在正常生成。第三种,客户端主动断开(client gone):用户中途关闭了页面、取消了请求,或者客户端所在的容器/进程被回收,这类中断的信号通常会在服务端日志里留下「peer closed connection」一类的记录,和上面两种的方向相反。第四种,改写层吞掉终结事件:有些反向代理、CDN 或改写中间件会重新处理流式响应体,如果它的实现有缺陷,可能会在转发正常内容的同时,吞掉标志「生成已正常完成」的那个终结事件(比如 SSE 里的 [DONE]),导致客户端以为连接异常中断,而实际上模型那边已经正常生成完毕。
为什么这个排查方法值得记
四种成因里,前两种(上游限流帧、超时链路)是最容易被误诊成「我们的网络不稳定」的类型;第三种(客户端主动断开)常常被服务端错误归咎为「模型不稳定」;第四种(改写层吞掉终结事件)最隐蔽,因为客户端拿到的内容看起来是完整的,只是缺了那个标志性的结束信号,容易被当成「偶发 bug」而不是系统性问题。
时间线
流式响应(SSE 等协议)与其终结事件机制是长期存在的标准设计,四种中断成因伴随流式协议的普及一直存在。
agent 化工作流让长时间流式生成的场景变多,链路上任意一跳的超时设置不合理都更容易暴露出来。
改写层吞掉终结事件这一类最隐蔽的成因,随着反向代理/CDN 中间件复杂度上升,出现频率也在上升。
已确认 vs 常见误解
已确认
「stream closed before completed」这类提示在公开的开发者讨论里长期稳定出现,四种成因(限流帧、超时链路、客户端主动断开、改写层吞事件)都各自有据可查,且互不相同,需要分别排查。
常见误解
遇到流式中断,很多人第一反应是「模型不稳定」或「厂商服务有问题」,但实际统计里,超时链路和改写层吞事件这两种——都是客户端/中间层自己的配置或实现问题——占了相当大的比例。
怎么区分四种成因
服务端主动切断(限流帧 / 超时)
服务端日志会显示这次生成被限流或者某一跳超时触发中断;特征是同样的请求换个时段或降低并发后往往能正常完成。
客户端侧问题(主动断开 / 改写层吞事件)
服务端日志显示生成其实已经正常走完,问题出在客户端断开连接过早,或者中间的改写层没有把终结事件转发过去;这类要去查客户端/代理配置,不是模型服务的问题。
逐跳定位法
第一步,看服务端日志:这次生成是否完整跑完、有没有触发限流。如果服务端记录显示生成正常完成,问题大概率在客户端到服务端之间的某一跳。第二步,逐跳核对超时设置:客户端超时、CDN/网关超时、负载均衡器超时,找出哪一跳的超时时间比模型实际生成耗时短。第三步,如果怀疑是改写层吞事件,直接对比「服务端发出的原始流」和「客户端收到的流」,看终结事件是否在中间环节丢失。第四步,如果是客户端主动断开(比如用户手动取消),那本身不算故障,只是需要在业务逻辑里正确处理这种取消场景,避免误判成系统故障。
在 QCode 上怎么办
QCode 的边缘节点针对流式响应做了超时对齐,减少因为链路某一跳超时过短而误切断长生成任务的情况;如果你自己的代理层或改写中间件有类似问题,也可以直接切到 QCode 提供的端点做过渡排查。
常见问题
怎么知道流式中断是不是我这边的问题?
先看服务端日志(如果你能拿到):如果显示生成已经正常完成,那问题大概率出在客户端到服务端之间的某一跳(超时设置、改写层等),属于你这边可以排查和修复的范围。
为什么同样的请求有时候正常、有时候中断?
最常见的原因是超时链路:模型生成耗时本身有波动,当某次耗时恰好超过链路里最短的那个超时阈值时就会被切断,而耗时较短的那些请求能正常走完。
「客户端主动断开」算不算故障?
严格说不算——这是正常的用户行为(取消请求、关闭页面)或者客户端环境变化(进程被回收)导致的,只是需要在业务逻辑里正确识别和处理,而不是当成系统故障去排查。
改写层吞掉终结事件,这种情况怎么排查最快?
直接抓包或者加日志,对比服务端实际发出的流式内容和客户端最终收到的内容——如果服务端发了终结事件而客户端没收到,问题就出在中间的改写/转发环节。
遇到这个报错,第一步该做什么?
先看服务端日志有没有限流或错误记录;没有的话,去核对客户端、网关、负载均衡器各自的超时时间设置,这两步能排除掉四种成因里的三种。
这个问题和 429/529 是一回事吗?
不是。429/529 是请求还没真正开始生成就被拒绝;stream closed before completed 是生成已经开始、流已经建立,但在结束前被中断,两者发生的阶段完全不同。
信息来源
四种成因基于公开的开发者讨论、流式协议(如 SSE)的标准行为,以及逐跳超时排查这一通用运维方法整理;本页不针对任何单一厂商的私有实现细节做断言。本页内容于 2026-08-27 整理。
相关阅读
Context Length Exceeded 完整排查指南
另一类容易被误诊的报错,判别方法可以对照。
API Key 用量查询完整指南
怎么系统性地查看自己的调用记录与异常。
Claude 529 vs 429 vs Weekly Limit
另一组容易混淆的报错判别。
本页内容为跨厂商通用的技术性说明,不针对任何单一厂商的私有实现细节做断言;具体行为以你实际使用的模型、客户端与代理链路配置为准。