培歌行学LangGraph(13):节点执行与容错机制

0 阅读7分钟
  1. 培歌行学LangGraph(1):从 LangChain 到 LangGraph 的思维跃迁]
  2. 培歌行学LangGraph(2):Reducer(归约器)彻底搞懂
  3. 培歌行学LangGraph(3):编译、执行与可视化
  4. 培歌行学LangGraph(4):一文搞懂图的状态(State)管理
  5. 培歌行学LangGraph(5):状态管理与graph.invoke入参深度解析
  6. 培歌行学LangGraph(6):Multi Schema多状态管理详解
  7. 培歌行学LangGraph(7):预定义状态MessagesState与AgentState
  8. 培歌行学LangGraph(8):控制流详解
  9. 培歌行学LangGraph(9):控制流详解:defer延迟节点——让收尾工作自动排到最后
  10. 培歌行学LangGraph(10):多分支汇聚Fan-in——多个分支如何汇聚到一起?
  11. 培歌行学LangGraph(11):用循环结构实现 ReAct Agent
  12. 培歌行学LangGraph(12):递归限制——别让你的图无限跑下去

一、为什么需要容错?

在真实生产环境中,节点执行可能遇到各种问题:

问题举例后果
临时故障网络抖动、API 限流、模型服务不稳定节点执行失败
超时LLM 响应慢、工具调用卡住图被长时间阻塞
异常输入格式错误、工具返回非法数据整个图崩溃
重复计算相同输入反复触发同一节点浪费时间和费用

LangGraph 提供了四种机制来应对这些问题:

机制解决什么问题类型
重试(Retry)临时故障容错
超时(Timeout)节点卡住容错
错误处理(Error Handling)异常恢复容错
缓存(Cache)重复计算优化

二、重试机制(Retry)

2.1 什么是重试?

重试就是在节点执行失败后,自动重新执行该节点。

适用场景:

  • 网络抖动导致的 API 调用失败
  • 模型服务偶发超时
  • 数据库连接短暂断开

2.2 基本用法

add_node时传入 retry_policy参数:

from langgraph.types import RetryPolicy

builder.add_node(
    "node_a",
    node_a,
    retry_policy=RetryPolicy(max_attempts=3)
)

执行流程:

第 1 次:首次执行(失败)
第 2 次:第 1 次重试(失败)
第 3 次:第 2 次重试(失败)
→ 重试耗尽,抛出异常

注意:max_attempts=3包含首次执行,不是首次失败后再重试 3 次。

2.3 重试策略配置详解

RetryPolicy(
    max_attempts=3,          # 最大尝试次数(包含首次)
    initial_interval=0.5,    # 首次重试前等待 0.5 秒
    backoff_factor=2.0,      # 每次重试等待时间翻倍
    max_interval=128.0,      # 最大等待时间不超过 128 秒
    jitter=True,             # 添加随机抖动,防止同时重试
    retry_on=HTTPError,      # 只有 HTTPError 才重试
)
参数说明默认值
max_attempts最大尝试次数(含首次)3
initial_interval首次重试前等待时间(秒)0.5
backoff_factor等待时间增长倍数2.0
max_interval最大等待时间(秒)128.0
jitter是否添加随机抖动True
retry_on哪些异常需要重试见下文

2.4 指数退避示例

RetryPolicy(
    initial_interval=1,
    backoff_factor=2,
    max_interval=5
)

等待时间:

1s → 2s → 4s → 5s → 5s → ...

2.5 控制哪些异常需要重试

写法一:指定单个异常

RetryPolicy(retry_on=HTTPError)

写法二:指定多个异常

RetryPolicy(retry_on=(HTTPError, ConnectionError))

写法三:自定义判断函数

def should_retry(exc: Exception) -> bool:
    return isinstance(exc, HTTPError)

RetryPolicy(retry_on=should_retry)

2.6 默认重试条件

LangGraph 的默认策略:

  • 5xx 状态码(服务端错误)→ 重试
  • 4xx 状态码(客户端错误)→ 不重试
  • 连接错误​ → 重试
  • ValueError、TypeError 等逻辑错误​ → 不重试

三、超时控制(Timeout)

3.1 使用要求

langgraph >= 1.2

当前课程环境为 langgraph==1.1.2不支持此特性,仅做概念说明。

3.2 核心思想

限制单个节点的最大执行时间,超时后视为失败,触发重试或错误处理。

from langgraph.types import TimeoutPolicy

builder.add_node(
    "call_model",
    call_model,
    timeout=TimeoutPolicy(run_timeout=60)  # 最多等 60 秒
)

3.3 超时 + 重试组合

builder.add_node(
    "api_call",
    call_llm,
    timeout=TimeoutPolicy(run_timeout=10),
    retry_policy=RetryPolicy(max_attempts=3)
)

执行流程:

  1. 调用 LLM,等待最多 10 秒
  2. 超时 → 触发重试
  3. 等待 1 秒后重试
  4. 再次超时 → 等待 2 秒后重试
  5. 第三次超时 → 抛出异常

3.4 注意事项

节点级超时仅适用于异步节点async def)。同步节点一旦开始执行,Python 缺乏安全的外部终止机制,应在节点内部使用具体库的超时参数。


四、错误处理(Error Handling)

4.1 使用要求

langgraph >= 1.2

当前课程环境为 langgraph==1.1.2不支持此特性,仅做概念说明。

4.2 核心思想

当重试次数耗尽后,执行特定的错误处理逻辑,而不是直接抛出异常。

def handle_api_error(state, error):
    """重试耗尽后的兜底逻辑"""
    return {"result": f"查询失败,使用缓存数据。错误:{str(error)}"}

builder.add_node(
    "call_api",
    call_api,
    retry_policy=RetryPolicy(max_attempts=3),
    error_handler=handle_api_error
)

4.3 执行顺序

节点执行失败
    ↓
是否满足 retry_policy?
    ├── 是 → 重试(直到次数耗尽)
    └── 否 → 直接下一步
    ↓
进入 error_handler
    ↓
执行兜底恢复逻辑

4.4 适用场景

  • API 调用失败后返回兜底结果
  • 远程服务失败后切换到备用服务
  • 多步骤业务流程中执行补偿逻辑

五、节点缓存(Cache)

5.1 什么是节点缓存?

将节点的历史运行结果保存下来,后续收到相同输入时,直接返回缓存结果,不再重复执行。

5.2 适用条件

  1. 确定性:相同输入 → 相同输出
  2. 成本高:大模型调用、API 请求、复杂计算
  3. 重复出现:相同输入会多次出现
  4. 输入可序列化:str、int、dict 等简单类型

5.3 基本用法

第一步:配置缓存策略

from langgraph.types import CachePolicy

builder.add_node(
    "node_a",
    node_a,
    cache_policy=CachePolicy(ttl=10)  # 缓存 10 秒
)

第二步:启用缓存后端

from langgraph.cache.memory import InMemoryCache

graph = builder.compile(cache=InMemoryCache())

5.4 完整示例

import time
from langgraph.cache.memory import InMemoryCache
from langgraph.types import CachePolicy

def node_a(state):
    logger.info(f"node_a 被调用, user: {state['user']}")
    time.sleep(3)  # 模拟耗时操作
    return {"invoke_counts": 1}

builder.add_node("node_a", node_a, cache_policy=CachePolicy(ttl=10))
graph = builder.compile(cache=InMemoryCache())

# 第一次调用:执行节点,耗时 3 秒
graph.invoke({"user": "小明"})

# 第二次调用(相同输入):命中缓存,瞬间返回
graph.invoke({"user": "小明"})  # 不会打印 "node_a 被调用"

# 不同输入:不命中缓存,重新执行
graph.invoke({"user": "小花"})  # 会打印 "node_a 被调用"

5.5 缓存策略配置

参数说明默认值
key_func根据输入生成缓存 Key 的函数基于 pickle 序列化
ttl缓存存活时间(秒)None(永不过期)

5.6 自定义缓存 Key

def custom_key(user: str, **kwargs) -> str:
    return f"user_{user}"

CachePolicy(key_func=custom_key, ttl=60)

六、四种机制的执行顺序

当一个节点开始执行时,完整的容错流程如下:

1. 节点开始执行
    │
2. 检查缓存是否有结果
    ├── 有 → 直接返回缓存结果
    └── 无 → 继续执行
         │
3. 执行节点
    ├── 成功 → 存入缓存,返回结果
    └── 失败(超时/异常)
         │
4. 判断是否重试
    ├── 否 → 进入错误处理或抛出异常
    └── 是 → 等待后重试
         │
         └── 回到步骤 3

七、面试题

面试题1:max_attempts=3是什么意思?总共执行几次?

max_attempts=3表示最多尝试 3 次,包括首次执行。也就是首次执行 + 2 次重试。如果 3 次都失败,就进入错误处理或抛出异常。

面试题2:backoff_factorjitter分别有什么用?

  • backoff_factor:控制重试间隔的增长倍数,实现指数退避,避免短时间内频繁重试
  • jitter:为重试间隔添加随机抖动,防止大量任务同时重试导致服务被打爆

面试题3:默认情况下,哪些异常会触发重试?哪些不会?

  • 会重试ConnectionError、5xx 状态码的 HTTP 错误
  • 不会重试ValueErrorTypeErrorArithmeticErrorImportErrorSyntaxErrorRuntimeError等逻辑错误

原因是:逻辑错误重试也没有意义,需要修复代码。

面试题4:节点缓存适合哪些场景?不适合哪些场景?

适合

  • 幂等的查询操作(天气查询、百科搜索)
  • 相同输入的 LLM 调用(模板化生成)
  • 昂贵的计算(数据分析、报告生成)

不适合

  • 有副作用的操作(发送邮件、扣费、下单)
  • 结果随时间变化的操作(实时股价、当前时间)
  • 输入差异极大的操作(几乎不会命中缓存)

面试题5:配置了 cache_policy但缓存不生效,可能是什么原因?

:最常见的原因是编译图时没有传入 cache=参数。

# ❌ 错误:只配了策略,没配后端
graph = builder.compile()

# ✅ 正确:策略和后端都要配
graph = builder.compile(cache=InMemoryCache())

CachePolicy是"声明",InMemoryCache是"存储",两者缺一不可。


容错不是让代码不出错,而是让代码出错时也能体面地活着。 ​ 重试、超时、错误处理、缓存,这四个机制配合使用,能让你的 LangGraph Agent 在生产环境中稳定运行。