手把手教你在浏览器中处理流式传输(Event Stream/SSE)

前文:手把手教你在浏览器和RUST中处理流式传输 提到如何简单的处理流式输出,但是后来发现这个写法有bug,下面讲解一下更好的写法

顺便补充一下,上一篇文章提到的IterableReadableStream来自@langchain/core,你可以这样导入使用:

js 复制代码
import { IterableReadableStream } from '@langchain/core/utils/stream'

处理Event Stream

除了上一章的ndjson以外,最常用就是Event Stream 了,包括OpenAi等一众ai服务提供商都会提供sse接口,并且以Event Stream 的格式进行输出,先来看看ai是怎么理解Event StreamSSE的:

Server-Sent Events (SSE) ,一种基于 HTTP 的轻量协议,允许服务器向客户端推送实时数据流。

SSE 格式规范

  • 数据通过 HTTP 流式传输,内容类型为 text/event-stream

  • 每条事件由字段组成,用换行符分隔。字段包括:

    • data: 事件的具体内容(必填)。
    • event: 自定义事件类型(可选)。
    • id: 事件唯一标识符(可选)。
    • retry: 重连时间(毫秒,可选)。

示例

plaintext 复制代码
event: status_update
data: {"user": "Alice", "status": "online"}

id: 12345
data: This is a message.

retry: 3000

那再来看看ai输出的结果:

很标准的text/event-stream格式

使用langchainjs处理

你以为我要像上一篇一样开始手搓处理代码了吗,no no no,我们还是使用langchainjs进行处理,原因后面会提到。

这里推荐一个fetch封装工具:ofetch,一个类似axios的库,作用大家应该都懂了吧,这里我拿火山的接口来演示:

js 复制代码
// vite.config.js
export default defineConfig({
  base: "/",
  server: {
    proxy: {
      "/huoshan": {
        changeOrigin: true,
        ws: true,
        secure: false,
        target: "https://ark.cn-beijing.volces.com",
        rewrite: (path) => path.replace(/^\/huoshan/, ""),
      },
    },
  },
});

// vue.config.js
module.export = {
  devServer: {
    compress: false, // 重点!!!不关闭则有可能导致无法正常流式返回
    proxy: {
      '/huoshan': {
        target: 'https://ark.cn-beijing.volces.com', // 代理
        changeOrigin: true,
        ws: true,
        secure: false,
        pathRewrite: {
          '^/huoshan': '',
        },
      },
    }
  }
}

如果是webpack的话,一定要关闭devServercompress,不然会导致整个请求结束才返回,这样就不是流式输出了。

js 复制代码
// request.js

import { ofetch } from "ofetch";

export const fetchRequest = ofetch.create({
  baseURL: '/huoshan',
  timeout: 60000,
  onRequest({ options }) {
    options.headers.set('Authorization', 'Bearer xxxxx') // 替换火山api的key
  },
})
js 复制代码
import { fetchRequest } from "./request";
import { convertEventStreamToIterableReadableDataStream } from "@langchain/core/utils/event_source_parse";

async function test() {
  const res = await fetchRequest("/api/v3/chat/completions", {
    responseType: "stream",
    method: "post",
    body: {
      model: "deepseek-v3-250324",
      messages: [
        {
          role: "user",
          content: "你是谁?",
        },
      ],
      stream: true,
    },
  });
  const stream = convertEventStreamToIterableReadableDataStream(res);
  for await (const chunk of stream) {
    console.log(chunk);
  }
}
test()

返回正常,不过要注意,结尾有个[DONE],所以不能无脑反序列化,

js 复制代码
for await (const chunk of stream) {
  if (chunk !== '[DONE]') {
    console.log(JSON.parse(chunk))
  }
}

这样就拿到每个chunk了,当然你可以将test方法改成生成器,然后for里面yield JSON.parse(chunk)

为什么要用langchainjs封装好的方法处理

既然大家都知道流式输出是一个一个chunk的方式返回,那么是不是有可能一行的文本,拆分成两个chunk(在js看来是ArrayBuffer)?而一个utf8字符是定长的,可能是1-3字节,那是不是有可能在某个字符的时候,其中一部分字节拆分到一个chunk,然后剩下部分字节拆分到下一个chunk?

这样就会导致你在decode的时候发生报错,无法正常decode成文字,所以langchainjs的方法考虑到这个情况:

代码在:github.com/langchain-a...

其他关注点

使用代理时需要注意

上面的webpack配置已经讲解了一下devServer应该怎么配置才能流式输出。还有就是使用nginx代理的时候也需要修改一下配置:

ini 复制代码
server {
	listen 80;
	location /huoshan/ {
                # http1.1才支持长连接
		proxy_http_version 1.1;
		# 关闭代理缓冲
		proxy_buffering off;
		# 设置代理缓冲区大小
		proxy_buffer_size 10k;
		# 设置代理缓冲区数量和大小
		proxy_buffers 4 10k;
		proxy_set_header Host $host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_pass https://ark.cn-beijing.volces.com/;
	}


}

其实就是关闭一些代理缓冲,以及设置一下缓冲区,为什么要这样设置,这里有请懂nginx配置的大佬细说一下😜

相关推荐
斯~内克2 小时前
Electron 菜单系统深度解析:从基础到高级实践
前端·javascript·electron
数据知道2 小时前
【YAML】一文掌握 YAML 的详细用法(YAML 备忘速查)
前端·yaml
dr李四维2 小时前
vue生命周期、钩子以及跨域问题简介
前端·javascript·vue.js·websocket·跨域问题·vue生命周期·钩子函数
旭久2 小时前
react+antd中做一个外部按钮新增 表格内部本地新增一条数据并且支持编辑删除(无难度上手)
前端·javascript·react.js
windyrain2 小时前
ant design pro 模版简化工具
前端·react.js·ant design
浪遏2 小时前
我的远程实习(六) | 一个demo讲清Auth.js国外平台登录鉴权👈|nextjs
前端·面试·next.js
GISer_Jing3 小时前
React-Markdown详解
前端·react.js·前端框架
太阳花ˉ3 小时前
React(九)React Hooks
前端·react.js
鸿蒙布道师3 小时前
OpenAI战略转向:开源推理模型背后的行业博弈与技术趋势
人工智能·深度学习·神经网络·opencv·自然语言处理·openai·deepseek