chatgpt 原理 、 sse

318 阅读5分钟

server sent events (sse)

Server Sent Event is one-way communication only from server to client. (WebSocket is two-way communication from server to client)

Server-sent Events 规范

cloud.tencent.com/developer/a…

SSE是一种在网页开发中使用的、基于HTTP长连接技术, SSE 规范是 HTML 5 规范的一个组成部分。该规范比较简单,主要由两个部分组成:

  第一个部分是服务器端与浏览器端之间的通讯协议,

  第二部分则是在浏览器端可供 JavaScript 使用的 EventSource 对象。

  通讯协议是基于纯文本的简单协议。服务器端的响应的内容类型是“text/event-stream”。响应文本的内容可以看成是一个事件流,由不同的事件所组成。

  每个事件由类型数据两部分组成,同时每个事件可以有一个可选的标识符。不同事件的内容之间通过仅包含回车符和换行符的空行(“\r\n”)来分隔。每个事件的数据可能由多行组成。

在规范中为消息定义了 4 个字段:

event 消息的事件类型。客户端收到消息时,会在当前的 EventSource 对象上触发一个事件,这个事件的名称就是这个字段的值,如果消息没有这个字段,客户端的 EventSource 对象就会触发默认的 message 事件。

id 这条消息的 ID。客户端接收到消息后,会把这个 ID 作为内部属性 Last-Event-ID,在断开重连 成功后,会把 Last-Event-ID 发送给服务器。

data 消息的数据字段。客户端会把这个字段解析为字符串,如果一条消息有多个 data 字段,客户端会自动用换行符 连接成一个字符串。

retry 指定客户端重连的时间。只接受整数,单位是毫秒。如果这个值不是整数则会被自动忽略。

SSE使用注意事项

1、SSE 如何保证数据完整性

  客户端在每次接收到消息时,会把消息的 id 字段作为内部属性 Last-Event-ID 储存起来。

  SSE 默认支持断线重连机制,在连接断开时会 触发 EventSource 的 error 事件,同时自动重连。再次连接成功时 EventSource 会把 Last-Event-ID 属性作为请求头发送给服务器,这样服务器就可以根据这个 Last-Event-ID 作出相应的处理。

  这里需要注意的是,id 字段不是必须的,服务器有可能不会在消息中带上 id 字段,这样子客户端就不会存在 Last-Event-ID 这个属性。所以为了保证数据可靠,我们需要在每条消息上带上 id 字段。

2、减少开销

  在 SSE 的草案中提到,"text/event-stream" 的 MIME 类型传输应当在静置 15 秒后自动断开。在实际的项目中也会有这个机制,但是断开的时间没有被列入标准中。

  为了减少服务器的开销,我们也可以有目的的断开和重连。

  简单的办法是服务器发送一个 关闭消息并指定一个重连的时间戳,客户端在触发关闭事件时关闭当前连接并创建 一个计时器,在重连时把计时器销毁 。

与 WebSocket 的比较

SSE 与 WebSocket 作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。

总体来说,WebSocket 更强大和灵活。因为它是全双工通道,可以双向通信;SSE 是单向通道,只能服务器向浏览器发送,因为 streaming 本质上就是下载。如果浏览器向服务器发送信息,就变成了另一次 HTTP 请求。

但是,SSE 也有自己的优点。

  • SSE 使用 HTTP 协议,现有的服务器软件都支持。WebSocket 是一个独立协议。
  • SSE 属于轻量级,使用简单;WebSocket 协议相对复杂。
  • SSE 默认支持断线重连,WebSocket 需要自己实现断线重连。
  • SSE 一般只用来传送文本,二进制数据需要编码后传送,WebSocket 默认支持传送二进制数据。
  • SSE 支持自定义发送的消息类型。

因此,两者各有特点,适合不同的场合。

ChatGPT and sse

zhuanlan.zhihu.com/p/619722397

·ChatGPT的本质功能是单字接龙,

·长文由单字接龙的自回归所生成。

·通过提前训练才能让它生成人们想要的问答。

·训练方式是让它按照问答范式来做单字接龙。

·问答训练是为了让它学会「能举一反三的规律」。

·缺点是可能混淆记忆,无法直接查看和更新所学,并且高度依赖学习材料。


ChatGPT采用SSE技术实现流式输出,其原理如下:

  1. 建立连接:当用户与ChatGPT进行对话时,客户端与服务器之间会建立一个基于HTTP的长连接。这个连接通过SSE机制保持打开状态,允许服务器随时向客户端发送数据。
  2. 分步生成与实时推送:ChatGPT根据用户的输入和当前的上下文信息,逐步生成回答的一部分。每当有新的内容生成时,服务器就会通过SSE连接将这些内容作为事件推送给客户端。
  3. 客户端接收与展示:客户端通过JavaScript的EventSource对象监听SSE连接上的事件。一旦接收到服务器推送的数据,客户端会立即将其展示给用户,实现流式输出的效果。

dev.to/mirzaakhena…

define the header for this SSE support

w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("Access-Control-Allow-Origin", "*")
messageChan = make(chan string)//receive msg from ai output
flusher := w.(http.Flusher)

for  {
  select {
    case message := <- messageChan:
      fmt.Fprintf(w, "data: %s\n\n", message)
      flusher.Flush()

    case <-r.Context().Done():
      return
  }
}

Right before we are exiting the function, we need close the channel to avoid memory leak. We can do it by define it under defer. This defer statement should be right after we are instantiate the messageChan variable. Don't forget to put it as nil to make sure it can not be used anymore. ??

defer func() {
  close(messageChan)
  messageChan = nil
}()

go-openai

github.com/sashabarano…

package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	openai "github.com/sashabaranov/go-openai"
)

func main() {
	c := openai.NewClient("your token")
	ctx := context.Background()

	req := openai.ChatCompletionRequest{
		Model:     openai.GPT3Dot5Turbo,
		MaxTokens: 20,
		Messages: []openai.ChatCompletionMessage{
			{
				Role:    openai.ChatMessageRoleUser,
				Content: "Lorem ipsum",
			},
		},
		Stream: true,
	}
	stream, err := c.CreateChatCompletionStream(ctx, req)
	if err != nil {
		fmt.Printf("ChatCompletionStream error: %v\n", err)
		return
	}
	defer stream.Close()

	fmt.Printf("Stream response: ")
	for {
		response, err := stream.Recv()
		if errors.Is(err, io.EOF) {
			fmt.Println("\nStream finished")
			return
		}

		if err != nil {
			fmt.Printf("\nStream error: %v\n", err)
			return
		}

                        fmt.Printf(response.Choices[0].Delta.Content)
	}
}