入门教程
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 间隔稍长而被客户端取消。
五、反向代理的三个常见坑
- Nginx 或 CDN 开启了响应缓冲,导致后端已经收到数据,浏览器却要等完整响应才显示。需要关闭该接口的 buffering。
- 代理的 idle timeout 小于模型生成时间。长回答没有新数据的一段时间后,代理会主动断开。
- 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