OpenAI、豆包、通义如何实现对话流

695 阅读3分钟

一直比较好奇 ChatGPT、通义、豆包等大模型的对话交互是如何实现的,最近研究了一下,了解到类似他们那种对话交互是使用一种称为 SSE(全称:Server-sent events) 的方式来实现的。

什么是SSE?维基百科的解释为:

Server-Sent Events (SSE) is a server push technology enabling a client to receive automatic updates from a server via an HTTP connection, and describes how servers can initiate data transmission towards clients once an initial client connection has been established. They are commonly used to send message updates or continuous data streams to a browser client and designed to enhance native, cross-browser streaming through a JavaScript API called EventSource, through which a client requests a particular URL in order to receive an event stream. The EventSource API is standardized as part of HTML Living Standard[1] by the WHATWG. The media type for SSE is text/event-stream.


简单点讲就是:服务端可以基于 HTTP 链接持续的向客户端发送数据。

我们一般最常用的通信方式是:HTTP,但是 HTTP 有一定的局限性,即:无法实时通信,浏览器每次想要获取数据就必须调用服务端的接口。为了能够支持实时通信,就有了 SSE 和 WebSocket, SSE 和 WebSocket 之间的主要区别在于:

  1. SSE 只支持服务端向客户端持续的发送数据,即:单向通信,而 WebSocket 支持服务端和客户端之间的双向通信,即:全双工通信。

  2. SSE 仅支持文本数据,不适合传输大量二进制格式的数据。

接下来分析下 ChatGPT、豆包、通义的 SSE 是如何使用的。通过 Chrome 的 DevTools 可以看到:

ChatGPT 在回答问题时返回的数据如下:

图片

找到接口 conversation 在接口详情中有一个 EventStream Tab,这个 Tab 下的内容即为返回的 Event,通常来说一个 Event 包含三部分:ID,Type 和 Data。如图,可以看到 ChatGPT 返回的 Event 结构只包含 Type 和 Data。Data 可以是纯文本,也可以是 json 格式的数据,但是 json 格式的数据在前端使用的时候需要先 JSON.Parse() 。此外,在上图中我们还会看到三种 Type:delta_encoding、delta、message。Event 默认的 Type 为 message,支持自定义,如果使用自定义的类型,在监听时就需要监听对应的类型,不然会接收不到数据。比如:

const evtSource = new EventSource("https://any/any/events");
evtSource.addEventListener("ping"(event) => {  
    const newElement = document.createElement("li");  
    const eventList = document.getElementById("list");  
    const time = JSON.parse(event.data).time;  
    newElement.textContent = `ping at ${time}`;  
    eventList.appendChild(newElement);});

上述代码只会解析类型为 ping 的数据。
类型是用来标识 Data 的,可以让我们不用解析 Data 就能够快速的区分 Data,我们可以看到 ChatGPT 就是这样处理的,它把需要展示在用户屏幕上的回复内容都放在了 delta 这个类型下。最后通过一个默认的 message 类型,一个文本 Data: [DONE] 来表示本次回复结束。这样前端就可以知道本次回复的状态了。
再来看下豆包 SSE 的使用:

图片

豆包的回复接口是:message/stream_reply,同样在 EventStream tab 下,我们可以看到 Event 结构,豆包和 ChatGPT 不同点在于:

  1. Event 有 ID,并且是自增的

  2. Event 的 Data 为了安全考量被编码了,ChatGPT 返回的内容只是使用了 Unicode 编码。

  3. 最后一个 Event 返回的是一个类型为 done 的空数据。

最后来看下通义 SSE 的使用:

图片

通义的回复接口是:dialog/conversation,可以看到通义的 Event 和ChatGPT 的 SSE 比较像:没有 ID,没有对数据进行繁琐的编码,使用 "[DONE]" 来表示一次回复结束。区别在于看上去每次返回的回复内容都是全量数据。

Ok,大概的分析就到此为止了,其实这些大模型的前端应该是比较复杂的,比如豆包还使用了浏览器端的 indexedDB 来存储数据。

最后是一个 SSE 练手小 Demo,模拟类似 chatGPT、豆包等的对话。但是掘金好像不支持发视频,有兴趣可以搜索公众号查看 Demo:

公众号二维码@2x.png

原文链接:mp.weixin.qq.com/s/CXHjvdUxS…