流式输出:让 AI 回答像打字机一样逐字蹦出来

ChatGPT 的答案是逐字蹦出来的,背后是 SSE 流式输出。本文讲清为什么不能一次返回、SSE 是什么、给出 Flask 生成器 + EventSource 最小可运行示例,以及前端增量拼接、代理缓冲等工程边界。

流式输出:让 AI 回答像打字机一样逐字蹦出来

你用 ChatGPT 时,答案是一个字一个字蹦出来的,不是憋半天一次性弹出。这叫流式输出,背后大多是 SSE(Server-Sent Events)。它不改变答案本身,却极大改善了「等待感」——让用户知道「它在动」。

背景:为什么不能一次返回

LLM 是自回归逐 token 生成的,全部生成完再返回,用户要干等好几秒甚至更久,体验很差,还容易以为卡死了。流式把已生成的 token 立刻推给前端,边生成边显示。

SSE 是什么

SSE 是基于 HTTP 的单向推送:服务端用 text/event-stream 持续发送 data: ...\n\n 这样的数据块,浏览器用 EventSource 接收。相比 WebSocket,它更轻量,专做「服务器 → 客户端」的单向流,且天然走普通 HTTP、好穿代理。

一个最小可运行的例子

后端用生成器持续推送(Flask 风格):

from flask import Response
import time

def event_stream():
    for token in generate_tokens():   # 逐 token 推送
        yield f"data: {token}\n\n"
        time.sleep(0.05)

@app.route("/chat")
def chat():
    return Response(event_stream(), mimetype="text/event-stream")

前端用 EventSource 接收并拼接:

const es = new EventSource("/chat");
es.onmessage = (e) => {
  output.textContent += e.data;   // 逐字拼接到页面
};

取舍与边界

  • 前端逻辑更复杂:要处理「增量拼接」与渲染,比一次性返回麻烦不少。
  • 中途出错难处理:已经开始流了,报错只能中断或补一句,没法整体回滚。
  • 代理/网关要支持分块:有些中间件会缓冲响应,把流式又攒成大块,要显式关闭缓冲。
  • 不是所有场景都要流:内部批处理、离线评测可一次性返回,省事。

你能马上用起来的收获清单

  • 任何面向用户的生成接口,默认上流式,体感提升立竿见影。
  • 前端用 EventSourcefetch + ReadableStream 消费分块。
  • 检查你的反向代理(Nginx 等)是否缓冲了响应,必要时关掉。
  • 给流式加「超时 / 中止」按钮,用户能随时打断。
  • 批处理、评测类后台任务不必流式,保持简单。

KEEP READING