SSE 实现 AI 对话中的流式输出

SSE 实现 AI 对话中的流式输出

日常使用 deepseek,经常看到聊天机器人的流式输出,感觉很赞,想尝试下自己实现一个类似效果

各个大模型平台,api 调用都支持流式输出,如果用 node.js 如何实现一个流式输出效果呢

整体内容包含服务端实现客户端实现

整体效果

SSE 技术原理

Server-Sent Events (SSE) 是一种基于 HTTP 的服务器向客户端推送数据的技术。与 WebSocket 不同,SSE 是单向的,仅支持服务器向客户端推送数据,但实现简单,且天然支持断线重连。

  • 单向通信,由服务器发送数据,客户端接收数据
  • 自动重连
  • 轻量级,相比 websocket,开销更小
  • 主要传输文本数据,适合 JSON 等结构化数据
  • sse 的消息结构
    • id: 事件 id
    • event: 事件名称 如果不传默认为 message,如果设置其他名称,前端也需要修改成对应的名称,进行消息接受
    • data: 事件数据
    • retry: 重连时间

服务端实现

  • node.js 的 koa2 框架

实现步骤

  1. 设置 SSE 响应头 响应内容类型,客户端不缓存,保持长连接
  2. 防止 koa 自动处理响应,手动设置响应状态码为 200
  3. 定时发送消息模拟推流,通过ctx.res.write(data: msg),也可以传入id, event补充消息内容。
  4. 通过ctx.req.on(eventName, callback)进行连接监听,处理连接关闭,连接失败
js 复制代码
aiRouer.get("/stream", (ctx) => {
  console.log("进入stream");

  // 设置SSE响应头
  ctx.set({
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
    "Access-Control-Allow-Origin": "*",
  });

  // 关键修复:防止Koa自动处理响应
  ctx.respond = false;
  ctx.status = 200;

  // 发送初始连接成功消息
  ctx.res.write(`data: ${JSON.stringify({ message: "Connected to Koa2 SSE server" })}\n\n`);

  // 设置定时器,定期发送消息
  let counter = 0;
  const timer = setInterval(() => {
    counter++;
    const data = {
      message: `Server time: ${new Date().toLocaleTimeString()}`,
      counter: counter,
    };

    // 检查连接是否仍然有效
    if (ctx.res.writable) {
      // 按照SSE格式发送数据
      ctx.res.write(`id: ${counter}\n`);
      ctx.res.write(`event: message\n`);
      ctx.res.write(`data: ${JSON.stringify(data)}\n\n`);

      if (counter > 10) {
        ctx.res.end();
      }
    } else {
      // 如果连接已关闭,清理资源
      clearInterval(timer);
      console.log("SSE connection closed due to client disconnect");
    }
  }, 1000);

  // 处理连接关闭
  ctx.req.on("close", () => {
    clearInterval(timer);
    console.log("SSE connection closed");
  });

  // 处理错误
  ctx.req.on("error", (err) => {
    clearInterval(timer);
    console.error("SSE connection error:", err);
  });
});

客户端实现

  • vue.js

实现步骤

  1. 触发 SSE 推送,创建一个 EventSource 对象,并监听服务端的推送消息。
  2. 通过 inputStr 接受消息,设置连接关闭条件,并结束定时器。
  3. 定时取数据,输出到outStr
html 复制代码
<template>
  <div class="steam">
    <h3>服务端回答的消息</h3>
    <div ref="outRef" class="out-textarea" v-if="outStr">{{ outStr }}</div>
    <div>
      <n-button type="info" @click="onSend">发送</n-button>
    </div>
  </div>
</template>
js 复制代码
import { NInput, NButton } from "naive-ui";
import { reactive, toRefs } from "vue";

const state = reactive({
  outRef: null,
  outStr: "",
  inputStr: "",
  inputFinish: false,
});
const { outRef, outStr, inputStr, inputFinish } = toRefs(state);

/**
 * 流式输出
 */
function outStream() {
  let i = 0;
  let timer = setInterval(() => {
    // 一直等待输出,直到服务端停止输出
    if (inputFinish.value && i >= inputStr.value.length) {
      clearInterval(timer);
      timer = null;
      return;
    }
    if (i < inputStr.value.length) {
      state.outStr += inputStr.value[i];
      i++;
    }
  }, 100);
}
function onSend() {
  let eventSource = new EventSource("/api/ai/stream");
  inputStr.value = "";
  outStr.value = "";
  inputFinish.value = false;
  eventSource.onmessage = function (e) {
    const data = JSON.parse(e.data);
    inputStr.value += data.message;
    if (data.counter > 10) {
      eventSource.close();
      eventSource = null;
      inputFinish.value = true;
    }
  };
  outStream();
}
css 复制代码
.out-textarea {
  display: inline-block;
  background-color: rgba(0, 0, 0, 0.06);
  padding: 12px 16px;
  border-radius: 8px;
  position: relative;
  box-sizing: border-box;
  min-width: 0;
  max-width: 100%;
  color: rgba(0, 0, 0, 0.88);
  font-size: 14px;
  line-height: 1.5714285714285714;
  min-height: 46px;
  word-break: break-word;
  margin-top: 24px;
  margin-bottom: 24px;
  scrollbar-color: rgba(0, 0, 0, 0.45) transparent;
}

ai 组件库

  • 目前大厂都有成熟的 ai 组件库,其中就包括对话流式输出组件
  • antd-design-x-vue 对话气泡框
  • RICH 设计范式思考
    • Role 【角色】以后产品和人交互,更像是一个人。可以通过角色外观,声音,情绪,专业领域知识
    • Intention 【意图】 以前收集用户需求,通过输入框,按钮,鼠标,触摸动作 以后 ai 会做的更多,比如通过对话、语音、结合少量原先图形化交互、更加准确和简单
    • Conversation 【对话】人与 ai 的会话规则 开始/追问/提示/确认/错误/结束
    • Hybrid UI 【混合界面】 Do 为主/Do + Chat 均衡/Chat 为主
相关推荐
夏殇之殁20 小时前
包中创建自定义列表项。 . 使用自定义列表项进行数据绑定 . 将天气预报数据保存到本地内存表,通过LiveBindings进行显示。 ...
服务器·前端·javascript
新中地GIS开发老师21 小时前
WebGIS开发学生作品|低空航天管理与航线规划系统
前端·javascript·webgis·三维gis开发
丙氨酸長鏈1 天前
Web前端入门第 问:JavaScript 一个简单的 IndexedDB 数据库入门示例
前端·javascript·数据库
kyriewen1 天前
面试官让我手写虚拟列表——AI生成的版本,快速滚动几下就白屏了
前端·javascript·面试
林焱_RPAAI1 天前
影刀RPA技术深度:CSS选择器高级实战指南——伪类属性选择器与性能对比完全解析
vue.js·react.js
许彰午1 天前
政务督办的分合模式:主办协办的并发审批
前端·javascript·政务
陳陈陳1 天前
🚀 前端流式输出革命:SSE + BFF 架构从零到一实战指南
vue.js·架构·node.js
易筋紫容1 天前
创建型模式:对象的诞生艺术
开发语言·前端·javascript
0_Error_1 天前
猫国建设者 修改资源 自动采集
javascript
贩卖黄昏的熊1 天前
NestJS简明教程——安全
开发语言·javascript·安全·typescript·nest.js