后端如何做 Timeout、Retry 和 Fallback

AI Chat 后端不是简单地把用户消息转发给模型。

一轮对话可能包含:权限校验、上下文组装、模型调用、工具调用、结构化解析、流式返回、消息持久化、费用统计、fallback 路由和日志记录。任何一步失败,都需要后端判断:能不能重试,是否已经产生副作用,是否应该降级,最终应该给前端什么状态。

后端稳定性的核心是:失败不可避免,但失败不能让状态变得不确定。

场景

在一个企业知识库和工单助手里,用户问:

这个客户的合同续费条款是什么?如果快到期,帮我生成跟进建议。

后端处理链路可能是:

  1. 校验用户是否有客户和合同权限。
  2. 检索合同、历史工单和客户状态。
  3. 调用模型总结续费条款。
  4. 如需要,调用 CRM 工具查询续费日期。
  5. 流式返回分析和建议。
  6. 把完整 assistant 消息写入历史。

失败可能发生在每一步:

  • 权限服务慢。
  • 检索超时。
  • 模型供应商 429。
  • CRM 工具 5xx。
  • 模型输出到一半连接断开。
  • 写库成功但前端没有收到 done。
  • retry 时重复创建了跟进任务。

如果后端没有明确策略,系统会陷入“看起来失败,但不知道失败到哪一步”的状态。

问题类型

后端可靠性问题可以拆成五类。

问题类型常见表现关键判断
Timeout模型慢、工具慢、网关超时、队列等待过久等多久,哪一层超时,超时后状态是什么
Retry临时网络错误、429、5xx、供应商连接断开是否可重试,重试是否会重复副作用
Fallback主模型不可用、工具失败、上下文过长降级到什么能力,是否告知用户
幂等用户重复发送、前端重试、服务端重放同一请求是否只产生一次业务结果
降级关闭工具、返回部分结果、转人工、异步处理牺牲什么能力,保住什么体验

模型适合处理的是:在降级上下文中生成说明、基于部分资料回答、解释哪些信息无法确认。

程序必须处理的是:错误分类、超时预算、重试次数、幂等 key、工具副作用、事务、状态持久化、fallback 路由和审计。

人工应该介入的是:多次失败、高风险工具动作、对外承诺、权限异常、涉及客户权益的降级决策。

收益和成本判断

后端可靠性设计的收益包括:

  • 降低用户遇到空白失败的概率。
  • 减少重复调用模型和工具的成本。
  • 防止 retry 导致重复写库、重复发送和重复扣费。
  • 在供应商故障时保持基础服务可用。
  • 让事故复盘可以定位到具体阶段。
  • 让前端获得明确状态,而不是猜测。

成本包括:

  • 需要定义错误分类和状态机。
  • 需要维护模型和工具的 fallback 路由。
  • 需要幂等存储、事务和工具调用记录。
  • 需要设置多层超时预算。
  • 需要监控、告警和压测。

如果系统只是低风险草稿生成,简单策略通常足够:单次模型调用、服务端超时、失败返回可重试错误、前端手动重试。

如果系统会调用工具、写入业务数据、面向客户服务或承载高并发,就需要工程方案。因为这时失败不仅影响体验,还可能造成重复副作用、错误承诺、状态不一致和成本失控。

方案阶梯

后端可靠性也应该从轻到重升级。

第一层是单次调用超时。它能防止请求无限挂起,但不能解决重复提交和局部失败。

第二层是错误分类。后端要知道错误是否可重试、是否应该展示给用户、是否需要人工处理。

第三层是有限重试。只对临时错误重试,并设置次数、退避和总预算。

第四层是状态持久化。对话任务不再只存在于内存,断连或重启后仍可恢复。

第五层是幂等保护。发送、重试、工具调用和副作用写入都能防重复。

第六层是 fallback 路由。主模型、检索、工具或供应商失败时,有明确替代路径。

第七层是降级和观测。系统在高延迟、高错误率或成本压力下能主动降级,并能复盘每次失败。

什么时候简单策略足够

以下情况可以先用简单后端策略:

  • 只做低风险文本生成。
  • 不调用业务工具,不写入外部系统。
  • 重试只会重新生成答案,不会产生副作用。
  • 用户量小,失败后手动重试可接受。
  • 不要求跨请求恢复 streaming 状态。
  • 没有明确 SLA 或高并发压力。

这时可以设置一个后端总超时、一个模型调用超时、基础错误日志和前端手动 retry。不要为了早期验证产品价值过早引入队列、复杂路由和多级降级。

什么时候需要工程方案

以下情况应该升级:

  • 对话会调用只读或有副作用工具。
  • 回复会写入工单、CRM、审批、合同或客户沟通记录。
  • 用户可能重复发送、前端会自动 retry,或服务端会重放请求。
  • 主模型不可用时业务仍需要基础服务。
  • 失败会造成重复扣费、重复发送、重复创建任务或错误承诺。
  • 团队需要按 trace 复盘一轮对话失败到哪一步。

这时后端需要任务状态、错误分类、有限重试、幂等 key、工具调用记录、fallback 路由、降级阈值和可观测性。可靠性设计必须进入接口协议和数据模型,而不是只写在异常捕获里。

Timeout 设计

超时不是一个数字,而是一组预算。

推荐分层设置:

  • 网关总超时:保护整个请求不会无限占用连接。
  • 后端总超时:控制一次业务处理的最大耗时。
  • 模型首 token 超时:避免用户一直看不到反馈。
  • 模型总生成超时:避免长答案无限生成。
  • 工具调用超时:每个工具按业务重要性设置。
  • 持久化超时:写库失败要进入明确失败状态。

超时后要记录“失败阶段”。TIMEOUT 不够,至少要区分 AUTH_TIMEOUTRETRIEVAL_TIMEOUTMODEL_FIRST_TOKEN_TIMEOUTTOOL_TIMEOUTPERSIST_TIMEOUT

Retry 设计

重试必须先判断错误是否可重试。

通常可重试:

  • 临时网络错误。
  • 供应商 429。
  • 供应商 5xx。
  • 连接中断且没有副作用。
  • 工具只读请求超时。

通常不应重试:

  • 权限失败。
  • 参数校验失败。
  • 用户输入不足。
  • 内容安全拦截。
  • 业务状态不允许。
  • 已经执行成功但响应丢失的有副作用操作。

推荐使用有限重试和退避:

maxRetries: 2
baseDelayMs: 300
maxDelayMs: 2000
jitter: true
retryableCodes: ["PROVIDER_429", "PROVIDER_5XX", "NETWORK_RESET"]

重试还要有总预算。例如一轮对话最多 60 秒,不能让模型调用重试三次后留给持久化和前端收尾的时间为零。

Fallback 设计

fallback 不是“换一个模型再试一下”这么单一。

可以按能力分层:

Fallback 类型适用场景用户感知
同模型重试临时网络抖动通常无需特别说明
同级模型切换主供应商 5xx 或限流可提示系统繁忙但已恢复
轻量模型降级主模型慢或成本过高说明可能只提供基础回答
关闭工具工具不可用但可回答通用建议明确无法查询实时数据
返回部分结果检索成功但工具失败标记哪些信息已确认
异步处理用户可稍后查看结果告知任务已进入处理队列
转人工高风险、多次失败、客户权益相关展示人工接管状态

fallback 的原则是:可以降低能力,但不能伪装成完整成功。

如果 CRM 工具失败,模型可以基于合同内容给出一般续费建议,但不能说“客户将在 5 月 20 日到期”,除非这个日期来自可靠来源。

幂等设计

重试和 fallback 之所以危险,是因为后端可能已经完成了一部分操作。

至少需要三类幂等 key:

  • requestId:一次前端请求的唯一标识。
  • responseId:一次 assistant 生成的唯一标识。
  • toolCallId:一次工具调用的唯一标识。

对于有副作用的工具,还需要业务级幂等。例如创建工单、发送邮件、扣费、写 CRM 任务,都不能只依赖 HTTP 重试。

推荐记录工具调用表:

字段作用
toolCallId防重复调用
conversationId关联会话
requestId关联请求
toolName工具名称
inputHash判断参数是否相同
statuspending / success / failed
resultRef成功结果引用
errorCode失败原因
createdAt / updatedAt审计和恢复

当 retry 到来时,后端先查 toolCallId 或 inputHash。如果同一副作用动作已经成功,就返回已有结果,而不是再次执行。

降级设计

降级和 fallback 相近,但更偏系统容量和产品承诺。

常见降级策略:

  • 高峰期关闭长上下文,只保留最近消息。
  • 暂停非必要工具,只保留只读查询。
  • 从高质量模型切到低延迟模型。
  • 将实时生成改为异步任务。
  • 对免费用户降低并发或上下文长度。
  • 多次失败后转人工,不再继续模型重试。
  • 返回“当前无法确认”的安全答复,而不是猜测。

降级要有触发条件:

  • 模型错误率超过阈值。
  • P95 延迟超过阈值。
  • 工具超时率上升。
  • 队列积压超过阈值。
  • 成本预算接近上限。

降级也要有退出条件。否则系统可能长期停留在低质量状态。

方案选型表

场景条件推荐方案不推荐方案判断理由额外成本
低风险文本生成单次超时 + 手动重试复杂队列和多级 fallback失败后重来即可基础日志
模型偶发 429 / 5xx有限重试 + 退避立即报错或无限重试临时错误可恢复,但要控成本retry 策略
首 token 慢首 token 超时 + 进度事件让用户无反馈等待用户需要知道系统仍在工作流事件和阶段状态
只读工具超时工具级 retry + fallback 说明整轮对话失败可返回部分结果工具错误分类
有副作用工具幂等 key + 调用记录失败后自动整轮重试防止重复执行动作存储、事务、审计
主模型不可用模型路由 + 降级提示静默切换且不告知能力变化会影响可信度路由和质量验证
高风险客户流程转人工 + 审计 + 可回放模型持续自动尝试失败代价高于等待成本人工队列和运营流程

反模式

第一种反模式:所有错误都重试。

权限失败、参数错误、内容安全拦截和业务状态不允许,重试不会变好,只会增加成本和延迟。

第二种反模式:没有总超时预算。

每一步都设置了超时,但总和超过用户能接受的等待时间。结果系统看似有保护,体验仍然很差。

第三种反模式:有副作用工具没有幂等。

发送邮件、创建工单、扣费、写任务这类动作,一旦被 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、幂等和降级策略。

最重要的判断是:失败发生时,系统是否知道自己失败到哪一步;重试发生时,系统是否知道哪些事情已经做过;降级发生时,用户是否知道答案能力发生了变化。