后端如何做 Timeout、Retry 和 Fallback
AI Chat 后端不是简单地把用户消息转发给模型。
一轮对话可能包含:权限校验、上下文组装、模型调用、工具调用、结构化解析、流式返回、消息持久化、费用统计、fallback 路由和日志记录。任何一步失败,都需要后端判断:能不能重试,是否已经产生副作用,是否应该降级,最终应该给前端什么状态。
后端稳定性的核心是:失败不可避免,但失败不能让状态变得不确定。
场景
在一个企业知识库和工单助手里,用户问:
这个客户的合同续费条款是什么?如果快到期,帮我生成跟进建议。
后端处理链路可能是:
- 校验用户是否有客户和合同权限。
- 检索合同、历史工单和客户状态。
- 调用模型总结续费条款。
- 如需要,调用 CRM 工具查询续费日期。
- 流式返回分析和建议。
- 把完整 assistant 消息写入历史。
失败可能发生在每一步:
- 权限服务慢。
- 检索超时。
- 模型供应商 429。
- CRM 工具 5xx。
- 模型输出到一半连接断开。
- 写库成功但前端没有收到 done。
- retry 时重复创建了跟进任务。
如果后端没有明确策略,系统会陷入“看起来失败,但不知道失败到哪一步”的状态。
问题类型
后端可靠性问题可以拆成五类。
模型适合处理的是:在降级上下文中生成说明、基于部分资料回答、解释哪些信息无法确认。
程序必须处理的是:错误分类、超时预算、重试次数、幂等 key、工具副作用、事务、状态持久化、fallback 路由和审计。
人工应该介入的是:多次失败、高风险工具动作、对外承诺、权限异常、涉及客户权益的降级决策。
收益和成本判断
后端可靠性设计的收益包括:
- 降低用户遇到空白失败的概率。
- 减少重复调用模型和工具的成本。
- 防止 retry 导致重复写库、重复发送和重复扣费。
- 在供应商故障时保持基础服务可用。
- 让事故复盘可以定位到具体阶段。
- 让前端获得明确状态,而不是猜测。
成本包括:
- 需要定义错误分类和状态机。
- 需要维护模型和工具的 fallback 路由。
- 需要幂等存储、事务和工具调用记录。
- 需要设置多层超时预算。
- 需要监控、告警和压测。
如果系统只是低风险草稿生成,简单策略通常足够:单次模型调用、服务端超时、失败返回可重试错误、前端手动重试。
如果系统会调用工具、写入业务数据、面向客户服务或承载高并发,就需要工程方案。因为这时失败不仅影响体验,还可能造成重复副作用、错误承诺、状态不一致和成本失控。
方案阶梯
后端可靠性也应该从轻到重升级。
第一层是单次调用超时。它能防止请求无限挂起,但不能解决重复提交和局部失败。
第二层是错误分类。后端要知道错误是否可重试、是否应该展示给用户、是否需要人工处理。
第三层是有限重试。只对临时错误重试,并设置次数、退避和总预算。
第四层是状态持久化。对话任务不再只存在于内存,断连或重启后仍可恢复。
第五层是幂等保护。发送、重试、工具调用和副作用写入都能防重复。
第六层是 fallback 路由。主模型、检索、工具或供应商失败时,有明确替代路径。
第七层是降级和观测。系统在高延迟、高错误率或成本压力下能主动降级,并能复盘每次失败。
什么时候简单策略足够
以下情况可以先用简单后端策略:
- 只做低风险文本生成。
- 不调用业务工具,不写入外部系统。
- 重试只会重新生成答案,不会产生副作用。
- 用户量小,失败后手动重试可接受。
- 不要求跨请求恢复 streaming 状态。
- 没有明确 SLA 或高并发压力。
这时可以设置一个后端总超时、一个模型调用超时、基础错误日志和前端手动 retry。不要为了早期验证产品价值过早引入队列、复杂路由和多级降级。
什么时候需要工程方案
以下情况应该升级:
- 对话会调用只读或有副作用工具。
- 回复会写入工单、CRM、审批、合同或客户沟通记录。
- 用户可能重复发送、前端会自动 retry,或服务端会重放请求。
- 主模型不可用时业务仍需要基础服务。
- 失败会造成重复扣费、重复发送、重复创建任务或错误承诺。
- 团队需要按 trace 复盘一轮对话失败到哪一步。
这时后端需要任务状态、错误分类、有限重试、幂等 key、工具调用记录、fallback 路由、降级阈值和可观测性。可靠性设计必须进入接口协议和数据模型,而不是只写在异常捕获里。
Timeout 设计
超时不是一个数字,而是一组预算。
推荐分层设置:
- 网关总超时:保护整个请求不会无限占用连接。
- 后端总超时:控制一次业务处理的最大耗时。
- 模型首 token 超时:避免用户一直看不到反馈。
- 模型总生成超时:避免长答案无限生成。
- 工具调用超时:每个工具按业务重要性设置。
- 持久化超时:写库失败要进入明确失败状态。
超时后要记录“失败阶段”。TIMEOUT 不够,至少要区分 AUTH_TIMEOUT、RETRIEVAL_TIMEOUT、MODEL_FIRST_TOKEN_TIMEOUT、TOOL_TIMEOUT、PERSIST_TIMEOUT。
Retry 设计
重试必须先判断错误是否可重试。
通常可重试:
- 临时网络错误。
- 供应商 429。
- 供应商 5xx。
- 连接中断且没有副作用。
- 工具只读请求超时。
通常不应重试:
- 权限失败。
- 参数校验失败。
- 用户输入不足。
- 内容安全拦截。
- 业务状态不允许。
- 已经执行成功但响应丢失的有副作用操作。
推荐使用有限重试和退避:
重试还要有总预算。例如一轮对话最多 60 秒,不能让模型调用重试三次后留给持久化和前端收尾的时间为零。
Fallback 设计
fallback 不是“换一个模型再试一下”这么单一。
可以按能力分层:
fallback 的原则是:可以降低能力,但不能伪装成完整成功。
如果 CRM 工具失败,模型可以基于合同内容给出一般续费建议,但不能说“客户将在 5 月 20 日到期”,除非这个日期来自可靠来源。
幂等设计
重试和 fallback 之所以危险,是因为后端可能已经完成了一部分操作。
至少需要三类幂等 key:
- requestId:一次前端请求的唯一标识。
- responseId:一次 assistant 生成的唯一标识。
- toolCallId:一次工具调用的唯一标识。
对于有副作用的工具,还需要业务级幂等。例如创建工单、发送邮件、扣费、写 CRM 任务,都不能只依赖 HTTP 重试。
推荐记录工具调用表:
当 retry 到来时,后端先查 toolCallId 或 inputHash。如果同一副作用动作已经成功,就返回已有结果,而不是再次执行。
降级设计
降级和 fallback 相近,但更偏系统容量和产品承诺。
常见降级策略:
- 高峰期关闭长上下文,只保留最近消息。
- 暂停非必要工具,只保留只读查询。
- 从高质量模型切到低延迟模型。
- 将实时生成改为异步任务。
- 对免费用户降低并发或上下文长度。
- 多次失败后转人工,不再继续模型重试。
- 返回“当前无法确认”的安全答复,而不是猜测。
降级要有触发条件:
- 模型错误率超过阈值。
- P95 延迟超过阈值。
- 工具超时率上升。
- 队列积压超过阈值。
- 成本预算接近上限。
降级也要有退出条件。否则系统可能长期停留在低质量状态。
方案选型表
反模式
第一种反模式:所有错误都重试。
权限失败、参数错误、内容安全拦截和业务状态不允许,重试不会变好,只会增加成本和延迟。
第二种反模式:没有总超时预算。
每一步都设置了超时,但总和超过用户能接受的等待时间。结果系统看似有保护,体验仍然很差。
第三种反模式:有副作用工具没有幂等。
发送邮件、创建工单、扣费、写任务这类动作,一旦被 retry 重复执行,事故会比模型失败更严重。
第四种反模式:fallback 后仍使用完整成功文案。
如果关闭了工具、换了弱模型或只拿到部分上下文,就必须让用户知道答案边界。
第五种反模式:只在内存里记录任务状态。
服务重启、进程崩溃、连接断开后,running 状态无法恢复,前端只能永远 loading 或误判失败。
第六种反模式:写库放在流式返回之后才做。
如果前端已经收到完整答案,但后端写库失败,刷新后历史丢失。需要设计好持久化时机,至少能恢复最终状态。
工程清单
- 错误分类:是否区分权限、校验、模型、工具、网络、持久化和取消。
- 超时预算:网关、后端、模型首 token、模型总生成、工具和写库是否分层。
- Retry 策略:哪些错误可重试,次数、退避、jitter 和总预算是多少。
- 幂等 key:requestId、responseId、toolCallId 是否贯穿前后端。
- 工具记录:工具调用输入、状态、结果和错误是否可审计。
- 副作用保护:创建、发送、扣费、写入是否有业务级去重。
- Fallback 路由:主模型、备用模型、轻量模型、关闭工具、转人工是否有规则。
- 降级触发:错误率、延迟、队列、成本和供应商状态是否能触发降级。
- 降级退出:系统恢复后如何回到正常能力。
- 状态持久化:queued、streaming、failed、stopped、completed 是否可恢复。
- 前端协议:start、delta、error、done、fallback、cancelled 是否明确。
- 可观测性:traceId、阶段耗时、retry 次数、fallback 路径和最终状态是否记录。
- 成本控制:重试、长上下文、备用模型和异步任务是否有预算。
- 人工接管:多次失败或高风险流程是否能进入人工队列。
小结
后端可靠性设计的关键,是把“调用模型”升级成“管理一轮有状态的对话任务”。
低风险场景可以用简单超时和手动重试起步。只要涉及工具、副作用、客户流程、高并发或供应商不可用,就需要明确的 timeout、retry、fallback、幂等和降级策略。
最重要的判断是:失败发生时,系统是否知道自己失败到哪一步;重试发生时,系统是否知道哪些事情已经做过;降级发生时,用户是否知道答案能力发生了变化。