OpenAI API 流式输出中断怎么办?SSE 连接与重试排查
简答:OpenAI API 流式输出中断,先不要直接重试:确认请求是否收到正常结束信号,再用同一 Base URL、API Key 和模型分别测试非流式请求与 curl SSE 请求。Chat Completions 重点检查 finish_reason 和 data: [DONE];Responses API 重点检查 response.completed、response.failed 或 response.incomplete。若没有结束事件就断开,再按客户端解析、代理超时、网关返回和上游状态逐层定位。
定义:SSE 流式输出是服务器通过一个持续的 HTTP 连接,以事件或数据块逐步发送模型结果,而不是等待完整结果后一次性返回。
先试这一步:用最小请求判断断在哪一层
本文面向已经在使用 stream=true 的开发者。本站是第三方 AI API 接入指南站,不代表任何 API 平台官方。核验时间为 2026-10-31。
先固定四个变量:实际请求地址、API Key、模型 ID、请求体。不要一边排查一边更换模型、SDK、网关和提示词,否则无法判断是哪一层发生变化。
- 先把流式参数关闭,发送一次非流式请求。如果非流式也失败,优先检查 Key、Base URL、模型、权限、额度和网关响应。
- 非流式成功后,用
curl直接测试同一个端点,绕过业务代码和前端。 - 记录 HTTP 状态、
Content-Type、首字节时间、最后一个完整事件、断开时间、请求 ID 或响应 ID。 - 只有在确认请求尚未完成、且操作没有外部副作用时,才执行有限次数重试。
Chat Completions 的流式响应是数据型 SSE,客户端应按事件边界读取,而不是把每个 TCP 或网络 chunk 直接当成一个完整 JSON。官方示例显示,正常结束通常包含最后一个带 finish_reason 的数据块以及 data: [DONE]。可参考 OpenAI 流式响应指南 和 Chat Completions API 参考。
curl 基线示例:
curl --no-buffer -i https://api.openai.com/v1/chat/completions -H "Authorization: Bearer $OPENAI_API_KEY" -H "Content-Type: application/json" -d '{"model":"YOUR_MODEL","messages":[{"role":"user","content":"只回复:测试成功"}],"stream":true}'
如果 curl 能持续收到事件而应用代码中断,问题更可能在 SSE 解析、读取超时、线程取消或前端转发;如果 curl 也中断,再检查网关、代理链路和上游响应。
三种结束状态必须分开判断
| 观察到的现象 | 应如何判断 | 下一步 |
|---|---|---|
| 收到正常结束事件 | Chat Completions 出现 finish_reason,并收到 data: [DONE];Responses API 收到 response.completed | 不是断流。检查前端是否误把完成事件当成错误,或是否因内容拼接逻辑造成显示不完整。 |
| 收到错误事件 | Responses API 可能出现 response.failed 或 response.incomplete,其中包含状态或错误信息 | 先保存响应 ID、错误码和已完成内容,再按错误类型决定是否修复参数、等待后重试或查询最终状态。 |
| 没有结束事件,连接直接关闭 | 已收到部分文本,但没有结束标志,可能是客户端、代理、网关或上游连接被关闭 | 保留已显示文本并标记“未完成”,检查超时、缓冲、连接重置和 SSE 解析;不要把半截结果当成完整回答。 |
Responses API 的事件参考明确列出了 response.completed、response.failed 和 response.incomplete。其中 response.completed 表示响应完成,response.failed 表示响应失败。相关字段应以 Responses 流式事件参考 为准。
如果是 Chat Completions,还要区分 finish_reason:stop 表示自然停止,length 表示达到输出长度限制,tool_calls 表示模型产生工具调用。它们都不是同一种“网络断流”,不能只看界面上是否停止输出。
按故障层级排查:客户端、代理还是上游
1. 客户端 SSE 解析错误
SSE 事件可能跨越多个底层网络读取块,一个 JSON 也可能被拆成多段。解析器应先按 SSE 事件分隔规则组装完整事件,再解析其中的 JSON。不要对每次 read() 返回的数据直接调用 JSON 解析。
排查时打印脱敏后的事件类型、事件长度、最后一条完整事件和解析异常位置。API Key、完整 Authorization 头、用户输入和工具参数中的隐私数据不要写入日志。
2. 首字节超时与流中空闲超时
首字节超时表示请求发出后迟迟没有收到第一段响应;流中空闲超时表示连接已经建立,但连续一段时间没有新事件。两者应分开配置和记录。长上下文、复杂工具调用或网关排队可能影响首字节时间,但不能仅凭一次超时断言是上游故障。
3. 反向代理缓冲
如果服务端已经生成内容,但浏览器长时间收不到,检查反向代理是否对响应进行了缓冲、压缩或聚合。对 SSE 应确认代理按流式方式转发,并检查读取超时、连接超时和上游响应超时。具体配置名称取决于代理产品,不能把某一个代理的配置直接套用到所有网关。
4. 502、连接重置与网关错误
如果 HTTP 响应尚未建立就收到 502,优先检查网关到上游的连接、DNS、TLS 和上游超时;如果 HTTP 200 已经开始输出后连接被关闭,则要结合最后事件、代理日志和 TCP 错误判断。状态码本身不能证明一定是模型服务问题。
5. 429 与重试放大
429 可能对应请求或 Token 速率限制,也可能是额度、组织使用上限或项目支出限制。官方建议先读取错误详情和 Retry-After;临时限流时使用带随机抖动的指数退避,并限制重试次数和总时长。额度或支出限制不能靠重复发送恢复,应先处理对应限制。详见 OpenAI API 429 排查说明。
Python 与 Node.js 的最小流式处理示例
Python:保留已输出内容,区分完成与异常
from openai import OpenAI
client = OpenAI()
text = []
completed = False
try:
stream = client.chat.completions.create(
model="YOUR_MODEL",
messages=[{"role": "user", "content": "只回复:测试成功"}],
stream=True,
)
for chunk in stream:
choice = chunk.choices[0]
piece = choice.delta.content or ""
if piece:
text.append(piece)
print(piece, end="", flush=True)
if choice.finish_reason is not None:
completed = True
print("\n完成状态:", completed)
except Exception as exc:
print("\n流式请求异常,已输出字符数:", len("".join(text)))
raise
这个示例把“已经展示的文本”和“是否收到结束原因”分开保存。出现异常时,调用方应把结果标记为未完成,而不是自动拼接一句看似完整的结尾。
Node.js:记录最后事件与读取异常
import OpenAI from "openai";
const client = new OpenAI();
let output = "";
let finished = false;
try {
const stream = await client.chat.completions.create({
model: "YOUR_MODEL",
messages: [{ role: "user", content: "只回复:测试成功" }],
stream: true,
});
for await (const chunk of stream) {
const choice = chunk.choices?.[0];
const piece = choice?.delta?.content || "";
output += piece;
process.stdout.write(piece);
if (choice?.finish_reason) finished = true;
}
console.log("\n完成状态:", finished);
} catch (error) {
console.error("流式连接中断,已输出长度:", output.length);
throw error;
}
如果你使用的是 Responses API,应根据 SDK 返回的事件类型处理完成和失败事件,不要把 Chat Completions 的字段名直接套用到 Responses API。官方流式指南同时给出了两种 API 的流式调用方式。
安全重试:先确认状态,再决定是否重放
流式连接断开不等于服务端一定没有完成请求。官方错误恢复说明要求在断开后先检查已保存状态、已完成操作或会话最终状态,再决定重连或重试;对于可能调用外部工具、写入数据库、发送消息或产生其他副作用的请求,不能无条件从头重放。参考 OpenAI 错误与恢复指南。
- 先查结果:保存请求 ID、响应 ID、会话 ID、模型、最后事件和错误内容,优先查询可查询的最终状态。
- 区分可重试与不可重试:临时超时、过载、部分 429 或暂时性服务器错误,可以进入有限重试;认证、权限、模型不存在、参数无效和额度错误,应先修复原因。
- 遵守 Retry-After:有有效值时至少等待该时长;没有时使用指数退避加随机抖动。
- 设置边界:同时限制最大尝试次数、总重试时间和并发数量,避免多个组件各自重试造成请求风暴。
- 改善用户体验:保留已收到的文本,明确显示“输出未完成,请重试”,不要把中断结果伪装成正常答案。
一个通用的退避思路是:第 1 次失败等待较短时间,后续逐步增加等待,并叠加小幅随机值;实际延迟、次数和总截止时间应根据业务可接受时延、接口限制和是否存在副作用来设定,不能照搬固定数字。
一张自查清单:把日志补齐再判断
- 请求方法、实际 URL 和最终路径是否正确,是否重复拼接了
/v1或版本路径。 - 使用的模型、API Key 对应的项目和权限是否与测试环境一致。
- 非流式请求是否成功,curl 是否能收到连续 SSE 事件。
- 响应的 HTTP 状态、
Content-Type、响应 ID、请求 ID 是否已记录。 - 是否收到 Chat Completions 的
finish_reason与data: [DONE]。 - 是否收到 Responses API 的
response.completed、response.failed或response.incomplete。 - 首字节时间、相邻事件间隔、最后完整事件、客户端断开时间是否可对照。
- 反向代理、CDN、负载均衡器是否启用响应缓冲、压缩或较短的读取超时。
- 是否存在 SDK 内置重试与业务层重试叠加,导致同一请求被重复发送。
- 日志是否脱敏,是否避免记录完整 Key、Authorization、用户隐私和工具参数。
完成这份清单后,通常可以把问题收敛到三类:业务代码没有正确解析 SSE、链路中间层提前关闭连接,或上游确实返回了错误。若仍无法定位,应提交脱敏后的时间线、请求 ID、响应 ID、HTTP 状态和最后事件,而不是只提供“页面卡住了”。
如果你正在为多个 AI API 配置统一的 Base URL、模型和故障切换,可以在完成上述最小测试后,再考虑接入自己的路由层或兼容接口;所有端点、模型和认证方式仍应以对应服务的当前文档为准。
常见问题
OpenAI API 流式输出只返回半句话,是模型生成不完整吗?
不一定。先确认是否收到正常结束事件。如果没有 finish_reason、data: [DONE] 或 Responses API 的 response.completed,就只能判定为未确认完成;可能是客户端解析、代理超时、网关断开或上游错误。应保留已输出文本并继续查日志。
为什么非流式请求成功,stream=true 却超时?
流式请求会保持连接并持续传输事件,因此会额外受到 SSE 解析、代理缓冲、连接空闲超时和客户端读取逻辑影响。先用 curl 绕过业务代码测试,再检查中间层是否按流式方式转发,并分别记录首字节时间和流中空闲时间。
流式连接断开后能不能直接重新发送同一个请求?
不能默认直接重发。断开不代表服务端没有完成请求;如果请求触发工具调用、写入或其他副作用,重放可能造成重复操作。先查询可用的响应或会话状态,再决定是否只继续未完成部分,且设置最大尝试次数和总截止时间。
Chat Completions 和 Responses API 的流式结束标志一样吗?
不一样。Chat Completions 通常通过数据块中的 finish_reason 和 data: [DONE] 判断结束;Responses API 使用事件类型,例如 response.completed、response.failed 或 response.incomplete。应根据实际接口和官方参考处理,不能混用字段。
相关阅读
- 先做 OpenAI 兼容接口的连通、模型与流式基线测试
- 如果最小请求返回 401,按 Key、权限和 IP 白名单排查
- 如果返回 404,检查模型名和接口路径是否匹配
- 需要故障切换时,了解多供应商 API 的配置与重试边界