- 培歌行学LangGraph(1):从 LangChain 到 LangGraph 的思维跃迁]
- 培歌行学LangGraph(2):Reducer(归约器)彻底搞懂
- 培歌行学LangGraph(3):编译、执行与可视化
- 培歌行学LangGraph(4):一文搞懂图的状态(State)管理
- 培歌行学LangGraph(5):状态管理与
graph.invoke入参深度解析 - 培歌行学LangGraph(6):Multi Schema多状态管理详解
- 培歌行学LangGraph(7):预定义状态MessagesState与AgentState
- 培歌行学LangGraph(8):控制流详解
- 培歌行学LangGraph(9):控制流详解:defer延迟节点——让收尾工作自动排到最后
- 培歌行学LangGraph(10):多分支汇聚Fan-in——多个分支如何汇聚到一起?
- 培歌行学LangGraph(11):用循环结构实现 ReAct Agent
- 培歌行学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)
)
执行流程:
- 调用 LLM,等待最多 10 秒
- 超时 → 触发重试
- 等待 1 秒后重试
- 再次超时 → 等待 2 秒后重试
- 第三次超时 → 抛出异常
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 适用条件
- 确定性:相同输入 → 相同输出
- 成本高:大模型调用、API 请求、复杂计算
- 重复出现:相同输入会多次出现
- 输入可序列化: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_factor和 jitter分别有什么用?
答:
backoff_factor:控制重试间隔的增长倍数,实现指数退避,避免短时间内频繁重试jitter:为重试间隔添加随机抖动,防止大量任务同时重试导致服务被打爆
面试题3:默认情况下,哪些异常会触发重试?哪些不会?
答:
- 会重试:
ConnectionError、5xx 状态码的 HTTP 错误 - 不会重试:
ValueError、TypeError、ArithmeticError、ImportError、SyntaxError、RuntimeError等逻辑错误
原因是:逻辑错误重试也没有意义,需要修复代码。
面试题4:节点缓存适合哪些场景?不适合哪些场景?
答:
适合:
- 幂等的查询操作(天气查询、百科搜索)
- 相同输入的 LLM 调用(模板化生成)
- 昂贵的计算(数据分析、报告生成)
不适合:
- 有副作用的操作(发送邮件、扣费、下单)
- 结果随时间变化的操作(实时股价、当前时间)
- 输入差异极大的操作(几乎不会命中缓存)
面试题5:配置了 cache_policy但缓存不生效,可能是什么原因?
答:最常见的原因是编译图时没有传入 cache=参数。
# ❌ 错误:只配了策略,没配后端
graph = builder.compile()
# ✅ 正确:策略和后端都要配
graph = builder.compile(cache=InMemoryCache())
CachePolicy是"声明",InMemoryCache是"存储",两者缺一不可。
容错不是让代码不出错,而是让代码出错时也能体面地活着。 重试、超时、错误处理、缓存,这四个机制配合使用,能让你的 LangGraph Agent 在生产环境中稳定运行。