AI答疑,如何使用 fetch 获取接口返回的流式数据

程序员柳随风 2024-06-15 15:31:02 阅读 74

最近进行了AI答疑相关的需求开发,总共进行了两次版本迭代,在此做个记录并分享前端的实现过程。

需求描述

前端进行提问,接口这边使用AI进行答疑,回答内容流式输出到前端。接口的 Response Headers 的 Content-Type 为 text/event-stream; charset=utf-8

第一个版本

前端实现

/** 获取答案 */export const getAnswer = (params) => { return fetch('https://gpt.xxxx.com/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${ token}` }, body: JSON.stringify(params) } )}// 调用getAnswer({ question: '为啥?'}) .then(async response => { if (!response.ok) { throw new Error('Network response was not ok') } // 确保响应是可读流 if (!response.body) { throw new Error('Response body is not available') } const reader = response.body.getReader() const textDecoder = new TextDecoder() let result = true let output = '' while (result) { const { done, value } = await reader.read() if (done) { console.log('Stream ended') result = false break } const chunkText = textDecoder.decode(value) output += chunkText } console.log('output:', output) }) .catch(() => { })

为什么不用 axios:开始也尝试使用过 axios,设置 responseType: 'stream',但并没有实现流式输出。

此版本存在的缺陷:后端将前端的提问传给 chatgpt,等带 chatgpt 输入完后才流式输出给前端…

第二个版本

后端修改了第一个版本的不合理的流式输出,采用了 SSE 模式实现,不用等待 chatgpt 回答完成才输出内容。

简单介绍下SSE

SSE:Server-Sent Events 服务器推送事件,简称 SSE,是一种服务端实时主动向浏览器推送消息的技术。

​SSE 是 HTML5 中一个与通信相关的 API,主要由两部分组成:服务端与浏览器端的通信协议(HTTP 协议)及浏览器端可供 JavaScript 使用的 EventSource 对象。

从“服务端主动向浏览器实时推送消息”这一点来看,该 API 与 WebSockets API 有一些相似之处。但是,该 API 与 WebSockers API 的不同之处在于:

Server-Sent Events API WebSockets API
基于 HTTP 协议 基于 TCP 协议
单工,只能服务端单向发送消息 全双工,可以同时发送和接收消息
轻量级,使用简单 相对复杂
内置断线重连和消息追踪的功能 不在协议范围内,需手动实现
文本或使用 Base64 编码和 gzip 压缩的二进制消息 类型广泛
支持自定义事件类型 不支持自定义事件类型
连接数 HTTP/1.1 6 个,HTTP/2 可协商(默认 100) 连接数无限制

前端实现

/** 获取答案 */export const getAnswer = ( params: IAnswerParams, config?: { onopen?: (response: Response) => Promise<void> onmessage?: (e: EventSourceMessage) => void onerror?: (e: any) => void onclose?: () => void }) => { let p = Object.assign({ }, params, { env: envObj[import.meta.env.VITE_ENV] }) // window.abortControllerAi = new AbortController() // console.log('getAnswer', window.abortControllerAi.signal) return fetchEventSource( 'https://gpt.xxxx.com/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${ token}` }, body: JSON.stringify(p), // signal: window.abortControllerAi.signal, ...config } )}// 调用let answer = ref('')getAnswer( { question: '为啥?' }, { onmessage(event) { // console.log(event.data) // event.data 为 [DONE] 代表结束 let value: any = { } try { value = JSON.parse(event.data) } catch (e) { } // 回答内容 let str = '' try { str = value.choices.map(i => i.delta.content).join('') } catch (e) { } answer.value += str }, onclose() { console.log('close') // throw new Error() // hack 中断链接 }, onerror(err) { console.log('err', err) // window.abortControllerAi.abort() // 中断不生效 // window.abortControllerAi = new AbortController() // hack 中断链接, 不生效 throw new Error() // hack 中断链接 } })

这里使用了 '@microsoft/fetch-event-source' 库,以解决 EventSource 的不足。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。