HaoAI

AI API 流式输出中断怎么办?

更新于 2026-10-26 · hao.christmas

简答: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。

可直接交给客服或平台支持的脱敏日志清单

支持人员最需要的是“在哪一层断了”,而不是一段未经整理的完整日志。可以同时提供一次 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、完成状态和重试次数,并为有副作用的操作设计幂等机制。

相关阅读

参考资料

  1. OpenAI Responses API 流式事件参考
  2. Streaming messages - Claude Platform Docs(platform.claude.com)
  3. Long decode - Gemini API
  4. Nginx ngx_http_proxy_module 官方文档(nginx.org)
  5. MDN Server-sent events 使用说明
  6. curl 官方教程(curl.se)
  7. curl 官方命令手册(curl.se)
  8. OpenAI Python 官方 SDK