AI API 流式输出中断怎么办?
简答:AI API 流式输出中断,先用同一请求分别测试 stream=false 和 stream=true:非流式成功而流式失败,通常应优先检查 SSE 解析、代理缓冲、读取超时和结束事件。记录状态码、request_id、最后一条事件与 stop_reason,再决定调整超时、修正前端状态机,或保存已生成内容后续写,避免盲目重复提交。
定义:AI API 流式输出中断,是指请求已经返回部分内容,但连接、事件流或客户端处理在正常结束信号前停止。
先做对照:stream=false 成功,stream=true 才失败怎么办
本站是 AI API 接入指南站,不是任何模型平台的官方站点。遇到流式输出中断,不要先改模型或重复请求,先用同一套 API Key、Base URL、模型和输入内容做一次非流式对照。若 stream=false 也失败,问题更可能在鉴权、模型、请求参数或上游服务;若非流式成功而流式失败,再集中检查 SSE 链路和客户端事件处理。
| 现象 | 优先判断 | 下一步 |
|---|---|---|
| 首 token 前直接返回 HTTP 错误 | 请求、Key、模型或上游服务 | 记录状态码和错误 JSON,先完成非流式测试。 |
| 已经输出一部分后连接断开 | 读取超时、网络、代理或网关 | 用 curl -N 复现,保存最后一条事件和时间点。 |
| 收到内容但页面不更新 | 前端 SSE 解析、缓冲或状态机 | 检查 data: 分行、空行分隔和 flush。 |
| 有结束事件但内容像半截 | 达到输出上限或业务层截断 | 读取 finish_reason、stop_reason 或响应状态。 |
| 请求重复产生两份结果 | 断线后无条件重试 | 保存已接收内容,区分可安全续写和带副作用的请求。 |
OpenAI 的 Responses 流式事件包括 response.completed,并可能通过响应对象表达 completed、incomplete 或错误状态;Anthropic Messages 流通常以 message_stop 结束,并在 message_delta 中提供停止原因。不要把“收到最后一个文本块”当成请求一定完成,应该等待对应的结束事件。可分别参考 OpenAI Responses 流式事件文档 和 Claude 流式消息文档。
用 curl 做最小复现:先确认服务端到底发了什么
把前端、业务逻辑、数据库和代理暂时排除,只保留一个 HTTP 请求。示例中的地址、Key 和模型均使用环境变量,避免把凭据放进 URL 查询参数;模型名也不要直接照抄示例,改成你已在目标接口中确认可用的模型。
Bash 示例:
set -eu
command -v curl >/dev/null || { echo "curl not found"; exit 1; }
command -v jq >/dev/null || { echo "jq not found"; exit 1; }
curl --version | awk 'NR==1 {print $2}'
read -r -s -p "API key: " API_KEY; echo
BASE_URL="${BASE_URL:?set BASE_URL, for example https://api.example.com/v1}"
MODEL="${MODEL:?set MODEL}"
BODY=$(jq -n --arg model "$MODEL" '{model:$model,messages:[{role:"user",content:"Reply with one short sentence."}],stream:true}')
curl --no-buffer --fail-with-body --max-time 180 "$BASE_URL/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "$BODY"
--no-buffer 用于减少 curl 对输出的缓冲;curl 官方文档说明它适合观察慢速连接的即时输出。--fail-with-body 的可用版本需要在你的环境中确认,若版本过旧,可先移除该参数并保留 -i 或 -v 查看响应。--max-time 是整个 curl 命令的最大时间,排查时应显式设置,否则默认行为可能掩盖“客户端主动结束”和“服务端断开”的区别。参考 curl 官方教程 和 curl 官方命令手册。
观察四件事:第一,HTTP 状态码是否为 2xx;第二,响应头是否包含适合事件流的 Content-Type;第三,是否持续收到以 data: 开头的事件;第四,最后是否出现接口规定的结束信号。OpenAI、Anthropic 以及兼容接口的事件名称并不完全相同,因此不能只写死一个 [DONE] 判断所有平台。
按断点定位:网络、代理、客户端还是模型输出上限
| 断点位置 | 可观察信号 | 处理方法 |
|---|---|---|
| 首 token 前 | 没有正文,直接出现 401、403、404、429、5xx 或超时 | 先按 HTTP 错误处理,保存错误原文,不进入续写逻辑。 |
| 输出中途 | 已收到若干 delta,随后连接异常、读取超时或 EOF | 保留已收到文本,检查客户端 read timeout、网关 idle timeout、网络日志和最后事件。 |
| 服务端已结束 | 存在正常结束事件,但页面没有完整展示 | 检查前端是否只处理文本 delta,是否漏掉最后一个 chunk 或异步状态更新。 |
| 达到上限 | OpenAI Responses 可能出现 incomplete;Claude 通过 message_delta.stop_reason 表示停止原因 | 区分长度限制与网络断开;必要时合理提高输出上限或改用分段任务。 |
对 SSE 来说,事件由多行字段组成,事件之间用空行分隔,响应内容应使用 UTF-8;服务端还可以发送注释行维持连接。前端解析时不要假设一次网络读取就是一个完整事件,也不要把一个事件拆开的两次读取误判为异常。MDN 的 SSE 文档说明了 event、data、空行分隔以及 text/event-stream 响应格式,可作为通用解析依据:MDN Server-sent events 使用说明。
如果请求经过 Nginx,重点检查 proxy_buffering 和 proxy_read_timeout。Nginx 官方文档显示,proxy_buffering 默认开启,关闭后响应会在收到时同步传给客户端;proxy_read_timeout 默认值为 60 秒,而且它限制的是两次读取之间的间隔,不是整个响应总时长。实际值应结合你的服务端生成速度设置,不能只把总超时无限调大。参考 Nginx proxy 模块官方文档。
如果浏览器页面卡住但 curl 能持续看到事件,优先检查后端是否 flush、是否压缩或缓存响应,以及前端是否正确处理自定义事件。若 curl 也在固定时间间隔断开,优先查看网关、负载均衡、Serverless 平台和上游 API 的读取超时。假如中间很久没有新 token,保持连接的心跳或注释行可能有帮助,但必须确认目标接口是否会发送这类事件。
三种恢复策略:续写、重试,还是直接提示用户
策略一:保存残片并续写。适合纯文本、没有工具副作用的请求。续写请求应明确告诉模型已有内容在哪里结束,并要求从断点继续,而不是再次完整生成。Claude 官方文档针对不同模型版本给出了不同的恢复写法:较早模型可以把部分 assistant 内容作为延续上下文;较新的模型建议把部分内容放入用户消息并要求继续。具体格式应以对应版本文档为准,不要把一种消息结构套到所有模型上。参考 Claude 官方流式错误恢复说明。
策略二:有限重试。只对连接错误、临时 5xx 或明确可重试的 429 使用指数退避。建议把“最大尝试次数”和“最大总等待时间”同时设上,例如最多 2 至 3 次、总等待不超过业务可接受范围;具体数值应按接口限速和任务成本调整。每次重试都记录新的 request_id,并将原请求标识、重试次数和幂等键关联起来。若请求会触发工具调用、写入数据库、发送消息或产生其他副作用,不能仅凭网络断开就自动重发。
策略三:停止并提示用户。如果已经输出了足够内容但无法可靠判断后续是否执行,展示已保存部分并标记“连接中断”,让用户选择继续。对结构化 JSON、工具调用和思考内容,不能简单把半截字符串拼进下一次请求;Anthropic 文档明确提醒,工具调用和思考块不能按普通文本方式部分恢复,应从最近可恢复的文本块处理。
Gemini 的长时间解码文档提供了另一种官方恢复思路:保存 interaction ID 和每个 event_id,重连时传入 last_event_id,并在重连后读取到流结束再检查最终状态;长流还应设置较长的 HTTP 读取超时。这个机制只适用于该接口支持的事件流,不应推断为所有 OpenAI 兼容 API 都支持。参考 Gemini API 长时间解码文档。
最小伪代码:
partial = ""
attempt = 0
while attempt < 3:
try:
for event in stream_request():
save_event(event)
partial += extract_text_delta(event)
if is_normal_end(event):
return complete(partial)
except retryable_connection_error:
attempt += 1
sleep(min(2 ** attempt, 30))
return ask_user_to_continue(partial)
这里的关键不是复制代码,而是让 partial、最后事件、结束状态和重试计数都真正参与决策。SDK 可能自带自动重试,排查阶段应显式查看并记录其配置,否则一次页面请求可能对应多次上游请求。OpenAI Python SDK 的官方示例展示了通过 stream=True 迭代 SSE 事件;使用 SDK 时还应确认客户端超时、自动重试和异常字段的实际版本结构,参考 OpenAI Python 官方 SDK。
可直接交给客服或平台支持的脱敏日志清单
- 请求定位:时间、时区、内部 request_id、上游返回的 request_id;不要记录完整 API Key。
- 请求信息:接口路径、HTTP 方法、模型名、
stream值、输入与输出 token 上限;长文本和敏感提示词应脱敏或摘要化。 - 连接信息:客户端语言和 SDK 版本、curl 或浏览器复现结果、HTTP/1.1 或 HTTP/2、是否经过 Nginx、CDN、负载均衡或 Serverless。
- 流状态:首 token 时间、最后一个 delta 时间、chunk 间隔、收到的最后事件类型、是否有正常结束事件。
- 错误信息:HTTP 状态码、错误类型、错误消息、客户端异常类型和堆栈摘要;不要上传 Authorization 头。
- 重试记录:第几次重试、等待时间、是否使用幂等键、是否已经执行工具或业务副作用。
支持人员最需要的是“在哪一层断了”,而不是一段未经整理的完整日志。可以同时提供一次 stream=false 和一次 stream=true 的脱敏结果:前者成功、后者失败时,问题范围通常已经缩小到流式传输、代理或客户端处理;两者都失败时,则应先回到 HTTP 状态码和请求参数。
FAQ:AI API 流式输出中断常见问题
流式输出一定要看到 [DONE] 才算成功吗?
不一定。[DONE] 常见于部分兼容接口,但 OpenAI Responses、Anthropic Messages 和其他事件流接口可能使用不同的完成事件。应按目标接口文档判断,例如 OpenAI 关注 response.completed 等事件,Anthropic 关注 message_stop 及 message_delta 中的停止原因。不要把某一家接口的结束标记硬编码到所有渠道。
已经收到半截内容,最安全的处理方式是什么?
先保存已收到的文本、最后事件、请求 ID 和停止原因,再判断请求是否包含工具调用或其他副作用。纯文本通常可以采用带断点说明的续写;结构化输出、工具调用或写操作则应先确认是否已经执行,避免直接重发导致重复结果。
为什么 curl 能看到流,网页却一直不更新?
常见排查方向是前端没有按空行拆分 SSE 事件、后端或 Nginx 开启了响应缓冲、压缩层没有及时 flush,或者前端只监听默认 message 而没有处理命名事件。先在浏览器开发者工具中查看原始响应,再对比 curl 的事件顺序;SSE 格式和事件字段可参考 MDN 文档。
把 read timeout 调得很大就能解决吗?
不能直接下结论。读取超时只解决“两个连续数据之间等待过久”的一种情况;如果是代理缓冲、客户端解析、上游错误或达到输出上限,单独增大超时不会修复根因。Nginx 官方文档对 proxy_read_timeout 的定义是连续两次读取之间的超时,应同时检查缓冲配置和实际事件间隔。
流式中断会不会重复扣费或重复执行?
仅凭客户端断开无法判断上游是否已经完成部分工作,因此不能承诺不会产生重复成本或副作用。记录上游请求 ID、usage、完成状态和重试次数;涉及写入、扣款、发送通知或工具调用时,使用业务侧幂等设计,并在重试前查询执行状态。
常见问题
流式输出一定要看到 [DONE] 才算成功吗?
不一定。部分兼容接口会使用 [DONE],但不同 API 可能使用 response.completed、message_stop 或其他结束事件,应按目标接口文档判断。
已经收到半截内容,最安全的处理方式是什么?
先保存已收到的文本、最后事件、请求 ID 和停止原因,再判断是否存在工具调用或业务副作用;纯文本可续写,写操作不要直接重发。
为什么 curl 能看到流,网页却一直不更新?
优先检查前端是否正确拆分 SSE 空行、是否监听命名事件,以及后端、Nginx 或 CDN 是否启用了响应缓冲。
把 read timeout 调得很大就能解决吗?
不能。读取超时只是可能原因之一,还要检查代理缓冲、客户端解析、上游错误和输出上限。
流式中断会不会重复扣费或重复执行?
无法仅凭客户端断开判断上游状态。应记录请求 ID、usage、完成状态和重试次数,并为有副作用的操作设计幂等机制。
相关阅读
参考资料
- OpenAI Responses API 流式事件参考
- Streaming messages - Claude Platform Docs(platform.claude.com)
- Long decode - Gemini API
- Nginx ngx_http_proxy_module 官方文档(nginx.org)
- MDN Server-sent events 使用说明
- curl 官方教程(curl.se)
- curl 官方命令手册(curl.se)
- OpenAI Python 官方 SDK