流式输出是什么意思:SSE推送、分块粒度与断连恢复,在模型API中转里各管什么
流式输出让模型边生成边推送,用户几百毫秒就能看到第一个字。本文从 SSE 的事件格式讲起,拆解分块粒度与字符边界、跨厂商事件归一化、心跳保活与断连恢复,并说明模型 API 中转层在这些环节分别该做什么,帮助判断一条流式链路到底稳不稳。
很多人在搜索框里敲下“流式输出是什么意思”时,真正的困惑往往不是定义,而是体验:为什么同样一个问题,有的产品要等好几秒才把整段文字一次性吐出来,有的却是一个字一个字往外蹦。前者是请求、等待、完整返回,后者是模型边生成边推送。区别不在界面动画,而在服务端是否把生成过程拆成连续的小片段,通过一条长连接持续发回来。首字延迟因此成了体验分水岭,几秒的空白和几百毫秒的首字,用户感受完全不同。
从工程角度看,流式带来的不只是“看起来快”。它让长回答可以在生成过程中被用户随时打断,也允许前端渐进渲染、边读边滚动。代价是链路复杂度上升:一次请求的持续时间从毫秒级拉到几十秒,中间任何一层超时、缓冲或断开都会直接暴露在用户眼前。计费与统计也不能再依赖“响应结束”这一个事件,而要在流中累计增量。理解这些代价,才能理解 SSE、分块和断连恢复为什么值得单独讨论。
流式输出最主流的承载方式是 SSE,也就是服务端推送事件。它建立在普通 HTTP 之上,响应头里声明事件流类型,连接建立后服务端持续往响应体里追加文本。每条事件以 data 前缀开头,事件之间用空行分隔,结束时通常发送一个终止标记。相比双向长连接,SSE 是单向推送,握手简单、能复用现有网关和端口,浏览器端的事件源接口以及各家 SDK 的 stream 参数,底层基本都是它。
SSE 真正容易出问题的地方不在协议本身,而在中间层。反向代理、CDN、负载均衡如果开启了响应缓冲,就会把服务端发出的小片段先攒起来,攒够一定体积再转发,结果就是用户那里“卡一下、全出来”,流式体验荡然无存。排查时要逐层确认缓冲是否关闭、是否做了强制刷新。另一个常见问题是连接空闲:模型思考期间可能十几秒没有数据,中间层会判定超时并断开,因此需要周期性发送注释行或心跳事件来保活。
分块策略决定了用户看到的颗粒度。上游每次返回的片段大小并不一致,有的每生成一个词就发一个事件,有的按短语甚至整句聚合。中转或自建服务要不要重新切分,取决于两件事:一是不能破坏字符边界,中文字符在 UTF-8 下通常占三个字节,按字节截断会出现乱码;二是不能为了省事把多个小片段攒成大块,那等于把流式又变回批处理。合理的做法是按字符或按事件边界透传,保留上游节奏,仅在必要时做最小粒度的合并。
归一化解决的是厂商差异。不同模型服务的事件结构并不统一:有的把增量文本放在 delta 字段里,有的用独立的段落事件,工具调用的参数也常常是分段下发的。归一化层的作用,是把这些结构映射成一套客户端已经熟悉的格式,比如统一成候选数组里的增量内容,让业务代码只写一套解析逻辑。需要留意的是增量字段的含义是“这一段新增了什么”,而不是完整文本,拼接时要按顺序追加,遇到多个候选或工具调用还要按序号分别归集。
归一化还会遇到不少边界情况。首个事件可能只携带角色信息而没有正文,末尾事件往往只带结束原因而不带文本,用量统计有时会单独放在最后一个事件里。如果这一层处理不严谨,就会出现正文丢失、结束状态判断错误,甚至重试后同一段内容被追加两次。稳妥的做法是让每个事件都带有可判定的类型与序号,保证顺序、不丢不重,异常时也能明确告诉调用方这次流是正常收尾还是中途中断。
断连恢复是流式链路里最难的一环。流一旦断开,服务端可能还在继续生成,而客户端手里只有半截内容。常见做法分三层:客户端保留已收到的文本并记录请求标识;重试时优先判断能否续接,而不是无脑重发;服务端则需要有能力回答“这次请求生成到哪了”。最简单的策略是整段重试,代价是可能重复计费和内容重复;更精细的做法是要求上游支持偏移量或缓存已生成片段,从断点继续下发。是否值得做,取决于业务对完整性的要求。
超时参数需要跨层协同。客户端读超时、网关空闲超时、上游生成超时三者如果不匹配,就会出现“模型还在写、链路已经断”的尴尬。经验上,把中间层的空闲超时调到明显大于正常分块间隔,同时依靠心跳事件维持连接活性,能消掉大部分莫名其妙的断开。另外,流的结束必须可区分:正常结束会有一个明确的终止事件,异常中断则应该以错误事件或连接关闭的形式暴露,否则前端会把半截回答当成完整结果展示给用户。
衡量一条流式链路是否合格,看几个指标就够了:首字延迟、分块间隔的 P95 与 P99、异常中断率、重试成功率,以及乱码或截断出现的频次。首字延迟决定“快不快”,分块间隔的尾部决定“卡不卡”,中断率决定“稳不稳”。这些指标比总耗时更能反映真实体验,也更容易定位问题出在模型侧、中转侧还是客户端侧。上线前用一段长回答做压测,往往比反复读文档更能发现问题。
把这些环节拆开看,会发现中转层的价值主要落在三件事上:透传不缓冲、事件结构统一、断连有明确处理。快米兔的模型 API 中转在这几处做了适配,把上游各家不同的流式事件归一到统一的兼容格式,转发时保持增量透传,并对空闲连接做保活处理,业务侧通常只需要按一套解析逻辑接入。它采用注册送 5 元测试金、按量计费的方式,适合先小成本跑通流式链路再逐步放量,具体额度与计费细则以官方说明为准。
回到最初的问题,流式输出不是某个前端特效,而是一整套从协议到调度再到容错的工程约定。SSE 提供了承载方式,分块策略决定颗粒度,归一化抹平厂商差异,断连恢复决定极端情况下的可用性。选型或自建时不妨直接问三个问题:中间层会不会缓冲、事件格式是否统一、断开之后怎么续。能清楚回答这三点的方案,才谈得上把流式体验做稳。
