上周排查一个生产问题,Agent 调用天气 API 时报 503,重试三次都失败后,Agent 不干了——它把"获取天气失败"写进了最终答案,然后终止执行。用户看到的是"抱歉,今天无法查询天气"。真正的服务端日志显示,Agent 在第一次失败后继续调了一轮其他工具做了完整分析,但因为没有合理的中断机制,浪费了 4000 个 token 的推理成本,最后给的还是一个错误答案。
这类问题的根源不在于模型能力,而在于 Agent 的编排层缺少容错设计。传统后端服务对断路器和重试策略已经驾轻就熟,但到了 Agent 系统里,同样的故障模式变成了新问题:失败会在工具调用链里传播,一次 API 超时可能引发整条链路的 token 浪费,更糟的是,LLM 会尝试"弥补"错误而产生幻觉。
断路器:Agent 需要的不是无限重试,是快速失败
断路器模式在微服务体系里已经非常成熟:Closed 状态正常调用,失败率达到阈值切换到 Open 状态直接快速失败,经过冷却时间进入 Half-Open 尝试恢复。但把断路器搬到 Agent 系统里,有几个 Agent 独有的设计点。
第一个问题是粒度。Agent 在一个执行循环里可能调用多个不同工具:调用搜索 API、调数据库、调代码解释器。这些工具的可用性特征完全不同。一个共享的全局断路器没有意义——搜索 API 超时不应该让代码解释器的调用也直接失败。正确的做法是为每个工具独立维护断路器。LangGraph 等编排框架的节点级重试策略可以下沉到每个工具节点,但这还不够:断路器需要维护状态,跨 Agent 执行实例的共享状态才能做到真正的保护。
第二个问题是失败阈值怎么算。在 Agent 场景下,单一 HTTP 503 不一定意味着工具不可用——可能是瞬时的限流或者网络抖动。但 Agent 的重试行为和人类不同:用户可能只重试一两次,Agent 如果被告知"失败了就重试",在一个循环里重试五六次是常有的事。有些团队在 agent 的 system prompt 里写"如果工具返回错误,最多重试两次",这取决于模型是否遵守,不够可靠。断路器的阈值应该在编排层硬编码,而不是靠 prompt 约束。
第三个问题是快速失败后的替代路径。断路器 Open 了,Agent 不应该直接崩溃。一个实用的模式是"降级工具链":主搜索断路时切到备用搜索,没有备用源就返回缓存快照,最终兜底是告诉 Agent "这个工具暂时不可用,跳过相关步骤"。这需要 Agent 的编排层支持条件路由,而不是让 Agent 自己去决定"没有搜索我怎么回答问题"——那大概率会导致幻觉。
检查点恢复:当 Agent 执行到一半崩溃
比工具调用失败更棘手的是 Agent 自身进程崩溃。一个 Agent 可能已经执行了三轮工具调用、积累了 20K token 的上下文,这时进程因为 OOM 或网络断开而终止。所有中间状态丢失。用户看到的不是重试,是一条彻底的失败消息。
这个问题的标准解法是检查点持久化。LangGraph 的 checkpointer 机制是一个参考——在每一次"超级步"(superstep,即 LLM 调用 + 工具执行)完成之后,将当前状态写入持久化存储。存储后端可以是 SQLite 或 Postgres。崩溃后重新加载最近的检查点,从断点处继续执行。
检查点恢复有几个工程细节值得注意。第一个是幂等性。一个工具调用可能在检查点写入之前完成、写入之后崩溃。恢复重新执行时,那个工具会被再次调用。如果工具不是幂等的(比如"发送订单确认邮件"),就会产生副作用。解决方案是在检查点里记录已经执行成功的工具调用及其返回值,恢复时跳过这些步骤,直接从下一个待执行的步骤继续。
第二个是上下文窗口管理。Agent 执行过程中积累的完整对话历史可能超出模型的最大上下文窗口。恢复后如果从检查点重现执行,需要跳过那些已完成的 LLM 往返,只把最终结果放回上下文。否则上下文会在恢复时翻倍膨胀。
第三个是超时兜底。Agent 执行循环本身应该有整体超时,而不是依赖每个工具调用的独立超时。如果 Agent 已经执行了 30 轮工具调用,且每次调用的 token 成本在增长,整体超时是一个安全网。检查点加上整体超时,可以做到"超时后从最新检查点恢复",而不是从头重试。
重试策略分层:并不是所有失败都值得重试
不是所有失败模式都适用相同的重试策略。Agent 系统中常见的失败类型可以分为几层:
瞬时失败:网络抖动、限流 429、临时 503。这类失败适合指数退避重试,最多三次。注意每次重试之间加入 jitter,避免所有 Agent 实例同时重试造成"重试风暴"。
语义失败:工具调用参数错误、工具返回格式不符合预期。这类失败不应该重试——重试只会花更多 token 得到同样的错误。编排层应该捕获这类错误并将控制权交还给 Agent 的推理循环,让 Agent 修正参数或换一个工具。Anthropic 的 tool use 最佳实践里建议用结构化输出来约束参数生成,减少语义失败的概率。
级联失败:一个工具的失败导致后续工具全部不可用。例如身份验证 token 过期,所有需要该 token 的工具调用都会失败。这时候需要刷新 token 后整体重试,而不是逐个工具重试。编排层要能识别出这类失败模式——如果连续三个不同工具返回同一个错误码(比如 401),发起一次认证刷新,然后从失败点重新进入任务。
致命失败:非法输入、配置错误、内部断言失败。这类错误不应该重试。编排层应该终止执行并把错误上下文完整记录下来供排查。
分层重试需要在编排层有明确的失败分类逻辑。一个实用的做法是让每个工具调用的返回带上错误类型标签,编排层根据标签选择重试策略。这比让 LLM 自己判断"这个错误要不要重试"可靠得多。
Agent 执行的逃生舱设计
即使有了断路器、检查点和分层重试,仍然需要逃生舱——当所有保护机制都失效时,系统如何安全退出。
逃生舱的核心是两件事:有损降级和人工兜底。
有损降级的思路是:当 Agent 无法完成完整任务时,给出当前能提供的最佳结果。例如一个"分析销售趋势"的 Agent,如果数据库查询工具断路,它可以输出"销售数据暂不可用,以下是基于缓存快照的趋势分析",并注明数据范围。这比给出空答案或幻觉数字要好。
人工兜底意味着当 Agent 连续失败达到阈值时,不继续尝试也不返回错误,而是将任务升级给人工处理。这需要 Agent 框架支持"半成品状态序列化"——把已完成的步骤、失败原因、当前上下文打包成一个工单,推送到人工队列。用户在接手时能看到 Agent 已经做了什么、在哪里失败的,不需要从零开始。
这两种模式在提示词层很难实现。它们依赖编排层的状态管理和流程控制能力。如果团队目前只是把 prompt 写得尽量详细来兜底失败,说明编排层的容错设计还停留在手工阶段。真正的生产级容错应该交给代码,而不是提示词。
落地建议
对于正在建设 Agent 生产系统的团队,建议从三个层面落地容错设计:
编排框架层面,选一个支持节点级重试和检查点持久化的框架。LangGraph、Temporal、甚至自己实现的简单状态机都行,关键是编排层要能控制执行流,而不是完全交给 LLM。
监控层面,对每个工具的调用延迟、失败率、断路器状态做指标采集。断路器的开放/关闭转换应该触发告警。工具调用的失败模式应该按类型分类,方便定位瓶颈。
测试层面,把容错机制作为测试的一部分。模拟工具调用超时、返回错误、进程崩溃等场景,验证断路器能否正确打开、检查点能否正确恢复。这些测试应该集成到 CI 里,每次 Agent 的逻辑变更都跑一遍。
回到文章开头的例子。那个天气查询 Agent 需要的不是更聪明的模型,而是一个断路器来避免在不可用的工具上浪费 token,一个合理的中断机制让 Agent 知道何时放弃,以及一个降级路径提供"暂时无法获取实时天气,这是昨天的数据供参考"这类有损结果而非空洞的失败信息。容错不是可选项,是 Agent 上生产前的必要条件。
#AI应用架构 #AI应用开发 #Agent应用架构 #Agent应用开发
夜雨聆风