DOCAI · 课程 · L3 · 25 分钟
排查流式输出:从首字慢到断流
流式问题分两类:字来得慢,或者流到一半断。先用 curl 看原始帧,再分清代理缓冲、首字耗时与生成耗时,最后补上重连去重和超时降级。
你将学会
- 能用 curl 直接观察流式原始帧
- 能判断慢在首字、生成还是代理缓冲
- 能为断流加上限次重连与降级
第一步:用 curl -N 看原始帧
关掉 curl 的缓冲,直接观察服务器发来的事件流:
curl -N -sS -X POST "$BASE_URL/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"'"$MODEL"'","messages":[{"role":"user","content":"讲一段长一点的内容"}],"stream":true}'
重点看三件事:响应头是不是 text/event-stream;data: 行是不是一段段出现,而不是最后一次性涌出;结束处有没有结束标记。原始帧长什么样,与 流式输出的机制一致。
如果 curl 里就在最后一次性出现,问题在服务端或网络路径,不用怀疑你的客户端代码。
第二步:判断代理缓冲
curl 里逐段出现、应用里一次性出现,问题几乎肯定在中间层。依次检查反向代理、网关和 CDN 是否开了响应缓冲或压缩。
验证方法很简单:绕过代理直连源站再试一次。直连正常、过代理不正常,就改代理配置,不要在客户端代码里继续找。
有的服务会主动声明不要缓冲,但上层仍可能忽略这个声明。改完配置要用同一条 curl 复测,确认数据真的按段到达,而不是只看配置文件写了什么。
第三步:把首字与生成耗时分开
把一次请求拆成两段计时:
- 首字节耗时:从发出请求到收到第一个正文片段。
- 生成耗时:总耗时减去首字节耗时。
两段慢的原因不同。首字节慢,多半在排队、限流或输入过长,处理办法是缩短输入、退避后重试或换时段;生成慢,多半是输出太长或模型本身速度,处理办法是限制输出长度或换型号。
指标定义见首字延迟。不要用一次请求下结论,多测几次看分布,再决定优化哪一段。
第四步:断流后的重连与去重
先分清两种断流:连接被断开,和输出撞上最大 token 数被截断。后者不是网络问题,重连也补不回来,要做的是识别截断并提示用户。
多数接口不支持从断点续传,连接断开后只能整段重发。重发意味着输入再计一次费,而且新生成的文字不会和旧输出逐字一致。所以最稳的做法是丢弃已显示的部分、整段替换;如果必须保留旧内容,界面上要明确标出「正在重新生成」。
重连要限次数并退避。无限重连只会把一次故障放大成持续的费用和负载。
第五步:加超时与降级
流式至少要设两个超时:首字节超时和总超时。只设总超时,用户会长时间盯着空白;只设首字节超时,已经开始的流会永远不结束。
再加一条降级路径:重试一次仍失败,就改用非流式请求,或者缩短输出长度、把完整结果一次返回。界面上要能显示「已停止生成」,而不是一直转圈。
完整的实现见流式输出与超时设置;错误码怎么读见读懂一次报错。
自测
-
问:curl 里数据一段段出现,应用里却一次性收到,问题在哪? 答:多半在中间层。反向代理、网关或 CDN 开了响应缓冲,把事件流攒到最后才转发。绕过代理直连源站验证,再改代理配置。
-
问:首字节慢和生成慢,处理方式有什么不同? 答:首字节慢通常在排队、限流或输入过长,要改输入、退避或换时段;生成慢通常是输出太长或模型速度,要缩短输出或换模型。
-
问:断流后为什么不能无限重连? 答:多数接口不支持断点续传,重发会重新计费并重新生成,内容还不保证一致。必须限制重试次数并退避,失败后降级。
自测
先自己回答,再展开对照;答不上来就回到对应小节。01curl 里数据一段段出现,应用里却一次性收到,问题在哪?看答案
多半在中间层。反向代理、网关或 CDN 开了响应缓冲,把事件流攒到最后才转发。绕过代理直连源站验证,再改代理配置。
02首字节慢和生成慢,处理方式有什么不同?看答案
首字节慢通常在排队、限流或输入过长,要改输入、退避或换时段;生成慢通常是输出太长或模型速度,要缩短输出或换模型。
03断流后为什么不能无限重连?看答案
多数接口不支持断点续传,重发会重新计费并重新生成,内容还不保证一致。必须限制重试次数并退避,失败后降级。