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 为主
相关推荐
竹林8189 小时前
从 ethers.js 迁移到 Viem:一个签名验证 Bug 让我彻底放弃旧爱
javascript
你挚爱的强哥9 小时前
Vue2 实现 1.5s 十连击监听(连续点击若干次),封装通用可复用点击检测工具,不用 data!Vue 封装通用连击监听方法,支持自定义时长与点击次数
前端·javascript·vue.js
半个落月10 小时前
用 LangChain.js 手写一个能读写文件和执行命令的 Mini Cursor
javascript·人工智能·后端
天外天-亮10 小时前
HBuilder X 使用 uview-plus 方式
开发语言·javascript·hbuilder x·uview-plus
卓怡学长11 小时前
w266基于spring boot + vue 圣地延安美食乐享系统
java·数据库·vue.js·spring boot·spring·intellij-idea
Random_index13 小时前
#Vue3篇: Vue 项目发版后如何提示用户刷新?基于 package.json 版本号的前端更新检测方案
前端·vue.js·json
多加点辣也没关系15 小时前
JavaScript|第9章:对象 — 基础
开发语言·javascript·ecmascript
用户831348593069815 小时前
Cesium自定义可调节星空夜景+星星闪烁
vue.js·cesium
白露与泡影15 小时前
用 Spring Boot + Vue + Fuzio 构建现代 Java 桌面应用
java·vue.js·spring boot
后台开发者Ethan15 小时前
setState组件更新
前端·javascript·react