流式VS非流式对比

非流失

import os
from openai import OpenAI

client = OpenAI(
    # This is the default and can be omitted
    api_key=os.environ.get("OPENAI_API_KEY"),   # 获取本地变量
    base_url=os.environ.get("OPENAI_API_BASE"))   # 获取本地变量

chat_completion = client.chat.completions.create(
    messages=[
        {"role": "system", "content": "You are a helpful assistant"},
        {"role": "user", "content": "您好,我叫老王"},
        {"role": "assistant", "content": "Say this is a test"},
    ],
    model="gpt-3.5-turbo")

print(chat_completion.choices[0].message.content)

问题

当你从OpenAI请求一个完成内容时,如果你在生成长的完成内容,等待响应可能需要几秒钟的时间。影响用户体验

思路,加个时间判断看看程序执行的时间。

查看代码
 import time
import os
from openai import OpenAI

client = OpenAI(
    # This is the default and can be omitted
    api_key=os.environ.get("OPENAI_API_KEY"), 
    base_url=os.environ.get("OPENAI_API_BASE"))

start_time = time.time()
response = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": "用1000字介绍SSE",
        }
    ],
    model="gpt-3.5-turbo",
)

print(f'response:{response.choices[0].message.content}')

end_time = time.time()
elapsed_time = end_time - start_time
print(f"代码执行时间: {elapsed_time}秒")

流失

HTTP 请求

SSE

服务器发送事件(Server-sentevents,简称SSE),服务器向客户端推送数据,客户端通过事件监听器接收数据。
严格地说,HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。
也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载.

向OpenAl请求流式输出

要流式传输完成内容,请在调用聊天完成或完成端点时设置stream=True。

查看代码
 import requests
import os

headers = {
    'Authorization': f'Bearer {os.environ.get("OPENAI_API_KEY")}'}

body = {
    "model": 'gpt-3.5-turbo',
    "messages": [
        {
            "role": "user",
            "content": "hello"
        }
    ],
    'stream': True
}

url = f'{os.environ.get("OPENAI_API_BASE")}/chat/completions'
# 当stream等于True时,requests不会立即下载响应内容,而是在访问r,content属性时才开始下载。适合下载大文件,一个块一个块
r = requests.post(url, headers=headers, json=body, stream=True)    # 这里的 stream=True 这里里面是 request库里参数。

# print(r.text)

解析SSE Response

OpenAi流式返回格式

# response
data:数据块\n\n
data:数据块\n\n
# 数据块
{"id":"chatcmpl-8cPm5v40zHPdnMTYfUNuH50tZBPho","obiect":"chat.completion.chunk","created":1704166233,"model":"gpt-35-trbo","system fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finiish reason":null}]}

# 倒数第2个数据块
{"id" :" chatcmpl-8cPm5v40zHPdnMTYfUNuH50tZBPho","object":"chat.complet ion.chunk","created":1704166233,"model":"gpt-35-turbo","system fingerprint":null,"choices":[{“index":0,"delta":{},"finish reason":"stop"}]}

# 最后一个数据块
[DONE]

object 不同

choices 没有 meagess 换成了 delta。也是一个 role和content

finish_reason 返回的不同。

分行打印OpenAl流式返回的数据块

for line in r.text.split('\n\n'):
print(line)

解析OpenAl流式返回的内容

content从 delta 字段而不是 message 字段提取数据块, 

delta可包含的内容

  • a role token (e.g., {"role":"assistant"})
  • a content token (e.g., {'content": "\n\n"}
  • nothing(e.g., {}), when the stream is over
查看代码
 # 这里边举例子,r.iter_lines(): 得存在
# iter lines:一行一行的下载内容
import json

for line in r.iter_lines():
    if not line:
        continue
    data = json.loads(line.decode('utf-8').lstrip('data: '))    # 去掉data和空格. 使用json解析字符串
    finish_reason = data['choices'][0]['finish reason']
    if finish_reason == 'stop':    # delta 等于 null的时候, finish_reason 等于stop
        break
    print(data['choices'][0]['delta']['content'], end='')

print('\n')

使用requests 请求OpenAI流失返回内容

用request,了解运行过程
 # 这里边举例子,r.iter_lines(): 得存在
# iter lines:一行一行的下载内容
import json
import requests
import os

api_key = os.environ.get("OPENAI_API_KEY"),
base_url = os.environ.get("OPENAI_API_BASE")


headers = {
    'Authorization': f'Bearer {api_key}'}

body = {
    "model": 'gpt-3.5-turbo',
    "messages": [
        {
            "role": "user",
            "content": "用1000字介绍SSE"
        }
    ],
    'stream': True
}

url = f'{base_url}/chat/completions'
# 当stream等于True时,requests不会立即下载响应内容,而是在访问r,content属性时才开始下载。适合下载大文件,一个块一个块
r = requests.post(url, headers=headers, json=body, stream=True)    # 这里的 stream=True 这里里面是 request库里参数。

# print(r.text)


for line in r.iter_lines():
    if not line:
        continue
    data = json.loads(line.decode('utf-8').lstrip('data: '))    # 去掉data和空格. 使用json解析字符串
    finish_reason = data['choices'][0]['finish reason']
    if finish_reason == 'stop':    # delta 等于 null的时候, finish_reason 等于stop
        break
    print(data['choices'][0]['delta']['content'], end='')

print('\n')

 

使用openai-python库解析

解析服务器发送事件(Server-sent events,SSE)并非易事,应该谨慎进行。简单的策略,如通过换行符分割,可能会导致解析错误。建议使用支持SSE协议的已有客户端库。这些库已经考虑了SSE的所有规则和特殊情况,能够正确地解析事件流。使用这些库可以避免手动解析时可能遇到的复杂性和错误,使得开发者可以专注于如何使用事件数据,而不是如何解析它们。

import os
from openai import OpenAI

client = OpenAI(
    # This is the default and can be omitted
    api_key=os.environ.get("OPENAI_API_KEY"),
    base_url=os.environ.get("OPENAI_API_BASE"))


stream = client.chat.completions.create(
    messages=[
        {
            "role": "user",
            "content": "Say this is a test"
        }
    ],
    model="gpt-3.5-turbo",
    stream=True
)
print(stream.choices[0].message.content)


# 返回数据
for chunk in stream:
    if chunk.choices[0].delta.content is not None:
        print(chunk.choices[0].delta.content, end="")

 

OpenAl流式请求的缺点

内容审查问题。请注意,在生产应用程序中使用 stream=True 会使内容的审核变得更加困难,因为部分完成内容可能更难评估。
向C端用户提供服务的时候,需要注意内容审查问题。
无 usage 字段。流式响应的另一个小缺点是响应不再包括 usage 字段,告诉你消耗了多少令牌。在接收和组合所有响应后,你可以使用 tiktoken 自己计算这一点。

 

总结
简单来说,这种流式传输方式使得客户端在长内容生成过程中无需等待整个内容完成,就可以逐步接收到生成的内容,从而提高了响应速度和用户体验。

 

 

 

 

 

 

 

 

 

 

 

 

 

end...

posted @   王竹笙  阅读(913)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示