基于SSE实现AI聊天场景的流式输出

什么是SSE

SSE(Server-Sent Events),轻量级单向实时通信技术,专为 "服务器向客户端持续推送流式数据" 而设计。

SSE本质是客户端发送一个普通的HTTP请求,服务器保持连接打开,然后持续不断地向客户端推送数据。

  • 普通http:一次请求 -> 一次响应 -> 连接关闭
  • SSE:一次请求 -> 多次响应 -> 连接长期保持

SSE的特点

  1. 单向通信:只支持服务器->客户端推送,不支持客户端通过SSE向服务器发送数据
  2. 自动重连:浏览器原生支持,无需手动实现
  3. 断点续传:通过Last-Event-ID头,断线重连后可以从上次断开的位置继续接收数据
  4. http原生支持
  5. 多个SSE可以共享一个TCP连接,降低服务器开销

基于以上特点,SSE是目前AI聊天、日志监控等场景的首选方案。

工作步骤

步骤:

  1. 连接建立
  2. 服务器响应
  3. 数据推送
  4. 连接关闭

连接建立

客户端通过EventSource对象向服务器发送一个http GET请求,请求头中有Accept: text/event-stream。

vbnet 复制代码
GET /api/chat HTTP/1.1
Host: example.com
Accept: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

服务器响应

yaml 复制代码
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Transfer-Encoding: chunked

数据推送

数据必须遵循SSE标准格式

yaml 复制代码
id: 123
event: message
data: 这是第一条消息

data:这是第二条消息
这还是第二条消息

retry: 5000

id: 消息id,可选,用于断点续传 event:自定义事件类型,默认是message data: 消息内容,可以跨多行 retry: 断线重连间隔多少ms

豆包网页版的SSE截图:

代码实现

前端示例

ts 复制代码
window.mockChat = () => {
  // 创建SSE连接
  const eventSource = new EventSource('http://localhost:3000/api/chat?prompt=你好')

  // 监听默认message事件
  eventSource.onmessage = (e) => {
    console.log('收到消息:', e.data)
  }

  // 监听自定义事件
  eventSource.addEventListener('complete', (e) => {
    console.log('回答生成完成')
    eventSource.close() // 关闭连接
  })

  // 监听错误事件
  eventSource.onerror = (e) => {
    // @todo
  }
}

服务端示例

js 复制代码
import express from 'express'

const app = express()

app.get('/api/chat', (req, res) => {
  // 设置SSE响应头
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive',
    'Access-Control-Allow-Origin': '*',
  })

  // 模拟大模型流式生成回答
  const answer = '你好,我是AI助手,很高兴为你服务!'
  let index = 0

  const timer = setInterval(() => {
    if (index < answer.length) {
      // 每次发送一个字
      res.write(`data: ${answer[index]}\n\n`)
      index++
    } else {
      // 发送完成事件
      res.write('event: complete\n')
      res.write('data: done\n\n')
      clearInterval(timer)
      res.end()
    }
  }, 100)

  // 客户端关闭连接时清理资源
  req.on('close', () => {
    clearInterval(timer)
    console.log('客户端断开连接')
  })
})

app.listen(3000, () => {
  console.log('服务器运行在 http://localhost:3000')
})

效果截图

SSE和WebSocket的对比

对比维度 SSE (Server-Sent Events) WebSocket
协议基础 纯 HTTP/HTTPS 协议 GET 请求 独立的 WebSocket 协议
通信方向 单向:仅服务器→客户端推送 全双工:客户端↔服务器双向自由发送
数据格式 仅支持文本数据(UTF-8),内置事件流格式 支持二进制和文本数据,无内置格式,完全自定义
自动重连 浏览器原生支持,断线后自动重连 无原生支持,需要手动实现重连、心跳、退避算法
兼容性 所有现代浏览器支持,IE10+ 所有现代浏览器支持,IE10+
CDN / 代理支持 完美支持,可利用 CDN 缓存、边缘计算 较差,多数 CDN 不支持 WebSocket 代理
性能开销 极低,HTTP/2 支持多路复用,多个 SSE 连接共享一个 TCP 连接 中等,每个 WebSocket 连接独占一个 TCP 连接

最后

SSE 是一种轻量级、简单、可靠的实时通信技术,它没有 WebSocket 那么强大,但在 "大模型一次请求,流式响应" 这个业务场景下,它是简单、成本低、兼容性最好的选择。

相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万2 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋3 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
1点东西3 天前
做了近两年的Agent开发,其实真正要学的就是这五件事
llm·agent·ai编程
七牛云行业应用3 天前
Qwen-Image-2.1 开源部署完整指南:Diffusers、ComfyUI 与推理服务
ai编程
卡布鲁3 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
福如意如我心意3 天前
TencentDB Agent Memory和其他开源memory
ai编程
汉堡大王95273 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端