测一家
首页/教程/SSE 流式调试:从"没有输出"到"块乱序"的排查路径
故障排查

SSE 流式调试:从"没有输出"到"块乱序"的排查路径

流式不通有七种原因,从代理缓冲到 nginx 配置、从 SDK 超时到中转站伪流式,按这个顺序查最快。

agentapiai.com 2026-11-13 约 8 分钟

流式输出出问题时,表现通常只有一句话:"没有输出"或者"卡住了"。但原因可能在不同层:客户端、代理、中转站、上游。下面按"从近到远"的顺序排了一条排查路径,每一步都能排除一层。

第一步:确认请求本身是流式的

curl -sS -N "https://relay.example.com/v1/chat/completions" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o-mini","stream":true,"messages":[{"role":"user","content":"数到 20"}]}'

-N 是关键,它关掉 curl 的输出缓冲。少了这个参数,你会以为没有流式,其实是 curl 攒着没打印。

如果这一步能看到多个 data: 行陆续出现,说明服务端是流式的,问题在客户端。如果一次性全部出现,可能是伪流式或代理缓冲。

第二步:看是不是代理缓冲

最常见的"本地好好的,线上没有流式"就是这一条。中间的反向代理(nginx、Cloudflare、公司网关)默认会缓冲响应,等整个响应结束才转发。

nginx 需要:

proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;

Cloudflare 对流式响应一般是透传的,但如果开了某些优化功能,也可能缓冲。测试方法:绕过代理直连源站,如果直连有流式、走代理没有,问题就在代理。

第三步:看客户端有没有读完

有些 HTTP 客户端默认会等响应体完整再返回。Python 的 requests 要设 stream=True 并逐行迭代;Node 的 fetch 要读 response.body 的 reader。

import requests, json

with requests.post(url, headers=h, json=payload, stream=True, timeout=60) as r:
    for line in r.iter_lines(decode_unicode=True):
        if not line or not line.startswith("data: "):
            continue
        data = line[6:]
        if data == "[DONE]":
            break
        chunk = json.loads(data)
        delta = chunk["choices"][0]["delta"].get("content")
        if delta:
            print(delta, end="", flush=True)

注意 flush=True。少了它,Python 会把输出攒在缓冲区,看起来像没流式。

第四步:看超时设置

流式请求的总时长可能很长(生成 2000 字要几十秒)。如果客户端设了 30 秒总超时,会在流到一半时断开,报连接错误。

正确做法是设读超时而不是总超时:只要还在收数据,就不算超时。requests 的 timeout=(connect, read) 里,read 是两次数据之间的最大间隔。

第五步:看是不是中转站伪流式

前面说过,伪流式的特征是所有块几乎同时到达。判断方法:

curl -sS -N ... | while IFS= read -r line; do
  printf '%s %s\n' "$(date +%s.%N)" "$line"
done | head -20

看每行前面的时间戳。真流式的间隔是几十到几百毫秒;伪流式会是同一时刻的一串。

第六步:看块的内容是否完整

有时候流是通的,但内容被截断。原因可能是:

  • max_tokens 到了上限(看 finish_reason 是不是 length)
  • 上游断开,站点没有补发结束事件
  • 代理的缓冲区大小限制,超长响应被切

检查最后一个块有没有 finish_reason。没有的话,这个流是不完整的,不能当成正常结束。

第七步:看错误发生在流的中间还是开头

  • 开头就错:鉴权、模型名、参数问题,返回的是普通 JSON 错误,不是流。
  • 中间断开:上游超时、站点重启、网络抖动。这类错误在 SSE 里通常表现为连接直接关闭,没有 data: [DONE]。

对中间断开的处理:客户端要能检测"没有收到结束标记",并决定是重试还是报错。流式请求重试有重复计费的风险,如果流已经输出了一部分内容,重试可能让用户看到重复内容。

一个排查顺序表

现象最可能的原因验证方法
完全没有 data: 行请求不是流式,或代理缓冲curl -N 直连
一次性出现全部内容代理缓冲或伪流式加时间戳看间隔
前几块正常,后面卡住读超时太短加长 read timeout
流中间断开无结束标记上游超时/重启看站点状态页
块内容重复客户端重试了查客户端重试逻辑
有流但解析报错事件格式不是 OpenAI 风格打印原始行

最后

本站检测台的第七项检查会统计 SSE 块数和首 token 时间。多块且首 token 可测记 PASS,只有单块记 WATCH(疑似伪流式),没有内容块记 FAIL。这项检查目前覆盖 OpenAI Chat 和 Anthropic 两种协议。