如何流式传输补全
默认情况下,当您向 OpenAI 请求补全时,整个补全会在一次响应中生成并发送回来。
如果您正在生成长补全,等待响应可能需要很多秒。
为了更快地获得响应,您可以“流式传输”补全,即在生成过程中进行传输。这允许您在完整的补全完成之前开始打印或处理补全的开头部分。
要流式传输补全,请在调用聊天补全或补全端点时设置 stream=True
。这将返回一个对象,该对象会以仅数据服务器发送事件的形式流式传输响应。请从 delta
字段而不是 message
字段中提取块。
缺点
请注意,在生产应用程序中使用 stream=True
会增加内容审核的难度,因为部分补全可能更难评估。这可能对批准的使用产生影响。
示例代码
下面,此笔记本展示了:
- 典型的聊天补全响应是什么样的
- 流式聊天补全响应是什么样的
- 流式传输聊天补全可以节省多少时间
- 如何获取流式聊天补全响应的令牌使用数据
# !pip install openai
# 导入
import time # 用于测量 API 调用时间
from openai import OpenAI
import os
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY", "<如果未设置为环境变量,则为您的 OpenAI API 密钥>"))
1. 典型的聊天补全响应是什么样的
使用典型的 ChatCompletions API 调用,响应会先被计算出来,然后一次性返回。
# OpenAI ChatCompletion 请求示例
# https://platform.openai.com/docs/guides/text-generation/chat-completions-api
# 记录发送请求之前的时间
start_time = time.time()
# 发送 ChatCompletion 请求以计数到 100
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[
{'role': 'user', 'content': '数到 100,每个数字之间用逗号隔开,没有换行符。例如:1, 2, 3, ...'}
],
temperature=0,
)
# 计算接收响应所需的时间
response_time = time.time() - start_time
# 打印延迟时间和接收到的文本
print(f"请求后 {response_time:.2f} 秒收到完整响应")
print(f"完整响应:\n{response}")
请求后 1.88 秒收到完整响应
完整响应:
ChatCompletion(id='chatcmpl-9lMgdoiMfxVHPDNVCtvXuTWcQ2GGb', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100', role='assistant', function_call=None, tool_calls=None))], created=1721075651, model='gpt-july-test', object='chat.completion', system_fingerprint='fp_e9b8ed65d2', usage=CompletionUsage(completion_tokens=298, prompt_tokens=36, total_tokens=334))
回复可以与 response.choices[0].message
一起提取。
回复的内容可以与 response.choices[0].message.content
一起提取。
reply = response.choices[0].message
print(f"提取的回复: \n{reply}")
reply_content = response.choices[0].message.content
print(f"提取的内容: \n{reply_content}")
提取的回复:
ChatCompletionMessage(content='1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100', role='assistant', function_call=None, tool_calls=None)
提取的内容:
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100
2. 如何流式传输聊天补全
使用流式 API 调用,响应会通过事件流逐步返回块。在 Python 中,您可以使用 for
循环迭代这些事件。
让我们看看它的样子:
# 具有 stream=True 的 OpenAI ChatCompletion 请求示例
# https://platform.openai.com/docs/api-reference/streaming#chat/create-stream
# 一个 ChatCompletion 请求
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[
{'role': 'user', 'content': "1+1 是多少?用一个词回答。"}
],
temperature=0,
stream=True # 这次,我们将 stream 设置为 True
)
for chunk in response:
print(chunk)
print(chunk.choices[0].delta.content)
print("****************")
ChatCompletionChunk(id='chatcmpl-9lMgfRSWPHcw51s6wxKT1YEO2CKpd', choices=[Choice(delta=ChoiceDelta(content='', function_call=None, role='assistant', tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1721075653, model='gpt-july-test', object='chat.completion.chunk', system_fingerprint='fp_e9b8ed65d2', usage=None)
****************
ChatCompletionChunk(id='chatcmpl-9lMgfRSWPHcw51s6wxKT1YEO2CKpd', choices=[Choice(delta=ChoiceDelta(content='Two', function_call=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1721075653, model='gpt-july-test', object='chat.completion.chunk', system_fingerprint='fp_e9b8ed65d2', usage=None)
Two
****************
ChatCompletionChunk(id='chatcmpl-9lMgfRSWPHcw51s6wxKT1YEO2CKpd', choices=[Choice(delta=ChoiceDelta(content='.', function_call=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1721075653, model='gpt-july-test', object='chat.completion.chunk', system_fingerprint='fp_e9b8ed65d2', usage=None)
.
****************
ChatCompletionChunk(id='chatcmpl-9lMgfRSWPHcw51s6wxKT1YEO2CKpd', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, role=None, tool_calls=None), finish_reason='stop', index=0, logprobs=None)], created=1721075653, model='gpt-july-test', object='chat.completion.chunk', system_fingerprint='fp_e9b8ed65d2', usage=None)
None
****************
如上所示,流式响应具有 delta
字段而不是 message
字段。delta
可以包含:
- 角色令牌(例如
{"role": "assistant"}
) - 内容令牌(例如
{"content": "\n\n"}
) - 无内容(例如
{}
),表示流已结束
3. 流式传输聊天补全可以节省多少时间
现在让我们再次让 gpt-4o-mini
计数到 100,看看需要多长时间。
# 具有 stream=True 的 OpenAI ChatCompletion 请求示例
# https://platform.openai.com/docs/api-reference/streaming#chat/create-stream
# 记录发送请求之前的时间
start_time = time.time()
# 发送 ChatCompletion 请求以计数到 100
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[
{'role': 'user', 'content': '数到 100,每个数字之间用逗号隔开,没有换行符。例如:1, 2, 3, ...'}
],
temperature=0,
stream=True # 再次,我们将 stream 设置为 True
)
# 创建变量以收集块流
collected_chunks = []
collected_messages = []
# 迭代事件流
for chunk in response:
chunk_time = time.time() - start_time # 计算块的延迟时间
collected_chunks.append(chunk) # 保存事件响应
chunk_message = chunk.choices[0].delta.content # 提取消息
collected_messages.append(chunk_message) # 保存消息
print(f"请求后 {chunk_time:.2f} 秒收到消息: {chunk_message}") # 打印延迟和文本
# 打印延迟时间和接收到的文本
print(f"请求后 {chunk_time:.2f} 秒收到完整响应")
# 清理 collected_messages 中的 None
collected_messages = [m for m in collected_messages if m is not None]
full_reply_content = ''.join(collected_messages)
print(f"完整对话: {full_reply_content}")
请求后 1.14 秒收到消息:
请求后 1.14 秒收到消息: 1
请求后 1.14 秒收到消息: ,
请求后 1.14 秒收到消息:
请求后 1.14 秒收到消息: 2
请求后 1.16 秒收到消息: ,
请求后 1.16 秒收到消息:
请求后 1.16 秒收到消息: 3
请求后 1.35 秒收到消息: ,
请求后 1.35 秒收到消息:
请求后 1.35 秒收到消息: 4
请求后 1.36 秒收到消息: ,
请求后 1.36 秒收到消息:
请求后 1.36 秒收到消息: 5
请求后 1.36 秒收到消息: ,
请求后 1.36 秒收到消息:
请求后 1.36 秒收到消息: 6
请求后 1.36 秒收到消息: ,
请求后 1.36 秒收到消息:
请求后 1.36 秒收到消息: 7
请求后 1.36 秒收到消息: ,
请求后 1.36 秒收到消息:
请求后 1.36 秒收到消息: 8
请求后 1.36 秒收到消息: ,
请求后 1.36 秒收到消息:
请求后 1.36 秒收到消息: 9
请求后 1.36 秒收到消息: ,
请求后 1.36 秒收到消息:
请求后 1.36 秒收到消息: 10
请求后 1.36 秒收到消息: ,
请求后 1.36 秒收到消息:
请求后 1.36 秒收到消息: 11
请求后 1.36 秒收到消息: ,
请求后 1.36 秒收到消息:
请求后 1.36 秒收到消息: 12
请求后 1.36 秒收到消息: ,
请求后 1.36 秒收到消息:
请求后 1.45 秒收到消息: 13
请求后 1.45 秒收到消息: ,
请求后 1.45 秒收到消息:
请求后 1.45 秒收到消息: 14
请求后 1.45 秒收到消息: ,
请求后 1.45 秒收到消息:
请求后 1.45 秒收到消息: 15
请求后 1.45 秒收到消息: ,
请求后 1.45 秒收到消息:
请求后 1.46 秒收到消息: 16
请求后 1.46 秒收到消息: ,
请求后 1.46 秒收到消息:
请求后 1.47 秒收到消息: 17
请求后 1.47 秒收到消息: ,
请求后 1.47 秒收到消息:
请求后 1.49 秒收到消息: 18
请求后 1.49 秒收到消息: ,
请求后 1.49 秒收到消息:
请求后 1.52 秒收到消息: 19
请求后 1.52 秒收到消息: ,
请求后 1.52 秒收到消息:
请求后 1.53 秒收到消息: 20
请求后 1.53 秒收到消息: ,
请求后 1.53 秒收到消息:
请求后 1.55 秒收到消息: 21
请求后 1.55 秒收到消息: ,
请求后 1.55 秒收到消息:
请求后 1.56 秒收到消息: 22
请求后 1.56 秒收到消息: ,
请求后 1.56 秒收到消息:
请求后 1.58 秒收到消息: 23
请求后 1.58 秒收到消息: ,
请求后 1.58 秒收到消息:
请求后 1.59 秒收到消息: 24
请求后 1.59 秒收到消息: ,
请求后 1.59 秒收到消息:
请求后 1.62 秒收到消息: 25
请求后 1.62 秒收到消息: ,
请求后 1.62 秒收到消息:
请求后 1.62 秒收到消息: 26
请求后 1.62 秒收到消息: ,
请求后 1.62 秒收到消息:
请求后 1.65 秒收到消息: 27
请求后 1.65 秒收到消息: ,
请求后 1.65 秒收到消息:
请求后 1.67 秒收到消息: 28
请求后 1.67 秒收到消息: ,
请求后 1.67 秒收到消息:
请求后 1.69 秒收到消息: 29
请求后 1.69 秒收到消息: ,
请求后 1.69 秒收到消息:
请求后 1.80 秒收到消息: 30
请求后 1.80 秒收到消息: ,
请求后 1.80 秒收到消息:
请求后 1.80 秒收到消息: 31
请求后 1.80 秒收到消息: ,
请求后 1.80 秒收到消息:
请求后 1.80 秒收到消息: 32
请求后 1.80 秒收到消息: ,
请求后 1.80 秒收到消息:
请求后 1.80 秒收到消息: 33
请求后 1.80 秒收到消息: ,
请求后 1.80 秒收到消息:
请求后 1.80 秒收到消息: 34
请求后 1.80 秒收到消息: ,
请求后 1.80 秒收到消息:
请求后 1.80 秒收到消息: 35
请求后 1.80 秒收到消息: ,
请求后 1.80 秒收到消息:
请求后 1.80 秒收到消息: 36
请求后 1.80 秒收到消息: ,
请求后 1.80 秒收到消息:
请求后 1.82 秒收到消息: 37
请求后 1.82 秒收到消息: ,
请求后 1.82 秒收到消息:
请求后 1.83 秒收到消息: 38
请求后 1.83 秒收到消息: ,
请求后 1.83 秒收到消息:
请求后 1.84 秒收到消息: 39
请求后 1.84 秒收到消息: ,
请求后 1.84 秒收到消息:
请求后 1.87 秒收到消息: 40
请求后 1.87 秒收到消息: ,
请求后 1.87 秒收到消息:
请求后 1.88 秒收到消息: 41
请求后 1.88 秒收到消息: ,
请求后 1.88 秒收到消息:
请求后 1.91 秒收到消息: 42
请求后 1.91 秒收到消息: ,
请求后 1.91 秒收到消息:
请求后 1.93 秒收到消息: 43
请求后 1.93 秒收到消息: ,
请求后 1.93 秒收到消息:
请求后 1.93 秒收到消息: 44
请求后 1.93 秒收到消息: ,
请求后 1.93 秒收到消息:
请求后 1.95 秒收到消息: 45
请求后 1.95 秒收到消息: ,
请求后 1.95 秒收到消息:
请求后 2.00 秒收到消息: 46
请求后 2.00 秒收到消息: ,
请求后 2.00 秒收到消息:
请求后 2.00 秒收到消息: 47
请求后 2.00 秒收到消息: ,
请求后 2.00 秒收到消息:
请求后 2.00 秒收到消息: 48
请求后 2.00 秒收到消息: ,
请求后 2.00 秒收到消息:
请求后 2.00 秒收到消息: 49
请求后 2.00 秒收到消息: ,
请求后 2.00 秒收到消息:
请求后 2.00 秒收到消息: 50
请求后 2.00 秒收到消息: ,
请求后 2.00 秒收到消息:
请求后 2.00 秒收到消息: 51
请求后 2.00 秒收到消息: ,
请求后 2.04 秒收到消息:
请求后 2.04 秒收到消息: 52
请求后 2.04 秒收到消息: ,
请求后 2.04 秒收到消息:
请求后 2.04 秒收到消息: 53
请求后 2.04 秒收到消息: ,
请求后 2.13 秒收到消息:
请求后 2.13 秒收到消息: 54
请求后 2.14 秒收到消息: ,
请求后 2.14 秒收到消息:
请求后 2.14 秒收到消息: 55
请求后 2.14 秒收到消息: ,
请求后 2.14 秒收到消息:
请求后 2.14 秒收到消息: 56
请求后 2.14 秒收到消息: ,
请求后 2.14 秒收到消息:
请求后 2.16 秒收到消息: 57
请求后 2.16 秒收到消息: ,
请求后 2.16 秒收到消息:
请求后 2.17 秒收到消息: 58
请求后 2.17 秒收到消息: ,
请求后 2.17 秒收到消息:
请求后 2.19 秒收到消息: 59
请求后 2.19 秒收到消息: ,
请求后 2.19 秒收到消息:
请求后 2.21 秒收到消息: 60
请求后 2.21 秒收到消息: ,
请求后 2.21 秒收到消息:
请求后 2.34 秒收到消息: 61
请求后 2.34 秒收到消息: ,
请求后 2.34 秒收到消息:
请求后 2.34 秒收到消息: 62
请求后 2.34 秒收到消息: ,
请求后 2.34 秒收到消息:
请求后 2.34 秒收到消息: 63
请求后 2.34 秒收到消息: ,
请求后 2.34 秒收到消息:
请求后 2.34 秒收到消息: 64
请求后 2.34 秒收到消息: ,
请求后 2.34 秒收到消息:
请求后 2.34 秒收到消息: 65
请求后 2.34 秒收到消息: ,
请求后 2.34 秒收到消息:
请求后 2.34 秒收到消息: 66
请求后 2.34 秒收到消息: ,
请求后 2.34 秒收到消息:
请求后 2.34 秒收到消息: 67
请求后 2.34 秒收到消息: ,
请求后 2.34 秒收到消息:
请求后 2.36 秒收到消息: 68
请求后 2.36 秒收到消息: ,
请求后 2.36 秒收到消息:
请求后 2.36 秒收到消息: 69
请求后 2.36 秒收到消息: ,
请求后 2.36 秒收到消息:
请求后 2.38 秒收到消息: 70
请求后 2.38 秒收到消息: ,
请求后 2.38 秒收到消息:
请求后 2.39 秒收到消息: 71
请求后 2.39 秒收到消息: ,
请求后 2.39 秒收到消息:
请求后 2.39 秒收到消息: 72
请求后 2.39 秒收到消息: ,
请求后 2.39 秒收到消息:
请求后 2.39 秒收到消息: 73
请求后 2.39 秒收到消息: ,
请求后 2.39 秒收到消息:
请求后 2.39 秒收到消息: 74
请求后 2.39 秒收到消息: ,
请求后 2.39 秒收到消息:
请求后 2.39 秒收到消息: 75
请求后 2.39 秒收到消息: ,
请求后 2.40 秒收到消息:
请求后 2.40 秒收到消息: 76
请求后 2.40 秒收到消息: ,
请求后 2.42 秒收到消息:
请求后 2.42 秒收到消息: 77
请求后 2.42 秒收到消息: ,
请求后 2.51 秒收到消息:
请求后 2.51 秒收到消息: 78
请求后 2.51 秒收到消息: ,
请求后 2.52 秒收到消息:
请求后 2.52 秒收到消息: 79
请求后 2.52 秒收到消息: ,
请求后 2.52 秒收到消息:
请求后 2.52 秒收到消息: 80
请求后 2.52 秒收到消息: ,
请求后 2.52 秒收到消息:
请求后 2.52 秒收到消息: 81
请求后 2.52 秒收到消息: ,
请求后 2.52 秒收到消息:
请求后 2.52 秒收到消息: 82
请求后 2.52 秒收到消息: ,
请求后 2.60 秒收到消息:
请求后 2.60 秒收到消息: 83
请求后 2.60 秒收到消息: ,
请求后 2.64 秒收到消息:
请求后 2.64 秒收到消息: 84
请求后 2.64 秒收到消息: ,
请求后 2.64 秒收到消息:
请求后 2.64 秒收到消息: 85
请求后 2.64 秒收到消息: ,
请求后 2.64 秒收到消息:
请求后 2.66 秒收到消息: 86
请求后 2.66 秒收到消息: ,
请求后 2.66 秒收到消息:
请求后 2.66 秒收到消息: 87
请求后 2.66 秒收到消息: ,
请求后 2.66 秒收到消息:
请求后 2.68 秒收到消息: 88
请求后 2.68 秒收到消息: ,
请求后 2.68 秒收到消息:
请求后 2.69 秒收到消息: 89
请求后 2.69 秒收到消息: ,
请求后 2.69 秒收到消息:
请求后 2.72 秒收到消息: 90
请求后 2.72 秒收到消息: ,
请求后 2.72 秒收到消息:
请求后 2.82 秒收到消息: 91
请求后 2.82 秒收到消息: ,
请求后 2.82 秒收到消息:
请求后 2.82 秒收到消息: 92
请求后 2.82 秒收到消息: ,
请求后 2.82 秒收到消息:
请求后 2.82 秒收到消息: 93
请求后 2.82 秒收到消息: ,
请求后 2.82 秒收到消息:
请求后 2.82 秒收到消息: 94
请求后 2.82 秒收到消息: ,
请求后 2.82 秒收到消息:
请求后 2.82 秒收到消息: 95
请求后 2.82 秒收到消息: ,
请求后 2.82 秒收到消息:
请求后 2.82 秒收到消息: 96
请求后 2.82 秒收到消息: ,
请求后 2.82 秒收到消息:
请求后 2.82 秒收到消息: 97
请求后 2.82 秒收到消息: ,
请求后 2.82 秒收到消息:
请求后 2.82 秒收到消息: 98
请求后 2.82 秒收到消息: ,
请求后 2.82 秒收到消息:
请求后 2.82 秒收到消息: 99
请求后 2.82 秒收到消息: ,
请求后 2.82 秒收到消息:
请求后 2.82 秒收到消息: 100
请求后 2.82 秒收到消息: None
请求后 2.82 秒收到完整响应
完整对话: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100
时间比较
在上面的示例中,两个请求都花了大约 4 到 5 秒才完全完成。请求时间会因负载和其他随机因素而异。
但是,通过流式请求,我们在 0.1 秒后收到了第一个令牌,之后每约 0.01-0.02 秒收到一个令牌。
4. 如何获取流式聊天补全响应的令牌使用数据
您可以通过设置 stream_options={"include_usage": True}
来获取流式响应的令牌使用统计信息。这样做时,将作为最后一个块流式传输一个额外的块。您可以通过此块上的 usage
字段访问整个请求的用法数据。当您设置 stream_options={"include_usage": True}
时,有几点需要注意:
- 除最后一个块外,所有块上的
usage
字段值都将为 null。 - 最后一个块上的
usage
字段包含整个请求的令牌使用统计信息。 - 最后一个块上的
choices
字段将始终是一个空数组[]
。
让我们通过 2 中的示例看看它是如何工作的。
# 具有 stream=True 和 stream_options={"include_usage": True} 的 OpenAI ChatCompletion 请求示例
# 一个 ChatCompletion 请求
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[
{'role': 'user', 'content': "1+1 是多少?用一个词回答。"}
],
temperature=0,
stream=True,
stream_options={"include_usage": True}, # 检索流响应的令牌使用情况
)
for chunk in response:
print(f"choices: {chunk.choices}\nusage: {chunk.usage}")
print("****************")
choices: [Choice(delta=ChoiceDelta(content='', function_call=None, role='assistant', tool_calls=None), finish_reason=None, index=0, logprobs=None)]
usage: None
****************
choices: [Choice(delta=ChoiceDelta(content='Two', function_call=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)]
usage: None
****************
choices: [Choice(delta=ChoiceDelta(content='.', function_call=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)]
usage: None
****************
choices: [Choice(delta=ChoiceDelta(content=None, function_call=None, role=None, tool_calls=None), finish_reason='stop', index=0, logprobs=None)]
usage: None
****************
choices: []
usage: CompletionUsage(completion_tokens=2, prompt_tokens=18, total_tokens=20)
****************