大模型 API 里的流式输出是什么意思?SSE 机制、分块策略与断连恢复各解决什么问题

流式输出是指服务端把一次生成拆成多个增量片段,在同一条 HTTP 连接内逐段推给客户端,把首字延迟与总生成时长解耦。SSE 负责承载这条单向事件流,分块策略决定片段粒度与字符边界,断连恢复和归一化处理则对应中断重试与多厂商流式格式差异。

流式输出指模型服务端把一次生成拆成多个增量片段,在同一条 HTTP 连接尚未关闭时逐段推送给客户端,而不是等全部内容生成结束后一次性返回。它解决的是首字延迟问题:用户看到第一个字的时间取决于首片段到达时间,而不是整段生成的总耗时。它不适用于结果需要整体校验后再使用的批处理任务,例如结构化抽取后必须整段解析才能入库的场景。 SSE 是流式输出最常见的传输载体,规范名称为 Server-Sent Events,走的是普通 HTTP 请求。响应头使用 Content-Type: text/event-stream,正文由以 data: 开头的事件行组成,事件之间用空行分隔,服务端持续写入且不声明 Content-Length。因此连接是否结束,客户端只能靠结束事件或连接关闭来判断。 SSE 相比 WebSocket 的优势在于单向推送与 HTTP 兼容性。模型生成本质是服务端到客户端的单向数据流,客户端在生成过程中不需要回传消息,全双工能力用不上;同时 SSE 能直接复用现有的鉴权头、网关、日志与链路追踪,接入成本更低。代价是断线语义弱,没有应用层确认与重传机制,连接质量完全依赖 TCP 与中间设备的表现。 分块策略决定一次推送多少内容。分块粒度过小时,每个事件的固定开销(事件行前缀、空行、可能的压缩与 TLS 记录)占比升高,单位时间事件数暴涨;粒度过大时,用户会感受到明显停顿,流式体验退化成分段加载。工程上通常在 token 生成侧做短时间窗口聚合,把若干 token 合并成一个事件下发,在延迟与开销之间取平衡。 分块必须保证 UTF-8 字符边界完整。一个汉字占三个字节,若按固定字节数切分,很容易把多字节字符截成两半,客户端解码后出现乱码。正确做法是在字节缓冲中向前回退到合法字符边界再下发,或者直接以 token、字符串为单位切分,从源头避免跨字符截断。这条规则对中文内容尤其关键。 中间层缓冲是流式“看起来不流”的首要原因。反向代理、网关或 CDN 默认可能开启响应缓冲,把多个事件攒够一定大小再一次性转发,客户端就会看到内容成批出现而非逐字增长。处理方式是在代理层对该路由关闭缓冲(例如 nginx 的 proxy_buffering off 与 X-Accel-Buffering: no),并避免对流式响应启用 gzip 压缩。 空闲超时需要靠心跳维持。长时间没有内容下发的连接会被网关或负载均衡按空闲超时切断,而推理排队、思考内容生成或工具调用等待期间恰好可能数十秒无数据。常见做法是周期性发送以冒号开头的注释行作为心跳,这类行不进入业务数据,但能让连接保持活跃,避免在第一个正式片段到达前就被断开。 断连恢复的前提是服务端能感知客户端已经断开。客户端关闭连接后,服务端下一次写入会失败,或通过请求上下文的取消信号被通知,此时应立即中止上游推理、释放算力与连接配额,否则会出现客户端已走、模型还在算的浪费。对按量计费的服务来说,这类泄漏会直接体现在成本上。 断连后从中间续传在多数大模型接口上并不可行。SSE 规范定义了 Last-Event-ID 供重连时携带,但模型生成是有状态的解码过程,服务端通常不保留可续算的中间状态,因此重试往往意味着重新生成。可行的折中是缓存已下发的文本前缀,重试完成后对比前缀、只向用户追加新增部分,避免界面出现重复内容。 归一化处理是把多家不同的流式格式映射成同一种增量结构。不同厂商在事件命名、字段层级和结束标记上差异很大:有的用统一事件类型加 delta 对象,有的把消息开始、内容块增量、消息结束拆成不同事件名,结束标记也不统一。归一化的目标字段通常包括角色、文本增量、推理内容增量、工具调用增量、结束原因与用量统计。 工具调用与用量字段是流式归一化里最容易出错的部分。工具调用的参数往往以 JSON 字符串片段的方式分多次下发,客户端必须按序拼接后再整体解析,逐片解析必然报错;用量统计通常只在最后一个事件或结束事件中给出,若早期事件就写死用量,结算会失真。归一化层应把这些字段单独缓存,等到结束事件再合并输出。 流式响应中的错误只能以事件形式下发。HTTP 状态码在第一个字节写出后就已经固定为成功,此后上游报错、限流或超时都无法再用状态码表达,只能发送一个错误事件再关闭连接。客户端必须显式处理这类事件,否则会把报错文本当作正常回答渲染出来,而衡量流式质量主要看首字延迟、事件间隔抖动与断连率三项,它们比平均吞吐量更能反映真实体验。