Next.js AI 局部响应恢复:流中断后保留、重试与续传
AI 回答已输出一半后断开,简单重试可能重复文本、重复工具副作用或丢失上下文。本文建立事件日志、恢复游标、错误分类和安全重试边界。
本文要点
- 主题聚焦:Next.js、React、DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
传统页面请求失败时,重新加载通常足够;AI 流已经输出一半时,“再试一次”却不是一个动作。网络可能只丢了最后几帧,模型可能已经完成但结束事件没到,工具调用可能成功产生了外部副作用,而浏览器只知道连接断开。若客户端清空现有文字并重发全部请求,用户会丢失可用内容,系统也可能重复扣款、发邮件或修改数据。
恢复设计的第一原则是把“看到多少文本”和“运行执行到哪里”分开。React 中的部分文本只是投影,服务端 run 才是事实。每次生成有稳定 runId,服务端为输出事件分配单调递增的 sequence,并在可恢复时间窗内保存事件或检查点。客户端记录最后完整应用的序号,断线后用 runId + afterSequence 查询状态或重新订阅,而不是立即创建新 run。
先判断失败发生在哪一层
至少要区分五类结果:用户主动取消、浏览器传输中断、模型上游失败、工具步骤失败、服务端策略拒绝。它们的恢复权限不同。主动取消默认不自动重连;纯传输中断可以续订同一 run;模型失败只能在确认没有继续执行后重试;工具失败要看操作是否幂等;策略拒绝则不能通过重试绕过。
HTTP 状态只能描述响应开始前的错误。流一旦返回 200 并发送正文,后续失败应通过结构化事件表达,例如 error { code, retryable, phase, lastSequence }。若连接直接消失,客户端把状态标为 disconnected 而不是 failed,先向状态接口询问 run 是否仍在执行、已经完成或确实失败。这样可以避免“结束事件丢失导致已经完成的回答被重复生成”。
Next.js 的路由错误处理负责返回一致的公开错误结构;React Error Boundary 负责渲染阶段崩溃的降级界面,但二者都不会自动处理流读取 Promise 中的业务错误。读取循环必须捕获异常并向 reducer 派发带 runId 的事件。可结合 React 错误边界 理清渲染故障与异步运行故障的边界。
事件重放必须天然去重
客户端 reducer 只接受 sequence > lastAppliedSequence 的事件。文本事件最好携带不可变增量或 segment ID,不能只说“当前完整字符串”,否则断线前后快照容易相互覆盖。服务端恢复接口在同一事务语义下返回 run 状态与事件上界,客户端应用完重放事件后再接实时流,避免查询与订阅之间留下空洞。
若基础设施无法保留逐块 token,可按语义段落建立检查点。恢复时展示已确认段落,把未确认尾部标成临时状态;重新生成只针对后续部分,并明确告诉模型已确认内容的摘要和结束位置。不要声称能从任意 token 精确续写:多数模型 API 不保证相同输入产生完全相同后缀,强行拼接可能重复半句话或改变结论。
用户选择“重新生成”时创建新 runId 和新候选版本,旧的部分回答仍保留并标注结束原因。不要让新流继续写旧消息,也不要把未完成答案默认纳入下一轮上下文。流式状态与取消细节可参考 LLM 流式前端实现。
重试边界由副作用决定
纯生成调用在请求未被上游接受前通常可以退避重试,但收到 token 后自动重试要更谨慎。工具调用必须携带业务幂等键,服务端保存 prepared / executing / succeeded / failed / unknown 状态。只有能确认未执行或幂等执行的步骤才允许自动重试;状态为 unknown 时先查询外部系统,无法确认则进入人工处理。
例如“查询订单”可以安全重复,“发送邮件”需要稳定去重键,“转账”则不能靠前端按钮直接重放。恢复引擎从最近的安全检查点继续,而不是从整段对话开头执行全部工具。更系统的顺序可参考 Agent 故障恢复设计;整个服务的上游切换与数据恢复则可结合 LLM 灾难恢复。
重试还必须有总预算:最大次数、累计时间、供应商切换次数和成本上限。指数退避加入抖动,并尊重服务端 Retry-After。当错误不可重试或预算耗尽,界面保留已确认内容,显示具体到动作的选项,例如“重新连接”“从此处创建新版本”或“联系人工”,而不是一个对所有故障都相同的重试按钮。
降级界面要保护已有成果
消息组件可分别渲染 streaming、disconnected、partial、completed 和 failed。局部消息失败不应让整个会话树被 Error Boundary 替换;来源面板失败也不应清空答案。对已确认段落保持可复制和可阅读,对尚未确认的尾部使用清晰状态,不用无限旋转图标掩盖停滞。
服务端日志关联 conversationId、runId、最后序号、执行阶段、重试次数和上游请求 ID,正文默认脱敏。指标观察断线恢复成功率、重放事件数、重复事件丢弃数、部分回答保留率、工具未知状态、重试额外 token 与恢复耗时,并将失败预算纳入 SLO 与错误预算 的发布门禁。
验证必须注入真实故障:首字节前断开、中文字符编码边界处断开、结束事件前断开、事件重复与乱序、浏览器刷新、服务实例重启、模型 429、工具成功但响应丢失、恢复窗口过期。每个场景都检查最终消息没有重复片段、危险工具没有重复执行、旧 run 不能污染新 run、资源最终释放。能解释并证明这些结果,才是恢复;单纯在异常后再次调用模型,只是把不确定性放大了一次。
Related