为什么 LLM 应用需要流式输出
大语言模型生成文本通常需要数秒甚至更久,用户盯着空白等待的体验很糟。流式输出让 token 逐个返回,既降低首字延迟,也让用户能提前判断答案方向、随时中断无用生成。这不是可选优化,而是 LLM 应用的基础体验要求。
选择流式方案时,核心权衡点是协议复杂度、基础设施兼容性、客户端支持三者的平衡。下面按从简到繁排序,逐个分析四种主流方案。
方案一:Server-Sent Events (SSE)
SSE 是最常见选择。客户端发起普通 HTTP 请求,服务端保持连接打开,用 data: ... 格式逐行推送事件。浏览器原生支持 EventSource API,后端只需设置 Content-Type: text/event-stream 和 Cache-Control: no-cache。
适用场景:单向推送(服务端到客户端)且客户端是现代浏览器。假设你在做一个 Web 端的 AI 写作助手,用户提交 prompt 后等待生成结果,这是典型的单向数据流,SSE 完全够用。
技术约束:
- 浏览器对同一域名的 SSE 连接有数量限制(通常 6 个),多标签页场景需注意
- 只能传输文本,二进制数据需 Base64 编码
- 部分老旧代理服务器或 CDN 会缓冲 SSE 响应,导致延迟(需测试你的部署环境)
方案二:HTTP Chunked Transfer Encoding
本质是 HTTP/1.1 的分块传输,服务端不声明 Content-Length,而是用 Transfer-Encoding: chunked 逐块发送响应体。客户端用普通 fetch 或 XMLHttpRequest 接收,通过 ReadableStream 读取片段。
适用场景:需要更灵活的数据格式(如 JSON Lines),或客户端不支持 EventSource。假设你在开发移动端 SDK,需要在 iOS/Android 原生代码中接收流式响应,Chunked 比 SSE 更容易集成进标准 HTTP 客户端库。
技术约束:
- 客户端需手动处理流式解析(SSE 的
EventSource会自动处理重连和事件分割) - 某些企业网络环境的中间代理可能强制缓冲 chunked 响应
- 错误处理需自行实现(SSE 有内置的
retry机制)
方案三:WebSocket
全双工通信协议,客户端和服务端都能主动推送消息。建立连接后用帧(frame)传输数据,支持文本和二进制。
适用场景:需要双向交互的场景,比如用户能在生成过程中发送控制指令(暂停、调整参数、追加上下文)。假设你在做一个协作式 AI 编程助手,用户边看生成的代码边实时调整需求,这种持续对话需要 WebSocket。
技术约束:
- 基础设施复杂度显著提升:需处理心跳、重连、状态同步
- 负载均衡器需支持 WebSocket(传统七层 LB 可能不兼容)
- 服务端需维护长连接状态,水平扩展时要考虑连接迁移或状态共享
过度设计风险:如果你的应用只需单向推送,WebSocket 的双向能力是浪费,还带来额外的运维成本。
方案四:短轮询(不推荐但有存在场景)
客户端定时发请求询问是否有新数据。在流式输出场景下几乎总是劣选,但在极端受限环境(如某些企业内网只允许标准 HTTP GET/POST)可能是唯一选项。
技术约束:
- 延迟取决于轮询间隔,间隔短则服务端压力大,间隔长则体验差
- 无法做到真正的"流式",只是分段拉取
- 仅在客户端完全无法建立持久连接时考虑
决策建议
按优先级排序:
- 默认选 SSE:覆盖 90% 的 Web 端 LLM 应用场景,实现成本低,浏览器兼容性好
- 需要双向交互时选 WebSocket:但要确认你真的需要客户端主动推送,而不是简单的"取消请求"(后者用 HTTP abort 即可)
- 客户端受限时选 Chunked:比如原生移动端、非浏览器环境,或需要自定义协议格式
- 避免轮询:除非前三者都不可行
假设一个日均 10 万次对话的 AI 客服场景:SSE 方案在标准云服务器上可直接承载,WebSocket 则需额外考虑连接数上限、内存占用、负载均衡配置,运维复杂度至少翻倍。选型时先问自己:我是否真的需要更复杂的方案来解决实际问题,还是在为技术而技术?
如果你在架构 LLM 应用时遇到流式输出、多模型接入或成本优化的具体问题,欢迎通过 联系我们 与 Taylent Labs 团队讨论。