Skip to content

SSE 流式传输、网络中断恢复与大模型打字机打点 ​

回到总览:AI 时代的工程师能力
相关模块:Prompt 工程、Tool Calling 与 Agent 记忆机制 (Memory Workflow) · API 幂等性、超时重试与移动端数据最终一致性

一句话定义 ​

SSE(Server-Sent Events,服务器发送事件)是大语言模型(LLM)实现流式文本输出(Streaming)的标准 HTTP 轻量级协议;结合移动端网络中断自动恢复(Last-Event-ID)与 Markdown 增量解析,构成了 AI 打字机渲染体验的核心技术链路。

代码索引 ​

主题Lab 说明源码
SSE 流式 + 中断恢复sse-agent-orchestrationserver.js · client.js
SSE + Tool Calling + 最小 RAG 流式前端tool-calling-rag-streaming-demoserver.js · client.js

为什么需要 ​

  • 为什么 LLM 输出代码或长文本时必须采用 SSE 流式传输,而不是像传统 API 那样等完全生成后再一次性返回?
    • 一句话答:LLM 生成长文本需要数秒甚至数十秒,一次性返回会导致 TTFB(首字节时间)极长,用户会感觉页面假死;通过 SSE 按 Token 逐字流式下发,可以在几十毫秒内向用户展示首字,极大地降低了感知延迟。
  • 为什么移动端 SSE 客户端必须实现 Last-Event-ID 重连机制?
    • 一句话答:移动网络(Wi‑Fi / 5G 切换)极易发生长连接断开;利用 SSE 协议标准的 Last-Event-ID 报头,客户端在断网重连后可以告知服务端从中断处的那个 Token 接着推,避免文本重复或从头打印。
  • SSE 与 WebSocket 在 LLM 打字机场景中如何选型?
    • 一句话答:LLM 打字机是单向接收流式文本,基于标准 HTTP 的 SSE 协议更轻量且原生支持断线自动重连;只有需要双向高频交互的场景才选 WebSocket。

底层机制 ​

1. SSE (Server-Sent Events) HTTP 协议流流转 ​

对应 Lab:sse-agent-orchestration

SSE 报头标准规则 ​

  • Content-Type: text/event-stream
  • Cache-Control: no-cache(禁止中间 CDN 节点缓存)
  • Connection: keep-alive

2. 网络中断重连与 Last-Event-ID 机制 ​

当网络瞬断重连时: 客户端向服务端发送 Last-Event-ID: 2 报头,服务端直接从 id: 3 的 Token 开始接着推送,实现无缝增量恢复。

3. 最小流式前端 demo 要关心的不是“页面好不好看”,而是事件边界 ​

对应 Lab:tool-calling-rag-streaming-demo

真实 AI 产品里,前端并不只是接收一串 token。更常见的是同一条 SSE 流里混着:

  • tool_call:展示“模型正在查资料/调工具”
  • tool_result:展示工具结果摘要
  • token:真正的打字机内容
  • done:收尾与引用来源

本仓库的最小 demo 直接把这四类事件拆开,CLI 客户端按事件类型分别处理:

js
const event = lines.find((line) => line.startsWith('event: '))?.slice(7) || 'message';
const payload = JSON.parse(dataLine.slice(6));

if (event === 'token') {
  process.stdout.write(payload.token);
} else {
  process.stdout.write(`
[${event}] ${JSON.stringify(payload, null, 2)}
`);
}
  • 可能执行顺序
    1. 建立 /api/chat SSE 连接。
    2. 服务端先发 tool_call。
    3. 再发 tool_result。
    4. 随后连续发 token。
    5. 最后发 done。
  • 可能输出
    text
    [tool_call] {...}
    [tool_result] {...}
    问题:为什么 RAG 需要 chunk overlap?
    这个最小 demo 展示了一条最短 AI 工程闭环...
    [done] {"references":["docs/08-ai-engineering/04-rag-and-knowledge-retrieval.md#chunk-6"]}
  • 预期现象:前端可以区分“正在调工具”和“正在打印答案”,而不是把所有事件都混成一段普通文本。
  • 观察重点:这正是“流式前端 demo”的最小价值——先把事件协议定稳,再谈浏览器 UI 和 Markdown 增量渲染。

Android / Flutter / Web / Backend 对照 ​

维度SSE (Server-Sent Events)WebSocketHTTP 长轮询 (Long-Polling)
数据流向单向 (Server → Client)双向 (Full-Duplex)双向 (多次 HTTP 模拟)
协议复杂度极低 (基于标准 HTTP 协议)较复杂 (需要独立握手与帧)低
自动重连原生支持 (Last-Event-ID)需要自定义心跳与重连逻辑需要客户端再次发请求
适合场景LLM 流式打字机、新闻直播实时聊天室、多人游戏简单状态轮询

常见场景与代码实现 ​

1. OkHttp 处理 SSE 流式事件流 ​

java
Request request = new Request.Builder()
        .url("https://api.openai.com/v1/chat/completions")
        .header("Accept", "text/event-stream")
        .header("Last-Event-ID", lastEventId) // 中断恢复!
        .post(body)
        .build();

RealServerSentEvent sse = new RealServerSentEvent(request, new EventSourceListener() {
    @Override
    public void onEvent(EventSource eventSource, String id, String type, String data) {
        if ("[DONE]".equals(data)) {
            // 结束流
            return;
        }
        String token = parseTokenFromJson(data);
        // 切回主线程追加渲染 Markdown 打字机
        mainHandler.post(() -> ui.appendToken(token));
    }
});

常见误配、事故后果与排障 ​

1. 事故:代理服务器(Nginx)开启了 Buffer 导致 SSE 流式打字机失效变“一次性吐出” ​

  • 误配原因:后端的 Nginx 反向代理未关闭 proxy_buffering,Nginx 缓存了所有 SSE 块,等 LLM 完全生成后才一次性发给客户端。
  • 后果:前端完全丧失了流式打字机效果,TTFB 延迟暴增。
  • 排障与修法:在 Nginx 配置中增加 proxy_buffering off;,或在服务端 HTTP Header 中返回 X-Accel-Buffering: no。

2. 误配:把工具事件、token 事件、done 事件混成同一种 data ​

  • 后果:前端很难区分“正在查工具”还是“正在输出正文”,恢复/重放时也无法按阶段回放。
  • 排障与修法:显式使用 event: 字段区分事件类型;最小 demo 里就是 tool_call / tool_result / token / done 四类事件分流。

与相近概念对比 ​

传输机制协议连通性开销为什么 LLM 首选 SSE
SSEHTTP极低LLM 是单向推 Token 场景,无需 WebSocket 复杂双向握手
WebSocketWS/WSS高适合聊天室等双向高频交互

对应实验 ​

Lab说明源码
sse-agent-orchestrationSSE 流式推送 + 任务状态机 + 中断恢复(纯 Node,可跑可停)server.js · client.js
tool-calling-rag-streaming-demoSSE 事件分流:工具调用、检索结果、token、done(与文首「代码索引」一致)server.js · client.js

复习检查题 ​

  1. 在 LLM 大模型文本生成场景中,为什么 SSE (Server-Sent Events) 比 WebSocket 更适合作为传输协议?

    答:因为 LLM 文本生成是一个典型的“客户端发起一次请求,服务端单向连续推送 Token”的单向流式场景。SSE 直接基于标准的 HTTP 协议,实现极其简单,天然支持网络中断后基于 Last-Event-ID 的自动重连;而 WebSocket 是双向全双工协议,需要额外的握手、心跳包与断线重连逻辑,在 LLM 打字机场景中显得过于繁重。

  2. 为什么 Nginx 反向代理配置不当会导致 SSE 流式打字机效果失效?如何解决?

    答:因为 Nginx 默认开启了 proxy_buffering(代理缓冲区),会将服务端推过来的数据先暂存在 Nginx 的 Buffer 中,凑够一定大小后才一次性发送给客户端。这破坏了 SSE 逐字下发的实时性。解决办法是在 Nginx 配置中对该 API 路径设置 proxy_buffering off;,或者让服务端返回 Header X-Accel-Buffering: no。

速记 ​

  • SSE 协议头:text/event-stream + Cache-Control: no-cache + Connection: keep-alive。
  • 中断恢复:利用 Last-Event-ID 报头实现断网重连后的增量续推。
  • 事件分流:工具调用、token、done 最好分事件类型,不要混成一坨文本。
  • Nginx 防坑:服务端/代理必须关闭 Buffer (proxy_buffering off) 保障实时流式。

站点构建时间:2026/8/24 23:43:17