@sydneyrunkle: https://x.com/sydneyrunkle/status/2062588423295111208
摘要
本文介绍了如何使用 RetryPolicy、TimeoutPolicy 和错误处理程序为 LangGraph 代理添加容错能力,涵盖带退避的重试、超时以及用于生产环境可靠性的补偿逻辑。
查看缓存全文
缓存时间: 2026/06/05 05:09
LangGraph 中的容错机制:重试、超时与错误处理
在实际生产环境中,智能体会遇到原型阶段从未见过的错误:网络故障、工具调用错误、LLM 速率限制。
想象一下,你有一个运行了数小时甚至数天的任务,在任务中途遇到了不可恢复的错误。你会怎么做?放弃运行并完全重新开始?这不是生产级智能体的可持续方式。
编写快乐路径通常很简单。但让智能体在生产环境中存活下来的错误处理样板代码(重试、超时、降级方案)往往比业务逻辑本身还要长。
LangGraph 将你的智能体建模为一组离散步骤(节点),并组织成图结构。对于一个典型的智能体,它包括调用模型的节点、执行模型返回的任何工具调用的节点,以及你希望围绕该循环封装的确定性逻辑。由于 LangGraph 控制执行,它也负责处理这些步骤中任何一步失败时的情况。
本文介绍了 LangGraph 提供的三种容错原语、它们如何组合使用,以及一旦你开始考虑补偿逻辑,为什么将这些原语放在工作流引擎内部很重要。
这三种原语是:
-
RetryPolicy:针对瞬时错误,自动进行带退避/抖动的重试。
-
TimeoutPolicy:针对单个节点尝试,设置基于挂钟时间或进度的上限。
-
error_handler:一个节点,在重试耗尽后运行,并携带失败上下文。
在 LangGraph 中,你通过向 StateGraph 添加节点和边来定义智能体。这三种原语都通过 add_node 直接附加到节点上,因此你的容错配置就在它保护的逻辑旁边。(如果你想一次性配置默认值,请参见 set_node_defaults。)
从重试开始
瞬时故障是任何非平凡图中最常见的故障类型:LLM 提供商返回 5xx、向量存储连接重置、下游 HTTP 服务暂时不可用。每一种本质上都是“稍后再试一次,很可能就会成功”的错误。
如果没有一流支持,你最终会在每个节点内部编写同样的包装器:
LangGraph 的 RetryPolicy 消除了这个样板代码。它应用于每次节点尝试,支持指数退避、可选的抖动,以及一个可配置的谓词,用于判断哪些异常属于可重试的:
默认的 retry_on 策略故意保守:它重试 ConnectionError、来自 httpx/requests 的 5xx 响应,以及一些通用的瞬时错误类别。
默认情况下,它 不 重试 ValueError、TypeError、RuntimeError 等,这些几乎总是编程错误。
retry_on 规范可以是一组错误类型,或者是一个可调用对象,在运行时检查错误是否符合重试条件。
超时:“瞬时故障”的特殊情况
超时实际上只是“由于挂起时间过长,将该尝试视为瞬时故障”。如果没有明确的超时,一个卡住的 HTTP 调用或冻结的子进程可能会无限期挂起图的运行。
LangGraph 的 TimeoutPolicy 支持两种超时类型:
-
run_timeout 是单个尝试的硬挂钟时间上限。当你根本不想等待超过 N 秒时很有用。
-
idle_timeout 会在每次“进度”信号时重置:通道写入、流式数据块(从 LangChain LLM 模型自动发出)、子任务事件、LangChain 回调事件。长时间运行但主动流式传输的工作不会触发它,但真正挂起的调用会触发。内部,它依赖于每个信号的“心跳”。如果你控制工作并自行发出进度信号,可以切换到
refresh_on="heartbeat"并从节点内部显式调用runtime.heartbeat()。
当超时触发时,节点尝试被取消,并引发 NodeTimeoutError。
错误处理程序:当重试不够时
重试处理“可能在 5 秒内成功”。然而,它们无法处理重试耗尽后需要执行一些逻辑的情况。例如:“我们已经尝试了六次,支付提供商仍然宕机,现在你需要:
- 将订单标记为失败并通知客户,或
- 回滚我们已经提交的部分副作用,或
- 为整个系统发布一个 payment.failed 事件以供反应。”
在重试耗尽后,错误处理程序有很多用例。包括清理、告警、死信写入、降级到更便宜的模型,或只是路由到“我们道歉”的消息。
在 LangGraph 中,现在自然支持这一点(文档:错误处理):
关于这种连接方式,有几点需要注意:
它仅在重试耗尽后触发。 这是使该功能真正有用的特性。如果你想在每个异常上都运行,只需在节点内部编写 try/except 即可。
失败上下文被注入。 处理程序可以使用参数 NodeError 来获取失败节点的名称以及异常(error.node、error.error)。
转换是原子的。 当原始节点失败时,其 ERROR 写入被提交到检查点,并且处理程序任务在同一步骤中被调度为新任务。这一点在某些关键流程中至关重要——一旦进入错误处理程序步骤,你就无法回到常规步骤。如果宿主进程在处理程序中途崩溃,下次恢复运行时将重新调度处理程序,而不是原始失败的节点。
错误处理程序在同一执行周期中运行。* 当节点失败时,错误处理程序会与该步骤中已经在运行的任何其他节点一起立即被调度。它不会等待它们完成,它们也不会等待它。
*在 LangGraph 中,我们将“执行周期”称为“超步”(如果你熟悉运行时的话)。
你可以为每个节点设置默认处理程序。 set_node_defaults 应用于所有未指定自己处理程序的常规节点,但每个节点的 error_handler= 始终优先。
你不能为错误处理程序设置另一个错误处理程序。 这样就不会出现无限递归行为。
综合运用:容错的航班预订
上述三种原语自然组合,但它们的真正威力体现在涉及副作用的工作流中:改变真实世界状态的操作。考虑航班预订:它不是单一操作,而是一系列操作。预订座位、处理付款、出票。每一步都与外部系统通信。任何一步都可能失败。
朴素的方案(只需重试整个流程)很快崩溃。如果座位预订成功但付款或出票失败,预订就会陷入不良状态。你真正需要的是单独重试每一步,如果某一步重试耗尽,则仅撤销已经运行的步骤(以及失败的步骤,因为其状态未知)。
这就是 SAGA 模式,是在无法将所有操作包装在单个数据库事务中的分布式系统中处理故障的标准方法。
以下是它在 LangGraph 中的样子:
这给你带来:
-
按步骤配置的退避重试策略
-
一旦任何步骤的重试耗尽,原子转换为补偿
-
持久状态跟踪哪些步骤实际完成,以便补偿只撤销需要回滚的内容
结语
智能体正在承担更多的自主权,随之而来的是更大的行动能力。它们正在预订航班、提交工单、执行付款、调用内部服务。它们所采取的行动后果越来越严重,且难以逆转。
这提高了对可靠性的要求。在演示中,1% 的瞬时故障率只是一个小麻烦。但在一个包含数十个步骤且具有真实世界后果的生产智能体中,它会迅速累积。
RetryPolicy、TimeoutPolicy 和 error_handler 内置于 LangGraph 中,以便轻松构建对各种错误具有弹性的智能体。你只需为你的用例定义合理的策略,其余的由 LangGraph 智能体运行时处理。
开始使用: 通过官方容错文档配置每个节点的重试、超时和错误处理程序。
致谢
这篇博文由 @quanzhenglong 和 @sydneyrunkle 撰写,最初发表在 LangChain 博客上。
感谢 @huntlovell、@bromann 和 @veryboldbagel 的深思熟虑的审阅。
相似文章
@sydneyrunkle: https://x.com/sydneyrunkle/status/2066928783534289358
这篇博客文章由Sydney Runkle撰写,解释了使用LangChain原语构建可靠LLM代理的循环工程艺术,涵盖了四种循环级别:代理循环、验证循环、事件驱动循环和爬山循环。
代理的重试逻辑会随代理一起消亡
作者分享了将一个具有写入权限的 AI 代理投入生产环境后的经验教训,指出当进程终止时,代理循环内部的重试逻辑会失效。他们主张将有副作用的工具调用视为带有幂等键的持久后台任务。
您的智能体操作超时了。您的代码会重试吗?
技术文章,讨论当智能体操作超时时重试逻辑的重要性,并指出基于智能体的系统中一个常见误区。
学习LangGraph:智能体、黑板与瓶颈之旅
一篇关于LangGraph的教育文章,涵盖智能体架构、黑板模式以及构建智能体系统时的常见瓶颈。
你如何为智能体重试设置预算,同时不掩盖真正的失败?
询问开发人员如何为智能体重试设定预算,以区分瞬时故障和持久故障,以及哪些信号最能决定在生产环境中的智能体是停止还是重试。