流式输出中断了怎么办?断点、重发与幂等的三种处理
流式输出中断的尴尬在于:你已经为生成的那半截付了钱,但拿到的是残缺内容。整体重发意味着两边都付费,直接展示残缺内容意味着体验受损。这中间有几种折中方案,选哪种取决于你的场景能不能容忍”接不上”。
这篇讲三种处理策略。超时相关的通用处理见 API 超时与中断。
中断是怎么发生的
网络层断开:移动网络切换、代理超时、负载均衡器的空闲连接回收。长回答尤其容易撞上中间设备的超时设置——因为流式连接会保持很久。
服务端主动结束:触发限流、内部错误、达到 max_tokens 上限(这个严格说不算中断,但表现类似:内容戛然而止)。
客户端主动取消:用户关掉页面、切走、点了停止。这种情况反而最好处理,因为是预期内的。
区分它们的关键是看有没有收到结束事件。正常结束会有明确的完成信号和 finish_reason;异常中断则是连接直接断掉,什么都没收到。所以务必判断 finish_reason,别只看有没有内容。
策略一:保留残缺内容 + 提示用户
最简单的做法:把已经收到的部分保留下来,在界面上明确提示「生成中断」,给一个「继续生成」按钮。
适用:内容型场景(写作助手、长文生成),用户能看懂残缺内容,也愿意点一下继续。
优点:不浪费已付费的内容,实现简单。
注意:一定要在 UI 上区分「完整」和「中断」,别让残缺内容看起来像是模型的完整回答。这类静默残缺最容易招投诉。
策略二:把已生成内容作为上下文续写
用户点「继续」时,把原始问题加上已生成的部分一起发过去,让模型接着写。
做法:在新请求里把已有内容作为助手消息放进历史,并在指令里明确「从中断处继续,不要重复已有内容」。
优点:用户拿到的是完整内容,已生成的部分没有白付。
代价:续写请求的输入包含了已生成内容,这部分要按输入价再付一次。不过输入价通常远低于输出价,所以总账仍比整体重发划算。
风险:接缝处可能重复或跳跃。缓解办法是在续写提示里给出最后一小段原文作为锚点,并在拼接时做重叠去重。
策略三:整体重发(幂等重试)
丢弃已生成内容,用同样的参数重新请求一次。
适用:短输出场景(几百 token 以内),重来的成本很低;或者对内容连贯性要求极高、无法接受接缝的场景。
代价:两次输出都要付费。输出越长越亏,所以长内容不建议用这个策略。
必须配套幂等设计:如果这次调用有副作用(写数据库、发消息、扣费),重发前要确保不会重复执行。常见做法是给每次业务操作一个幂等键,落库前先查。
三种策略怎么选
按输出长度和场景性质分:
- 短输出 + 无副作用 → 策略三,最简单
- 长输出 + 内容型 → 策略二,最省钱
- 长输出 + 用户能接受残缺 → 策略一,实现成本最低
- 有副作用的调用 → 无论选哪种,先把幂等做好
工程上要配套的几件事
一、边流边存。 流式内容要实时落盘或落缓存,别只放在内存里等结束后统一保存——中断时内存里那份可能一起丢了。
二、记录中断点。 存下已生成的 token 数和最后一段内容,续写时要用。
三、把中断计入监控。 统计「中断率」和「中断时的平均已生成长度」。中断率突然上升,通常是超时配置、网络链路或厂商侧出了问题。
四、区分 finish_reason。 因为达到 max_tokens 而停止,和网络中断,处理方式完全不同:前者是你的参数设小了,后者才需要重连。混在一起统计会掩盖真实问题。
前端和后端各该做什么
流式场景的中断处理是前后端配合的事,职责划分不清就会互相等对方处理。
后端要做的:
- 逐块接收并立即转发给前端,同时写入持久化存储
- 记录已发送的 token 数和最后一段内容
- 判断结束原因(正常完成、达到上限、异常中断),把这个信号明确传给前端
- 中断时不要静默吞掉,要发一个明确的错误事件
前端要做的:
- 区分「还在生成」「正常结束」「异常中断」三种状态,UI 上表现不同
- 中断时保留已收到的内容,不要清空
- 提供「继续生成」入口,而不是让用户重新提问
- 处理连接断开后的重连(如果产品需要)
最容易出问题的是结束信号的传递。后端知道是异常中断,但如果没把这个信息传给前端,前端只会看到流突然停了,无法区分「写完了」和「断了」,于是只能按写完处理——用户就看到一段莫名其妙的残缺内容。
怎么减少中断本身
处理中断固然重要,但更根本的是减少中断发生。几个常见的着力点:
一、检查链路上所有中间设备的超时设置。 负载均衡、反向代理、API 网关、CDN,每一层都可能有空闲超时。流式连接在两个数据块之间如果间隔较长,会被误判为空闲而断开。把这些超时调到大于你的典型生成时长。
二、发送心跳。 有些实现会在数据块之间发送空注释行保持连接活跃,能有效对抗中间设备的空闲回收。
三、控制单次输出长度。 生成时间越长,撞上任何一层超时的概率越高。把一次超长生成拆成几次短生成,中断率会明显下降,见 max_tokens 该设多大。
四、监控中断率的分布。 按输出长度分组看中断率,如果长输出的中断率显著更高,那就是超时问题而不是随机故障。
一个常被忽略的点:中断也要计费
已经生成的 token 是要付费的,即便你没用上。所以高中断率不只是体验问题,也是成本问题——你在为一堆没交付的内容付钱。
如果中断率长期偏高,优先查两件事:中间设备的超时设置(负载均衡、网关、代理的空闲超时是否短于你的典型生成时长),以及输出长度是否过长(把一次超长生成拆成几次短生成,能显著降低单次中断的损失)。相关的成本影响见长文本任务的四个成本陷阱。
三个高频问题
问:怎么区分是模型写完了还是断了? 看 finish_reason。正常完成、达到 max_tokens、异常中断是三种不同的信号,务必分开处理。
问:续写会不会重复已有内容? 有可能。缓解办法是在提示里给出最后一小段原文作为锚点,并在拼接时做重叠去重。
问:用户主动取消要计费吗? 已经生成的部分照常计费。所以频繁被取消的场景,值得考虑先返回摘要再按需展开。