OpenAI API stream=true 是什么意思?流式输出、代码与排错
简答:OpenAI API 的 stream=true 表示启用流式响应:模型生成内容时,服务器会通过 SSE 分块发送,客户端可以边接收边显示。它主要改善首个片段到达时间和交互体验,不等于模型生成更快,也不会因为这个参数本身减少 token 或费用。Responses API 通常读取带类型的事件,Chat Completions 则读取每个 chunk 的 delta,并结合 finish_reason 或 [DONE] 判断是否正常结束。
定义:stream=true 是 API 请求中的布尔参数,用于让模型响应以服务器发送事件(SSE)或增量数据分块返回,客户端需要循环读取并拼接内容。
先看结论:stream=true 改变的是返回方式
本文是本站对公开技术文档的整理,本站不是相关 API 品牌的官方网站;以下内容按 2026-11-04 检索到的资料核对。最短理解是:stream=true 不是让模型换一个生成模式,也不是保证生成更快,而是让已经生成的内容更早发送到客户端。
| 对比项 | stream=false | stream=true |
|---|---|---|
| 返回方式 | 通常等待完整结果后返回一个响应对象 | 生成过程中持续返回事件或数据分块 |
| 客户端读取 | 直接读取完整 JSON 中的结果字段 | 循环读取 SSE、chunk 或 delta,再自行拼接 |
| 文本字段 | Chat Completions 常见为 message.content | Chat Completions 常见为 delta.content;Responses API 常监听文本增量事件 |
| 用户体验 | 实现简单,适合短请求和后台任务 | 可边生成边显示,适合聊天窗口和长文本 |
| 首个片段时间 | 通常要等完整响应准备好 | 通常可以更早看到首个事件或文本片段 |
| 完整响应时间 | 由模型、网络和服务端处理决定 | 不会因参数本身保证更快完成,仍受模型、网络和中间层影响 |
| 费用与 token | 按实际请求和模型规则核算 | 不能因为逐字显示就推断更省 token 或更便宜 |
OpenAI 的流式指南说明,默认请求通常在完整输出生成后一次性返回;设置 stream=true 后,客户端可以在模型继续生成时先处理开头内容,并通过服务器发送事件(SSE)接收数据。可查看 OpenAI Streaming API responses 指南。
可以先记住这个简化示意:非流式是“一次返回完整 JSON”;流式是“多次返回增量数据,最后以完成事件或结束标记收尾”。实际事件字段要按 Responses API、Chat Completions 或具体兼容接口分别核对。
最短主流程:启用、读取、判断完成
- 先用非流式请求确认基础配置。确认 API Key、Base URL、模型 ID、请求路径和普通响应都正常,再只增加
stream=true,不要同时修改多项配置。 - 按接口类型读取。Responses API 遍历类型化事件;Chat Completions 遍历 chunk,并从
choices[0].delta.content读取增量文本。 - 持续拼接文本。每次收到非空片段就追加到本地缓冲区,同时可以立即刷新界面。
- 不要只看循环是否结束。Chat Completions 要读取最终 chunk 的
finish_reason;原始 SSE 还要确认收到data: [DONE]。Responses API 则应关注response.completed,并检查响应状态和错误字段。 - 区分正常完成和异常断流。如果连接断开、没有完成事件、没有
[DONE],或最后一个 chunk 的结束信息缺失,应把结果标记为未完成,而不是直接当作完整答案。
OpenAI 的 Responses 流式事件参考列出了 response.output_text.delta、response.output_text.done、response.completed 和错误事件等类型。Chat Completions 的事件参考则说明,流式 chunk 包含 delta、finish_reason 和可选的 usage 字段。分别参考 Responses streaming events 与 Chat Completions streaming events。
Responses API:Python 和 JavaScript 最小示例
Python:读取文本增量、完成事件和错误
下面的 MODEL_ID 只是占位符,不代表某个已确认的模型名称。运行前应通过实际平台的模型列表、权限说明和接口文档核对可用模型。
from openai import OpenAI
client = OpenAI()
stream = client.responses.create(
model="MODEL_ID",
input="请用一句话解释 SSE。",
stream=True,
)
parts = []
completed = False
for event in stream:
if event.type == "response.output_text.delta":
parts.append(event.delta)
print(event.delta, end="", flush=True)
elif event.type == "response.completed":
completed = True
print("\\n完成")
elif event.type == "error":
print(f"\\n流式错误:{event}")
answer = "".join(parts)
if not completed:
print("\\n结果未收到 response.completed,需按未完成处理")
JavaScript:用异步迭代读取事件
import OpenAI from "openai";
const client = new OpenAI();
const stream = await client.responses.create({
model: "MODEL_ID",
input: "请用一句话解释 SSE。",
stream: true,
});
let fullText = "";
let completed = false;
for await (const event of stream) {
if (event.type === "response.output_text.delta") {
fullText += event.delta;
process.stdout.write(event.delta);
} else if (event.type === "response.completed") {
completed = true;
process.stdout.write("\\n完成\\n");
} else if (event.type === "error") {
console.error("流式错误:", event);
}
}
if (!completed) {
console.error("结果未收到 response.completed,需按未完成处理");
}
Responses API 使用语义化事件,文本通常从 response.output_text.delta 的 delta 字段中读取;收到 response.completed 只能说明服务端发出了完成事件,应用仍应结合本地拼接结果、异常状态和取消状态判断最终业务结果。
Chat Completions:delta.content、finish_reason 和 [DONE]
Chat Completions 的流式响应与非流式响应字段不同。非流式结果通常从完整消息的 message.content 读取;流式 chunk 使用 delta 表示本次增量,某些 chunk 可能只有角色、工具调用信息、结束原因或 usage,并不包含文本。
Python:拼接文本并读取 finish_reason
from openai import OpenAI
client = OpenAI()
stream = client.chat.completions.create(
model="MODEL_ID",
messages=[
{"role": "user", "content": "请用一句话解释流式输出。"}
],
stream=True,
)
parts = []
finish_reason = None
for chunk in stream:
if not chunk.choices:
continue
choice = chunk.choices[0]
text = choice.delta.content
if text is not None:
parts.append(text)
print(text, end="", flush=True)
if choice.finish_reason is not None:
finish_reason = choice.finish_reason
answer = "".join(parts)
print(f"\\nfinish_reason={finish_reason}")
JavaScript:读取增量和结束原因
import OpenAI from "openai";
const client = new OpenAI();
const stream = await client.chat.completions.create({
model: "MODEL_ID",
messages: [
{ role: "user", content: "请用一句话解释流式输出。" }
],
stream: true,
});
let answer = "";
let finishReason = null;
for await (const chunk of stream) {
const choice = chunk.choices?.[0];
const text = choice?.delta?.content || "";
answer += text;
process.stdout.write(text);
if (choice?.finish_reason) {
finishReason = choice.finish_reason;
}
}
console.log(`\\nfinish_reason=${finishReason}`);
finish_reason 常见值包括 stop、length、tool_calls 和内容过滤相关结果,具体取值应以实际接口参考为准。原始 HTTP SSE 场景下,流还会以 data: [DONE] 消息终止;因此,业务完成判断不应只依赖客户端循环是否自然结束。可核对 Chat Completions 流式事件参考 和 Chat Completions 创建接口参考。
usage、token 和费用:不要把流式显示当成计费变化
stream=true 主要改变传输时机,不会因为该参数本身减少模型生成的 token,也不能单凭逐字显示判断请求更便宜。端到端总耗时仍可能受到模型处理、网络、客户端、代理缓冲和重试影响。
Chat Completions 的流式 usage 不能简单理解为每个 chunk 都有完整统计。官方接口参考说明,只有在请求中设置 stream_options: {"include_usage": true} 时,才会额外发送包含整次请求 token 用量的 chunk;其他 chunk 的 usage 可能为空。这个最终 usage chunk 位于 [DONE] 之前,如果流被中断或取消,可能收不到它,因此不能把“没有 usage”直接等同于“没有产生用量”。
示例配置如下,是否被兼容接口支持仍需按实际服务文档核对:
stream = client.chat.completions.create(
model="MODEL_ID",
messages=[{"role": "user", "content": "你好"}],
stream=True,
stream_options={"include_usage": True},
)
如果目标是降低成本,应检查输入长度、输出上限、模型选择、重复请求和缓存策略,而不是只切换 stream 参数。最终用量应结合实际响应中的 usage、模型计费规则和服务商文档核验。
设置 stream=true 仍然一次性显示:按链路排查
| 现象 | 优先检查 | 处理方法 |
|---|---|---|
| 客户端没有任何输出 | 是否真的遍历了流对象;是否把流对象当普通 JSON 解析 | 先打印事件类型或 chunk 摘要,确认代码进入迭代循环 |
| 有 chunk 但文本为空 | 是否读取了错误字段;是否把 message.content 和 delta.content 混用 | 先打印单个 chunk 的结构,再按接口类型读取字段 |
| 命令行能分块,网页最后才显示 | 应用服务器、反向代理、压缩层或浏览器链路是否缓冲 SSE | 依次测试上游、应用接口、代理和浏览器,定位第一层开始缓冲的位置 |
| 输出中途停止 | 是否收到 finish_reason、[DONE]、response.completed 或错误事件 | 记录最后一个事件、HTTP 状态、连接断开时间和请求标识,不要只记录“请求失败” |
| usage 偶尔缺失 | 是否启用 stream_options.include_usage;流是否提前中断 | 按接口文档启用 usage,并允许最终统计因断流而缺失 |
| 输出重复或缺字 | 是否重复消费同一个流;重试时是否把旧缓冲区继续追加 | 每次请求创建独立缓冲区,重试时重新建立结果状态 |
如果你通过 OpenAI 兼容接口或 API Router 接入,必须单独核对它是否支持 SSE、是否透传 finish_reason、是否支持 stream_options.include_usage、是否会缓冲响应,以及它采用的是 Responses API 还是 Chat Completions 的字段语义。请求格式兼容,不代表所有流式事件和错误字段都完全一致。
安全边界与上线前自查
- 不要把 API Key 放在浏览器代码中。浏览器前端应调用自己的后端,由后端保存密钥并转发经过授权的请求。
- 流式输出要单独考虑内容审核。官方流式指南指出,部分输出更难评估;如果业务需要严格审核,不能只在完整输出后才设计安全控制。
- 记录完成状态。至少区分正常完成、达到长度上限、工具调用、内容过滤、取消和网络断开,不要把所有情况都标成成功。
- 保存必要的可观测字段。可记录接口类型、模型实际 ID、HTTP 状态、首个事件时间、最后事件时间、完成标记、错误对象和服务端请求 ID;不要记录完整 API Key。
- 半截文本不能静默当最终答案。没有完成事件、
finish_reason或[DONE]时,应标记结果未完成,并提供重试或重新生成路径。
上线前可以按这个顺序核验:先用非流式请求确认 Key、Base URL、路径和模型;再只增加 stream=true;然后用短文本测试正常完成;最后测试长文本、客户端取消、代理链路、超时和断流恢复。
常见问题
stream=true 是不是让模型生成速度更快?
不是。它让已经生成的内容更早发送到客户端,主要改善首个片段到达时间和感知延迟;完整输出仍受模型、网络、代理和客户端处理影响。核验时应分别记录请求开始、首个事件、最后事件和完整结果时间。
Chat Completions 流式响应什么时候算完成?
不能只看循环是否结束。应读取最终 chunk 的 finish_reason,并在原始 SSE 场景确认收到 data: [DONE];如果没有这些完成信号就断开,应按未完成或异常中断处理。
stream=true 会减少 token 用量或 API 费用吗?
不能这样推断。stream=true 主要改变响应传输方式。实际用量取决于输入、输出、模型和服务规则;Chat Completions 可能需要显式设置 stream_options.include_usage 才能在流末获得 usage,而且流中断时可能收不到最终 usage chunk。
Responses API 和 Chat Completions 的流式读取方式一样吗?
不一样。Responses API 使用带 type 的语义化事件,例如 response.output_text.delta 和 response.completed;Chat Completions 通常从每个 chunk 的 choices[0].delta.content 读取,并结合 finish_reason 和 [DONE] 判断结束。
为什么设置 stream=true 后网页还是一次性显示?
常见原因是应用服务器、反向代理、压缩层或浏览器链路缓冲了 SSE。先用原始命令行客户端确认上游是否连续返回,再逐层测试应用接口、代理和浏览器,找到开始缓冲的那一层。
流式响应中途断了,已收到的文字能直接当最终答案吗?
不建议。除非收到正常完成信号并确认没有错误,否则应把已拼接内容标记为未完成。记录最后一个事件、finish_reason、HTTP 状态和断开时间,再决定重试、续接或重新生成。