为什么 Streaming 故障难排查
流式响应让 LLM 应用的用户体验提升明显——用户不必等待完整响应生成,可以逐字看到输出。但这种模式也引入了新的故障模式:连接可能在任意时刻中断,Token 可能静默丢失,错误信息可能藏在流的中间位置。
传统 HTTP 请求的故障排查相对简单:要么成功返回完整 JSON,要么返回明确的错误码。流式响应则不同——连接建立成功不代表数据会完整传输,200 OK 状态码后仍可能遇到各种问题。
分层诊断路径
网络层:连接为何中断
假设一个场景:用户反馈"生成到一半就停了"。首先排查网络层:
超时配置不匹配是最常见原因。客户端、反向代理(如 Nginx)、云服务商的负载均衡器都有各自的超时设置。旗舰档模型生成长文本可能需要 60 秒以上,但 Nginx 默认 proxy_read_timeout 往往只有 60 秒。诊断方法:
- 检查客户端的
timeout/read_timeout配置 - 查看反向代理日志,搜索
upstream timed out关键词 - 确认云服务商控制台的超时设置(ALB/CLB 等)
修复建议:将整条链路的超时时间统一设置为 120 秒或更长,并在客户端实现重试逻辑。
连接池耗尽也会导致中断。如果应用使用连接池管理 HTTP 客户端,池子满了新请求会被拒绝或排队超时。检查连接池的 max_connections 和 keepalive_timeout 配置,确保与并发需求匹配。
协议层:SSE 解析错误
Server-Sent Events (SSE) 是流式响应的常用协议。它要求每个数据块以 data: 开头,以双换行符 \n\n 结尾。解析器对格式非常敏感:
- 如果上游 API 返回的 JSON 中包含未转义的换行符,解析器可能误判为事件边界
- 某些代理会修改
Content-Type,导致客户端用错误的解析器处理响应
诊断方法:抓包查看原始响应体,确认格式符合 SSE 规范。工具推荐 curl -N 或 Wireshark。
应用层:Token 为何丢失
缓冲区未及时刷新会导致 Token 积压。假设你用 Python 的 requests 库处理流式响应,如果没有正确迭代 iter_content() 或 iter_lines(),数据可能停留在内部缓冲区。Node.js 环境下也类似,需要监听 data 事件并主动消费。
错误处理逻辑吞掉了异常。某些 SDK 在遇到格式错误的数据块时会静默跳过,而不是抛出异常。建议在开发阶段启用详细日志,记录每个接收到的数据块。
编码问题导致多字节字符被截断。UTF-8 中一个汉字占 3 字节,如果按固定字节数切分流,可能把字符切成乱码。解决方法:使用支持流式解码的库(如 Python 的 codecs.getincrementaldecoder)。
构建可观测性
故障排查的前提是有足够的可见性。建议在以下位置埋点:
- 客户端:记录请求开始时间、首字节到达时间 (TTFB)、总接收 Token 数
- 网关层:记录上游 API 的响应头、流持续时长、是否正常关闭
- 应用层:记录解析后的事件类型、错误事件内容
当故障发生时,对比这三层的日志能快速定位问题边界——是网络未送达,还是应用未正确处理。
预防性措施
与其等故障发生再排查,不如提前加固:
- 实现心跳机制:每隔 15 秒发送一个注释行(SSE 中以
:开头),保持连接活跃 - 添加完整性校验:在流的最后一个事件中返回总 Token 数,客户端对比实际接收数量
- 设计降级策略:检测到流式失败时自动回退到非流式模式
如果你的团队在构建复杂的 LLM 应用,需要在多个上游 API 之间做统一的可观测性与容错处理,Taylent Labs 的 AI 应用开发咨询服务可以帮助设计更健壮的架构。