跳到正文
docai.cc

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 数被截断。后者不是网络问题,重连也补不回来,要做的是识别截断并提示用户。

多数接口不支持从断点续传,连接断开后只能整段重发。重发意味着输入再计一次费,而且新生成的文字不会和旧输出逐字一致。所以最稳的做法是丢弃已显示的部分、整段替换;如果必须保留旧内容,界面上要明确标出「正在重新生成」。

重连要限次数并退避。无限重连只会把一次故障放大成持续的费用和负载。

第五步:加超时与降级

流式至少要设两个超时:首字节超时和总超时。只设总超时,用户会长时间盯着空白;只设首字节超时,已经开始的流会永远不结束。

再加一条降级路径:重试一次仍失败,就改用非流式请求,或者缩短输出长度、把完整结果一次返回。界面上要能显示「已停止生成」,而不是一直转圈。

完整的实现见流式输出与超时设置;错误码怎么读见读懂一次报错。

自测

  1. 问:curl 里数据一段段出现,应用里却一次性收到,问题在哪? 答:多半在中间层。反向代理、网关或 CDN 开了响应缓冲,把事件流攒到最后才转发。绕过代理直连源站验证,再改代理配置。

  2. 问:首字节慢和生成慢,处理方式有什么不同? 答:首字节慢通常在排队、限流或输入过长,要改输入、退避或换时段;生成慢通常是输出太长或模型速度,要缩短输出或换模型。

  3. 问:断流后为什么不能无限重连? 答:多数接口不支持断点续传,重发会重新计费并重新生成,内容还不保证一致。必须限制重试次数并退避,失败后降级。

自测

先自己回答,再展开对照;答不上来就回到对应小节。
  1. 01curl 里数据一段段出现,应用里却一次性收到,问题在哪?看答案

    多半在中间层。反向代理、网关或 CDN 开了响应缓冲,把事件流攒到最后才转发。绕过代理直连源站验证,再改代理配置。

  2. 02首字节慢和生成慢,处理方式有什么不同?看答案

    首字节慢通常在排队、限流或输入过长,要改输入、退避或换时段;生成慢通常是输出太长或模型速度,要缩短输出或换模型。

  3. 03断流后为什么不能无限重连?看答案

    多数接口不支持断点续传,重发会重新计费并重新生成,内容还不保证一致。必须限制重试次数并退避,失败后降级。

查看 Markdown

涉及的概念