Streaming 改善的是“多久看到第一个结果”,不是自动缩短完整生成时间。Responses 新项目应按 event.type 消费语义化 SSE 事件;前端不能只拼接 delta,还必须等到 response.completed 才标记成功。流中断、failed、incomplete 或 error 都不能假装成完整答案。
官方资料(2026-10-30 核对):Streaming API responses · Responses streaming events · Chat Completions create · Function calling
Streaming 到底解决什么问题?
非流式请求会等模型完成整段输出后一次返回。流式请求通过 Server-Sent Events 持续发送增量,让页面先显示开头、进度或工具名称。它适合聊天、长报告、代码生成和需要“正在响应”反馈的界面。
| 维度 | 非流式 | 流式 |
|---|---|---|
| 首屏体验 | 等待完整结果 | 收到 delta 后即可逐步展示 |
| 完整结果判断 | 一次响应成功后读取 | 必须等待 completed/结束标志 |
| 错误处理 | 通常在响应前失败 | 可能已经显示部分内容后再失败 |
| 内容审核 | 可先检查完整输出再展示 | 部分文本可能先被用户看到 |
| 实现复杂度 | 较低 | 需要事件状态机、缓冲、取消和断线处理 |
Streaming 不会自动降低 Token、费用或 429 概率。限流、模型耗时和总输出长度仍需按 速率限制、Usage 与正式模型规则核对。
Responses API:按事件类型消费 SSE
Responses 使用 typed events。文本流至少关注 response.created、response.output_text.delta、response.completed 和 error;生产代码还应处理 response.failed、response.incomplete、拒答和连接提前结束。
import OpenAI from "openai";
const client = new OpenAI();
const stream = await client.responses.create({
model: "YOUR_MODEL",
input: "用三点解释提示词缓存。",
stream: true
});
let text = "";
let completed = false;
for await (const event of stream) {
switch (event.type) {
case "response.output_text.delta":
text += event.delta;
process.stdout.write(event.delta);
break;
case "response.completed":
completed = true;
console.log("\n完成,usage:", event.response.usage);
break;
case "response.failed":
case "response.incomplete":
case "error":
console.error("流式请求未完整完成", event);
break;
}
}
if (!completed) {
throw new Error("stream ended before response.completed");
}response.output_text.delta 可以出现多次;事件中还带有 item_id、output_index、content_index 和顺序信息。简单单文本界面可直接追加 delta;存在多个 output Item、工具或并发内容时,应按这些索引分别聚合,不能把所有字符串盲目拼到一个缓冲区。
Responses 流式事件生命周期
| 事件 | 发生时机 | 应用动作 |
|---|---|---|
response.created | 响应对象创建 | 记录 response ID,初始化状态 |
response.output_item.added | 消息、工具等输出 Item 出现 | 按 output_index/item_id 建立缓冲区 |
response.output_text.delta | 新增文本片段 | 追加文本并节流渲染 |
response.output_text.done | 某个文本部分完成 | 保存该文本部分,不等于整个 Response 已完成 |
response.completed | 整个响应成功完成 | 确认最终状态,读取完整 response 与 usage |
response.failed / response.incomplete | 生成失败或未完成 | 保留已显示内容但标记不完整,禁止当成成功结果 |
error | 流内错误 | 记录 code、message、response/request ID,按错误类型处理 |
不要只因为连接自然关闭就判定成功。你的状态机需要一个明确的终态:只有收到完成事件才写入“已完成”;否则记录为中断、失败或不完整,并向用户显示可理解的提示。
Chat Completions:读取 delta 与最终 usage
Chat Completions 同样设置 stream: true,但每个 chunk 主要从 choices[0].delta 读取内容。它不会使用 Responses 的 typed event 名称。
const stream = await client.chat.completions.create({
model: "YOUR_MODEL",
messages: [{ role: "user", content: "解释提示词缓存。" }],
stream: true,
stream_options: { include_usage: true }
});
for await (const chunk of stream) {
process.stdout.write(
chunk.choices[0]?.delta?.content ?? ""
);
if (chunk.usage) {
console.log("\n最终 usage:", chunk.usage);
}
}开启 stream_options.include_usage 后,官方参考说明会在 data: [DONE] 前发送一个额外 usage chunk,其中 choices 为空;其他 chunk 的 usage 通常为 null。如果流提前中断,最终 usage chunk 可能不会到达,因此不能把“没有 usage”误判成“没有消耗”。
如果要比较两套端点的 input、output、状态和迁移步骤,请看 Responses API 与 Chat Completions 区别;本页只解决流式消费者的正确实现。
流式 Function Calling:参数完成前不要执行
工具调用的 arguments 也是分片到达的 JSON 字符串。Responses 会发送 response.function_call_arguments.delta,并在参数完成时发送 response.function_call_arguments.done。要按 item_id 或 output_index 聚合不同调用,不能把多个工具的参数混在一起。
const argBuffers = new Map();
for await (const event of stream) {
if (event.type === "response.function_call_arguments.delta") {
const current = argBuffers.get(event.item_id) ?? "";
argBuffers.set(event.item_id, current + event.delta);
}
if (event.type === "response.function_call_arguments.done") {
const args = JSON.parse(event.arguments);
validateAndAuthorize(args);
// 只有 done 后才执行工具
}
}不要对半截 JSON 反复 JSON.parse,更不要根据尚未完成的参数提前执行支付、删除或发信。等 done 后再解析,随后执行 Schema、业务和权限校验;完整工具循环、call_id 与幂等控制见 OpenAI Function Calling 教程。
浏览器、服务端和 OpenAI 应如何连接?
- 浏览器:发送用户请求到你自己的后端,使用 Fetch stream、ReadableStream 或前端框架读取增量;不保存 OpenAI API Key。
- 应用服务器:完成登录、额度、内容策略和参数校验,持有服务端 Key,再调用 OpenAI。
- 流式转发:只向当前用户转发需要的事件或文本,设置正确的流式响应头,避免代理缓存整段响应。
- 客户端断开:停止继续渲染,并尽可能取消上游工作;服务端仍要记录本次请求是否已经产生用量或副作用。
- 最终落库:只有收到完成事件后才把回答标记为完整;中断内容要带状态,不与成功答案混淆。
前端渲染 Markdown 时也要防止 XSS,不要把 delta 直接写入 innerHTML。高频 delta 应合并到短时间缓冲后再刷新 UI,避免每个 Token 都触发一次昂贵重绘。
连接中断、超时和重试怎么处理?
- 区分两类失败:HTTP 建连前错误与流已经开始后的错误,用户界面提示和日志字段不同。
- 记录终态:保存是否见到 completed、最后 sequence/index、已显示字符数、response/request ID 和耗时。
- 不要假设可续传:普通 SSE 中断后不能靠最后一个文本位置保证无缝继续;需要根据业务重新请求、回到上一条完整消息,或让用户主动重试。
- 副作用不自动重试:工具、支付、写数据库必须先确认幂等状态,避免中断后重复执行。
- 有限退避:429、5xx 与连接错误按错误类别处理,避免 SDK 重试、代理重试和业务重试三层叠加。
详细的 500/503、超时、Request ID 和 SDK 重试边界见 OpenAI API 5xx 与超时排查。
流式内容审核为什么更难?
用户可能在完整答案出来前看到部分文本。OpenAI 官方指南明确提醒,部分 completion 更难评估;如果生成请求附带审核结果,分数是在完整输出后可用,不会随着每个 delta 同步返回。
- 低风险聊天可流式展示,但仍要处理 refusal、error 和最终状态。
- 中高风险场景可先缓冲一段、延迟展示,或使用非流式生成后整体审核。
- 工具参数和输出还需要单独的权限与策略检查;不要把文本审核等同于工具授权。
- 日志保留事件类型、状态和脱敏摘要,不记录 API Key 或敏感完整内容。
上线前检查清单
- API Key 只在服务端,浏览器网络请求中看不到。
- Responses 按 event.type 分支,不把 Chat Completions delta 代码直接复用。
- 只有 completed 才标记成功;failed、incomplete、error 和断线有明确 UI。
- 多 output Item 与多工具按 item_id/output_index 分开聚合。
- 工具参数等 done 后再解析、授权和执行。
- 最终 usage 缺失时不推断为零,能与服务端日志和 Usage Dashboard 对账。
- Markdown/HTML 安全、渲染节流、取消和代理缓存均经过真实网络测试。
- 429、5xx、客户端断线和重复提交都有评测用例。
SSE 用于同一次请求的增量展示;Webhook 用于异步事件通知。需要终态回调时,请按 OpenAI Webhooks 验签与去重指南实现,不能把 SSE 重连逻辑直接复制到 Webhook。
高风险场景应先确定缓冲或完整输出策略,并按 OpenAI Moderation API 指南读取 flagged、类别和分数。
常见问题
OpenAI API Streaming 会降低总生成时间吗?
Streaming 主要降低用户看到第一个 Token 的等待时间,让界面可以逐步显示内容;它不会自动缩短生成完整答案所需的总时间,也不会减少输出 Token。
Responses API 流式输出应该监听哪些事件?
文本场景至少处理 response.created、response.output_text.delta、response.completed 和 error,并为 response.failed、response.incomplete、refusal 与连接中断保留失败路径。只有完成事件出现后,才能把本次输出标记为完整。
Chat Completions 的 delta 能直接替换 Responses 事件吗?
不能。Chat Completions 从 choices 中读取 delta;Responses 使用带 type 的语义化事件。迁移时要改事件分发、完成判断、工具参数、错误与 usage 读取逻辑。
流式 Function Calling 的参数可以边到边执行吗?
通常不应。参数 delta 是尚未完成的 JSON 片段,应按 item_id 或 output_index 聚合,等 response.function_call_arguments.done 后再解析、校验权限和执行工具。
stream_options.include_usage 为什么有时拿不到总用量?
Chat Completions 开启 include_usage 后,会在 data: [DONE] 前发送一个额外 usage chunk;如果流在此前中断,官方说明这个最终用量块可能不会到达。因此应用还要保留服务端日志和 Usage Dashboard 对账。
浏览器可以直接连接 OpenAI Streaming API 吗?
生产站点不应把 API Key 放进浏览器。通常由自己的服务端持有 Key、调用 OpenAI,并把经过鉴权和过滤的流转发给前端,同时处理断线、限流和审计日志。
流式内容为什么更难做审核?
因为用户会在完整答案生成前看到部分文本。官方说明流式部分输出更难审核,而且生成请求附带的审核结果会在完整输出后到达,不会随每个 delta 一起返回。高风险场景应增加缓冲、策略检查或非流式审核。
SSE 适合逐步显示文字;需要 WebRTC、VAD、语音打断和双向音频时,请查看 OpenAI Realtime API 教程。
完整文件的 transcript delta/done 与麦克风 Realtime transcription 是两条不同路径;选型和完成判断见 OpenAI Audio API 教程。
Image API 与 Responses 可返回 partial image,但局部图有额外输出 Token,也不能当最终资产;事件与成本见 OpenAI Images API 教程。