AI 生成到 90% 突然断了:你的解决方案是?
今天看到个有点意思的问题,我把它发给朋友:
AI 生成到 90% 突然断了,你的解决方案是?
朋友秒回:“继续。”
我说:“没 Token 了呢,继续不了。”
他又回了两个字:“充钱。”
万能的氪金!
玩笑归玩笑,如果不是额度用完,而是 AI 在流式生成到 90% 时突然断开,“继续”真的有用吗?它究竟是从断点接着写,还是偷偷从头想了一遍?
这个看似简单的操作,背后可能是三件完全不同的事:补发你没收到的内容、根据已有文字重新续写,或者恢复模型中断前的计算状态。
先说结论:
AI 流式生成中的“恢复”,至少包含连接恢复、内容恢复和推理恢复三层。我们看到的“继续生成”,未必真的恢复了模型中断前的计算现场。
一、输出到 90% 断了,会发生什么
普通文字停在半句话,用户通常还能看懂前面的内容。但 AI 输出越来越多地被程序直接消费:JSON 用来渲染表单,HTML 用来生成页面,Markdown 用来展示文档,工具参数还可能触发发邮件、扣款等真实操作。
这些内容断在中间,问题就不只是“少了几个字”。
1. 普通文本:可能重复、漏字或风格突变
纯文本的容错能力最强。即使停在半句话,已经收到的部分通常仍然可读。
但如果原来的模型请求已经失败,系统只能重新请求模型续写,新内容就可能重复上一段,也可能突然改变语气、人称甚至结论。因为“重新续写”并不等于“恢复现场”,它只是基于已有文本发起了另一次推理。
2. JSON:少一个括号,整个对象都不能用
模型可能停在这里:
{ |
此时缺少右引号、右花括号和数组结束符,直接调用 JSON.parse() 一定报错。如果恢复后的模型又从一个新的 { 开始输出,把前后两段简单拼接也无济于事。
更稳妥的做法是:
- 流式阶段保存原始字符串,不把每个 chunk 当成完整 JSON;
- 使用增量解析器,保留引号、转义符、数组和对象的解析状态;
- 或者把结构化输出拆成
field_update、JSON Patch 等独立事件; - 收到
completed后,再执行完整的 Schema 校验; - 重新生成时创建新的
revision,不要把两次生成结果直接拼接。
3. HTML:能显示,不代表结构正确
模型可能只生成到:
<section class="card"> |
浏览器通常会尝试补齐缺失标签,因此页面不一定立刻白屏,却可能出现布局跳动、节点嵌套错误,甚至把后面的内容全部塞进同一个元素。
如果把模型生成的 HTML 直接写入 innerHTML 或框架的 v-html,还会引入 XSS 风险。流式中断和恢复不能绕过原有的安全策略。
实际处理时应当:
- 流式阶段优先按纯文本或代码预览;
- 必须预览 HTML 时,先严格清洗,再放进受限的 sandbox iframe;
- 不在半成品阶段执行脚本、事件属性或外部资源;
- 收到完成事件后,再做一次完整解析、清洗和渲染。
4. Markdown:不报错,也可能显示全乱
Markdown 常见的中断位置包括:
- 代码围栏没有闭合,后面的正文全部显示成代码;
- 表格只生成了一半,列数不断变化;
- 链接只生成了
[标题](,导致后续内容渲染异常; - 加粗、列表或引用块停在半截,页面持续抖动。
客户端可以使用支持增量更新的 Markdown 渲染器,也可以只在完整的自然段、代码块或事件边界上刷新。为了预览而临时补上的围栏或标签,只能留在显示层,不能写回真实内容。
5. Unicode:一个汉字也可能被切成两半
网络分片(chunk)按字节传输,并不保证刚好落在字符边界。UTF-8 汉字、Emoji 或组合字符都可能横跨两个分片。如果每个分片单独解码,客户端就可能出现 � 或乱码。
接收端需要使用支持流式状态的解码方式。例如 Web 客户端可以调用 TextDecoder.decode(chunk, { stream: true }),让解码器暂存尚未完整的字节序列。
6. 代码和 Agent:危险的不只是显示异常
代码中断时,函数、字符串和注释都可能没有闭合。此时自动执行测试、部署,或者直接覆盖原文件,都可能扩大故障。更安全的做法是先标记为 draft,完成语法检查后,再通过临时文件、Patch 或原子替换落盘。
Agent 工具调用的风险更高。工具参数经常以 JSON 增量形式到达,中断时可能只收到一半;恢复后如果直接重试,还可能重复发邮件、重复扣款或重复创建记录。
因此,工具调用至少需要:
- 通过
tool_call_id聚合同一次调用的参数; - 收到完成标记并通过 Schema 校验后才执行;
- 为有副作用的操作设置幂等键;
- 保存
pending / running / succeeded / failed状态; - 恢复时先查询原操作是否已经成功,再决定是否重试。
这些看似不同的问题,其实可以归结为同一个原则:
半成品可以展示,但不能当作完成数据解析、执行或提交。原始增量、解析状态和业务提交状态必须分开保存。
二、页面断了,不代表生成任务结束了
要理解如何恢复,得先找到故障发生的位置。一次常见的 AI 流式生成,大致经过这条链路:
模型服务 → 流式请求 → 业务服务 → SSE / WebSocket / 流式 HTTP → 客户端 |
所谓“生成到 90% 突然断了”,可能发生在任何一段。
第一种:客户端连接断了,模型仍在生成
这是最常见、也是最好处理的情况。
用户刷新了 Web 页面、移动端切换网络、桌面应用进入休眠,或者流式连接被网关关闭,但后台任务和模型请求都没有停止。模型仍在继续生成,服务端也在持续保存结果。
客户端重新打开会话后,服务端只需要把它没收到的部分补发回来。这种情况可以做到精确恢复,因为缺失的内容其实已经生成,只是没有成功送达当前客户端。
第二种:业务服务与模型之间的连接断了
这时麻烦大了。
模型可能只生成到某个 Token,上游流式请求就失败了。如果模型接口无法继续原任务,业务服务通常也拿不到模型当时的内部计算现场。
系统只能保留已收到的文本,把它们重新放进 Prompt,要求模型“从这里继续,不要重复”,然后发起一次新的模型请求。
它属于语义续写,不是精确续传。
第三种:生成服务本身崩溃了
如果任务状态、已生成文本和进度都只存在进程内存里,那么进程一崩,基本只能重新开始。
更成熟的系统会把生成任务交给独立 Worker,并持续记录任务状态和输出事件。这样即使服务实例重启,也能知道任务执行到了哪里、哪些内容已经生成,以及是否需要重试。
所以在讨论恢复之前,需要先回答:
断掉的是客户端订阅连接、模型请求,还是整个生成任务?
三、“继续”,可能是三种不同操作
找到断点以后,才能决定怎么“继续”。产品界面上只有一个交互,内部却可能执行三种完全不同的操作。
1. 重放
服务端已经生成了内容,只是客户端没有收到。
系统只需重新发送缺失事件,不必再次调用模型,内容可以保持完全一致。这是最可靠、成本也最低的恢复方式。
2. 重新续写
原来的模型请求已经失败,系统把已有文本重新交给模型,请它继续完成。
它会产生一次新的推理,可能重复、漏写或改变风格。对于 JSON、HTML、代码和工具调用,还要处理结构断裂与重复副作用。
3. 推理状态恢复
系统保留或重新加载 KV Cache、Token 序列和采样状态,让模型从原来的计算现场附近继续生成。
它最接近真正的“从模型断点继续”,但实现复杂,通常需要自建推理服务。
| 操作 | 实际发生了什么 | 能否精确恢复 |
|---|---|---|
| 重放 | 补发已经生成的事件 | 可以 |
| 重新续写 | 发起一次新的模型推理 | 不一定 |
| 推理状态恢复 | 加载原任务的推理状态 | 取决于状态是否完整 |
四、真实项目通常怎么处理客户端断线?
真实项目里,一个常见的坑是:客户端连接一断,就立刻取消模型请求。
这会把页面刷新、网络切换、应用休眠和网关超时都误判成用户主动放弃。
更稳妥的做法,是把“生成任务”和“客户端连接”解耦:
客户端只是结果的订阅者,不是生成任务的所有者。
连接断开后,后台任务可以继续运行一段时间,并把新内容写入事件日志。客户端回来后,再从最后收到的位置继续订阅。
创建生成任务 |
不同任务可以采用不同策略:
- 普通聊天:断线后继续生成并缓存;
- 长报告或代码:设置 30~60 秒宽限期,超时后再决定取消;
- 图片、视频等后台任务:继续执行,不依赖客户端连接;
- 实时语音:断线后尽快停止,避免持续消耗资源。
系统还要区分“网络断开”和“用户主动停止”。用户点击停止时,客户端应该调用独立的取消接口,而不是简单关闭 SSE 或 WebSocket。
五、事件日志如何保证内容不重不漏?
每个输出片段都可以记录为一个带递增序号的事件:
{ |
其中,generation_id 标识生成任务,revision 区分重新生成后的版本,seq 负责排序、查漏和去重。如果客户端已经连续收到 seq=57,重连时就要求服务端从 58 开始补发。
客户端已有:1 ~ 57 |
客户端按 generation_id + revision + seq 去重,即使服务端重复发送也不会重复拼接。近期事件可以放在 Redis Stream 等短期存储中,最终结果写入数据库;长任务则使用“文本快照 + 少量增量”,避免从第一个 Token 重放。
结构化内容还要额外保存解析状态:JSON 完成后再校验,HTML 清洗后再渲染,工具调用则在参数完整且幂等校验通过后执行。
六、KV Cache 能让模型“原地续上”吗?
事件日志解决的是内容补发问题。但如果模型请求真的停了,再次生成时还可能需要重新计算全部前文。
KV Cache 保存了历史 Token 在注意力计算中产生的 Key 和 Value,可以把它理解成模型的“计算草稿”:
文本:模型已经读过和生成了什么 |
如果模型已经处理了 10,000 个 Token,没有 KV Cache 就要重新执行 prefill;如果推理服务保留了可复用的 KV Cache,就能跳过大部分前文计算,更快生成下一个 Token。
事件日志解决“用户不要丢内容”,KV Cache 解决“模型不要重算前文”。
不过,拥有 KV Cache 不等于自动获得“断点续传”。它不能替代事件日志,而且体积很大。跨节点恢复还可能经历:
GPU → CPU → 网络存储 → CPU → 另一块 GPU |
恢复时还要保证模型版本、Tokenizer、缓存格式和采样状态兼容。第三方模型 API 通常不会把 KV Cache 交给应用管理,因此绝大多数产品仍以事件重放为主;只有原任务确实失败时,才降级为语义续写。
写在最后
可靠的 AI 产品,需要分别管理连接、内容和推理状态:事件日志保证内容可重放,任务系统让生成不依赖某一次客户端连接,KV Cache 则在值得的场景里减少重复计算。
本文标题:AI 生成到 90% 突然断了:你的解决方案是?
文章作者:Canace
发布时间:2026-08-03
最后更新:2026-08-03
原始链接:https://canace.site/ai-stream-recovery/
版权声明:转载请注明出处
分享