入门教程

AI API 流式输出中断怎么办?SSE 调试与重试完整指南(2026)

从 curl、Node.js 和 Python 三个角度排查 AI API 流式输出中断,区分首字节超时、代理缓冲、连接断开和重复重试,附可直接运行的测试代码。

发布:2026年8月28日
更新:2026/8/28

很多 AI API 使用 stream=true 后,会通过 SSE(Server-Sent Events)逐段返回内容。页面一直转圈、只收到半句话、或者 curl 能看到内容但程序收不到,通常不是模型本身故障,而是连接、代理或读取代码的问题。

一、先确认问题发生在哪一层

把链路拆成四层:客户端代码、反向代理、API 中转服务、上游模型。先用命令行绕过自己的业务代码测试:

curl -N --http1.1 https://你的服务商地址/v1/chat/completions \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"你的模型ID","messages":[{"role":"user","content":"请用三句话介绍 SSE"}],"stream":true}'

-N 禁止 curl 缓冲输出,--http1.1 可以排除部分 HTTP/2 代理兼容问题。正常结果应该是一串以 data: 开头的事件,最后通常是 data: [DONE]

二、首字节超时和中途断开不是一回事

首字节超时表示请求发出后迟迟没有任何数据,常见原因是模型排队、余额不足或服务商路由异常。可以重试,但要使用指数退避,例如 1 秒、2 秒、4 秒,最多 3 次。

中途断开表示已经收到部分内容后连接关闭。此时不要盲目把整条请求重放,否则用户可能收到重复答案,还会重复扣费。更稳妥的做法是保存已收到的文本,并在下一次请求中明确要求“从最后一句继续”,或者直接提示用户重新生成。

三、Node.js 正确读取 SSE

const response = await fetch(`${baseUrl}/v1/chat/completions`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ model, messages, stream: true }),
  signal: AbortSignal.timeout(60000),
})

if (!response.ok || !response.body) {
  throw new Error(`HTTP ${response.status}`)
}

const reader = response.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
  const { value, done } = await reader.read()
  if (done) break
  buffer += decoder.decode(value, { stream: true })
  const events = buffer.split('\n\n')
  buffer = events.pop() || ''
  for (const event of events) {
    const line = event.split('\n').find(item => item.startsWith('data:'))
    if (!line) continue
    const payload = line.slice(5).trim()
    if (payload === '[DONE]') continue
    const json = JSON.parse(payload)
    process.stdout.write(json.choices?.[0]?.delta?.content || '')
  }
}

关键点是保留未完成的 buffer。网络分片不一定刚好在换行处结束,直接对每次 read() 的结果执行 JSON.parse 会偶发报错。

四、Python 客户端的超时设置

import requests

with requests.post(
    f"{base_url}/v1/chat/completions",
    headers={"Authorization": f"Bearer {api_key}"},
    json={"model": model, "messages": messages, "stream": True},
    stream=True,
    timeout=(10, 120),  # 连接超时 10 秒,读取超时 120 秒
) as response:
    response.raise_for_status()
    for line in response.iter_lines(decode_unicode=True):
        if not line or not line.startswith("data:"):
            continue
        payload = line[5:].strip()
        if payload != "[DONE]":
            print(payload)

不要把连接超时和读取超时设置成同一个很小的数字。模型已经开始输出时,只要持续有数据,就不应该因为单个 token 间隔稍长而被客户端取消。

五、反向代理的三个常见坑

  1. Nginx 或 CDN 开启了响应缓冲,导致后端已经收到数据,浏览器却要等完整响应才显示。需要关闭该接口的 buffering。
  2. 代理的 idle timeout 小于模型生成时间。长回答没有新数据的一段时间后,代理会主动断开。
  3. gzip 或安全网关重写了 text/event-stream。SSE 接口应保持该 Content-Type,并尽量关闭压缩和缓存。

六、上线前测试清单

  • curl 能否持续看到多个 data: 事件;
  • 首个 token 是否在业务允许的时间内返回;
  • 断网后客户端是否能停止读取并释放连接;
  • 429、502、504 是否只在“尚未收到内容”时自动重试;
  • 日志是否记录 request id、状态码和耗时,但不记录 API Key 与完整提示词;
  • 同一请求重试后是否会重复扣费。

先用 curl 验证链路,再接入 Node.js/Python,最后才放到 CDN 或生产代理后面,定位问题会快很多。

标签:SSE流式输出API调试Node.jsPython