流式输出是什么意思?SSE、分块策略与断连恢复在模型 API 中转里到底怎么工作?
流式输出是模型把生成结果按 token 分块、通过长连接边算边推给调用方的方式,主流实现是 SSE(Server-Sent Events);它不等于 WebSocket,也不适合一次性返回的短请求。分块策略决定首字延迟与可读性,断连恢复决定长回答会不会白花钱。
流式输出是模型把生成结果按 token 分块、通过一条长连接边推理边推送的返回方式,最常见的实现是 SSE(Server-Sent Events),即服务端以 text/event-stream 持续下发 data 片段;它不适用于只需一次拿完整结果的短请求,也不能替代 WebSocket 的双向通信能力。
从调用方看,流式与非流的差别不在模型本身,而在响应什么时候开始可读。非流式请求要等模型把整段话生成完,服务端再一次性返回 JSON,首字延迟等于完整生成时间;流式请求在第一个 token 产出后就发出第一个事件,后续 token 陆续追加。对聊天类、代码补全类、长文摘要类场景,这个差别直接决定用户是盯着转圈还是看到字往外冒。
SSE 的机制是单条 HTTP 长连接上的服务端单向推送,协议格式是若干以 data: 开头的行、事件之间用空行分隔,客户端用 EventSource 或 fetch 的 ReadableStream 逐段读取。它天然是文本协议,每个事件带自己的字段,因此实现成本低、调试直观、几乎不需要额外握手,这也是模型 API 服务器普遍选它而不是 WebSocket 的原因:一次问答只需要服务端往客户端推,不需要双向通道。代价是 SSE 在部分中间设备上会遇到缓冲,反向代理如果没有关掉响应缓冲,客户端会等到连接结束才一次收到所有内容,看起来像没流式。
分块策略决定用户感知到的流畅度,而不是数据总量。服务端通常按 token 边界或固定字符窗口切分,每个事件可以是单个 token、几个 token,也可以是一句话;切得太碎会让事件数量暴涨、每包都带协议开销,切得太粗则会出现一顿一顿的跳跃感。工程上常见的折中是按 token 累积到一定长度或遇到标点就下发,同时保证每个事件都是合法的部分文本,客户端只要按顺序拼接即可还原完整回答。需要强调的是,分块只是传输粒度,不影响模型输出内容本身的质量。
首字延迟和生成速度是两个指标,混在一起看会误判体验。首字延迟指从发请求到收到第一个内容事件的时间,包含排队、预填充和第一个 token 的采样;生成速度指后续 token 的到达速率,单位常写作 token 每秒。流式只能把首字延迟暴露给用户并提前开始渲染,并不能让模型算得更快。如果上游本身排队严重,流式下用户会先看到长时间空白再突然出字,这种情况要优化的是排队与并发,不是分块大小。
断连恢复是流式输出里最容易被低估的一环,长回答中途断网就意味着已经生成的 token 可能白付费用。SSE 连接可能因为网络切换、客户端超时、网关空闲断开或服务端实例重启而中断,默认情况下客户端重连会重新发起一次完整请求,模型从头生成,费用和时延都翻倍。要避免这种情况,服务端需要在事件里带可续传的定位信息,客户端重连时携带该位置,服务端从对应偏移继续推送;业界常见做法是给每个事件编号、在响应头声明支持断点续传,让客户端在断连后用编号续接。
没有断点续传能力时,调用方至少要做三件事来止损。第一是把请求做成幂等,记录本次请求的标识和已接收内容,重连时优先复用而不是无条件重发;第二是设置合理的心跳或空闲超时,避免连接被静默掐断后客户端一直挂着;第三是对长回答预设最大长度和超时预算,一旦超出就主动收尾,防止单次请求无限拉长。这三条不是锦上添花,而是流式在生产环境可用的前提。
归一化处理是把不同上游的流式格式统一成一套事件模型的必要步骤。各家模型的流式响应字段名、结束标志、用量统计的返回时机并不一致:有的把用量放在最后一个事件,有的单独发一个结束事件,有的在错误时直接断开而不给错误码。中转层要做的是把这些差异收敛成统一结构,至少包含增量文本、结束原因、累计用量和错误类型,让下游客户端只认一套协议。
归一化的另一个作用是修复上游的边界行为,否则问题会原样传给业务方。常见需要处理的边界包括:最后一个事件缺少结束标记、内容里出现部分 UTF-8 字节导致乱码、心跳事件与内容事件混在一起、错误在中途以普通事件形式下发。中转层应当按字节缓冲、只有凑成完整字符才下发,并把错误统一转成带类型的终止事件,而不是让连接静默关闭。
从成本结构看,流式本身不改变按 token 计费的单价,但会改变重试带来的实际支出。按量计费下,一次被中断后从头重来的长回答,等于为同一段内容付两次费;如果客户端逻辑没有去重,重连还可能叠加并发。因此评估一个流式接口是否合格,除了看首字延迟和速率,还要看它是否支持续传、是否在错误事件里给出可判断的类型,这两点直接影响长会话场景的账单。
流式输出也有明确的适用边界,用错场景反而增加复杂度。一次性返回的短分类、结构化抽取、需要严格 JSON 完整校验的批处理任务,走非流式更简单,也更容易做重试;而在需要人机实时交互、回答可能长达数千 token、用户对等待敏感的对话场景,流式几乎是默认选项。判断标准可以简化为一句话:用户是否需要在中途就看到部分结果并可能提前终止,如果不需要,流式只是额外负担。
落地时的检查清单可以按四层来看,任何一层缺失都会让流式体验打折。传输层确认响应头是 text/event-stream 且中间代理关闭了缓冲;协议层确认事件分隔、结束标志和错误事件定义清晰;应用层确认客户端按序拼接、能处理乱码边界、能提前中止;运维层确认有断连续传、用量统计和超时预算。四层都对齐之后,流式输出才是可交付的能力,而不只是把返回切成几段。
总的来说,SSE 解决的是单向推送的传输问题,分块策略解决的是感知粒度问题,断连恢复解决的是长回答的可靠性问题,归一化解决的是多上游差异问题。这四件事各自独立,缺一个都会在特定场景下暴露:只有 SSE 没有续传,长回答一断就重来;只有分块没有归一化,换一个上游就要改一遍客户端。把四者分开设计、分别验收,比笼统地说支持流式更接近实际可用的标准。