使用openai库进行流式输出时,到底发生了什么

666 阅读5分钟

本篇文章首发于算法工程笔记,更多内容,欢迎关注。

注:本文章提到的 openai ,均指 python 语言的一个库OpenAI发明 GPT 系列模型的公司。

相信使用过大模型接口的开发者,对 python 的 openai 库都不陌生。因为几乎所有的大模型,都有兼容 OpenAI 定义的 API,而 openai 库,则是 python 语言使用大模型 API 最方便的工具。

关于 openai 库的接口定义,详见 OpenAI API Reference 。而在绝大多数使用到大模型进行实时问答的应用中,使用到最多的就是流式输出的功能了。

这里就用两篇文章,详细介绍下 openai 库的流式输出原理以及在实际应用中的注意点。

第一篇文章,着重介绍 openai 库流式输出的基本原理,以及当我们使用这个库的流式输出功能时,具体发生了什么

openai库的流式输出原理

在 openai 库的 github 页面上,有一句关于 streaming response 的原理介绍:

We provide support for streaming responses using Server Side Events (SSE) .

也就是说,流式输出是使用 Server Side Events (SSE) 实现的。

那么具体什么是 SSE 呢?

SSE 是什么

关于 SSE 的具体定义,这里选取阮一峰老师《Server-Sent Events 教程》中的一段话来解释:

严格地说,HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)
也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载
SSE 就是利用这种机制,使用流信息向浏览器推送信息它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。

所以,可以理解 SSE 是一种基于 HTTP 协议流式信息推送机制。

注意这里的两个关键词:

  • 基于 HTTP 协议:说明 SSE 本质上还是需要建立 HTTP 连接
  • 流式信息推送:说明是服务端主动向客户端发送消息

理解了这两点,就能后更好的理解下一节中,网络包的传输过程

流式输出时,网络包是如何传递的

这里使用 tcpdump 命令抓取网络包并利用 wireshark 工具进行可视化。

关于这两个工具的具体用法可以参考《实战!我用 Wireshark 让你“看见“ TCP》

使用到的具体抓包命令如下:

tcpdump -i any tcp and port 8700 -w ./1.pcap

具体参数的含义如下:

  • -i any: 指定所有可用的网络接口
  • tcp and port 8700: 表示只捕获 TCP 协议的数据包,并且目标或源端口为 8700 的数据包
  • -w ./1.pcap: 将捕获的数据包写入文件,文件名为 1.pcap,并保存在当前目录 (./) 下

综上所述,这条命令的作用是从所有网络接口捕获 TCP 协议端口为 8700 的数据包,并将这些数据包保存到名为 1.pcap 的文件中

这里 8700 是大模型服务所在的端口。

使用 openai 库进行一次流式请求的方式如下:

import os
from openai import OpenAI

base_url, api_key, model = BASE_URL, API_KEY, MODEL
client = OpenAI(api_key=api_key, base_url=base_url)

def test(stream=True):
    query = f"please introduce yourself briefly, no more than ten words."
    messages = [{"role""user""content": query}]
    req_dic = {"model": model, "messages": messages, "stream": stream}
    response = client.chat.completions.create(**req_dic)
    if stream:
        res = ""
        for chunk in response:
            if chunk.choices[0].delta.content is not None:
                res += chunk.choices[0].delta.content
        pass

test()

运行上述代码,便可以抓取对应的数据包。

然后通过 wireshark ,可以显示这次请求的 数据传输图

数据包传输图
注:上图中10开头的为客户端的IP,49开头的为服务端的IP

结合第一部分的 SSE 是一种基于 HTTP 协议的流式信息推送机制,可以看出:

  1. client创建时,客户端和服务端进行了 TCP 的连接(传输图中的前三帧
  2. 客户端发起请求后,实际上是在TCP连接中发送了一个 HTTP 请求(传输图中的第4、5帧),详见下图

HTTP请求数据包内容

  1. 响应传输完毕后,客户端和服务端进行了 TCP 连接的断开,并且是由客户端发起的挥手请求(传输图中的最后三帧,这里的4次挥手,变成了3次,是因为服务端的 ACK 和 FIN 消息合并为了一帧)
  2. 在响应过程中,服务端有了一部分的 chunk 结果,便会推送给客户端,而客户端收到 chunk 结果后,也会给客户端以回复(这里注意,不管客户端有没有 for 循环的遍历,甚至我们可以在for循环之前,添加一句sleep命令,服务端也始终会在生成结果的下一刻立马将结果推送给客户端,这也就是流式信息推送机制

整体请求的TCP流量图如下:

TCP流量图

以上就是一个正常的流式请求时,网络包传输的过程。

下一篇文章,介绍几种异常流式请求时,网络连接情况的分析