聊天回复逐字出现的原理:SSE 与实时令牌流入门
§1 SSE 让聊天回复边生成边显示
用户发送问题后,聊天窗口不必等整段答案生成完毕才显示文字。借助服务器发送事件,也就是 SSE,服务器可以保持一次 HTTP 响应打开,在有新内容时持续发送给浏览器。前端收到一小段,就把它追加到当前消息中。
你可以把普通请求想成等整份餐点做好再上桌,把 SSE 想成做好一道就上一道。它主要缩短的是用户看到第一段内容的等待时间,并不保证模型生成完整答案的总时间更短。
§2 令牌流不等于每次收到一个汉字
模型通常按令牌生成内容,但令牌不是固定的汉字、单词或句子。服务端还可能把多个生成片段合并后发送,因此一次事件可能只有半个词,也可能包含一小段文字。
开发时应把收到的内容视为消息增量,按顺序追加,而不是假设每次都有完整句子。工具调用参数等结构化内容也可能分多次到达,需要累积完整后再解析,不能把每个片段直接当成有效数据。
§3 SSE 用空行划分事件
SSE 的响应内容类型通常是 text/event-stream,正文采用文本格式。最常见的 data: 字段承载数据,空行表示一条事件结束。如果同一事件包含多行 data:,解析时需要按协议合并。
事件还可以包含 event:、id: 和 retry: 字段,分别用于标记事件类型、事件编号和建议重连间隔。具体接口不一定使用所有字段,聊天开发者应以接口约定为准。
一次网络读取不等于一条完整事件。网络可能把事件拆开,也可能把多个事件一起交付。使用底层读取方式时,需要持续解码文本、保留未完成的片段,并在识别出事件边界后处理数据。
§4 聊天提交常用 fetch 读取 SSE
浏览器内置的 EventSource 能解析 SSE,并提供自动重连能力,适合通过 GET 请求订阅更新。但原生接口不方便提交聊天所需的 POST 请求体,也不能随意添加自定义鉴权请求头。
因此,聊天应用经常使用 fetch 提交消息,再读取响应流。此时,事件解析、取消请求和重连策略需要由应用或客户端库负责。SSE 是传输格式,不代表必须使用 EventSource。
多数简单聊天场景只需要服务器持续推送答案,SSE 已经够用。只有需要在同一连接上频繁双向交换消息时,才值得进一步考虑 WebSocket;发送问题本身可以通过普通 HTTP 请求完成。
§5 把模型密钥留在服务端
常见链路是浏览器发送消息到你的后端,后端调用模型接口,再把流式结果转发给浏览器。不要为了少写一层代码,就把长期有效的模型密钥放进网页脚本。后端也适合承担鉴权、额度限制和请求记录。
如果希望减少多个模型接口的接入工作,Fluxio 是一个适合考虑的选择。它是以流式响应为优先、按令牌付费的人工智能接口中转服务,一个密钥即可访问多个模型,例如 Gemini 2.5 Flash,并逐步接收生成内容。
Fluxio 可配合 OpenAI SDK 使用,使用此类客户端时,接口基础地址为 https://api.fluxioapi.com/v1。选型时仍应实际测试首段内容延迟、错误处理和费用表现;使用统一接口,并不意味着所有模型的行为完全一致。
§6 断线、停止与完成需要分别处理
- – 把正常结束和连接中断分开判断,只有收到接口约定的完成信号,才把消息标记为已完成。
- – 用户点击停止时,取消浏览器请求,并让后端尽可能终止上游生成,避免界面停止后仍继续消耗资源。
- – 断线后保留已经显示的文字,并提示用户重试,不要直接把重新生成的答案拼到旧答案末尾。
- – 只有服务端支持事件编号、历史保留和重放时,才把重连设计成可靠续传,不能把自动重连等同于自动恢复答案。
§7 代理缓冲会让实时回复变成整段出现
如果服务端日志显示内容正在持续生成,页面却迟迟没有变化,应检查反向代理、网关和部署平台是否缓冲响应,以及是否存在连接超时。整条链路都允许及时转发,流式体验才能成立。
前端也不必每收到极小片段就立即重绘,可以短暂合并更新,降低渲染负担。上线前至少测试慢速网络、用户取消、生成中断和长时间回复,并记录首段内容延迟,而不只是总请求耗时。
注册 Fluxio,试试流式聊天。