《LangGraph开发AI Agent实践》1~6章试读_langgraph开发ai agent实践在线阅读-CSDN博客
LangGraph的核心优势在于通过图结构组织多个节点(如LLM调用、工具调用、条件判断等),支持动态流程控制和可扩展的对话逻辑。然而,在长时间运行或生产环境中,LangGraph应用可能面临异常中断(如网络故障、超时、LLM服务异常等),导致状态丢失、流程中断。为此,异常恢复(Exception Recovery)与断点续跑(Checkpointing & Resume)成为其关键的可靠性保障技术。
7.3.1 异常恢复与断点续跑技术详解
1. 异常恢复
(1)目标:在图执行过程中发生异常时,捕获错误并采取适当策略(如重试、降级、记录日志、通知等),避免整个流程崩溃。
(2)实现方式:
- 节点级异常处理:在每个节点(node)函数中使用try-except捕获异常,返回一个代表“错误状态”的图状态,供后续节点决策。
- 全局异常钩子:通过自定义executor,或在graph.invoke()/graph.stream()调用外层包装异常处理逻辑。
- 重试机制:结合tenacity等重试库,在节点内部对特定操作(如API调用)进行自动重试。
- 错误传播控制:设计图状态中包含error字段,使后续节点能根据错误类型决定是否跳过、回退或终止。
2. 断点续跑
(1)目标:将图执行过程中的中间状态持久化,以便在进程中断后从最近的检查点恢复执行,避免从头开始。
(2)核心机制:LangGraph内置检查点(Checkpoint)功能,通过Checkpointer接口支持状态持久化。
(3)关键组件:
- Checkpointer:一个实现了put/get/list等方法的存储后端(如内存、SQLite、Redis、PostgreSQL)。
- thread_id:每个执行流(thread)通过唯一ID标识,用于关联其检查点。
- Resume能力:调用graph.invoke(..., thread_id=...)或 graph.stream(..., thread_id=...)时,若存在该thread的检查点,则自动从最新状态继续执行。
(4)支持的存储后端(官方提供):
- MemorySaver(内存,用于测试)。
- SQLiteSaver。
- PostgresSaver。
- 自定义实现(需符合Checkpointer协议)。
7.3.2 实战案例:带异常恢复的数据分析工作流
【示例7.3】实现一个“数据读取→数据清洗→分析报告生成→可视化”的数据分析工作流,模拟节点异常后,从断点恢复执行(以通义千问Qwen3生成分析报告为例)。
#Data_analysis_workflow_with_exception_recovery.py
# -*- coding: utf-8 -*-
import os
import json
import random
from dotenv import load_dotenv
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from typing import TypedDict, Optional
# 加载环境变量
load_dotenv()
# 通义千问 API 配置(兼容 OpenAI 格式)
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY")
DASHSCOPE_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
# 1. 定义数据分析状态结构
class DataAnalysisState(TypedDict):
data: Optional[str] # 原始数据
cleaned_data: Optional[str] # 清洗后数据
report: Optional[str] # 分析报告
error: Optional[str] # 异常信息
progress: str # 进度(read→clean→report→visualize→done)
visualize_desc: Optional[str] # 可视化描述
# 2. 初始化LLM(使用OpenAI兼容接口访问通义千问)
llm = ChatOpenAI(
model="qwen-plus",
api_key=DASHSCOPE_API_KEY,
base_url=DASHSCOPE_BASE_URL,
temperature=0.3
)
# 3. 定义节点函数(模拟异常)
def read_data(state: DataAnalysisState) -> dict:
"""节点1:读取数据(模拟偶发异常)"""
# 模拟1/2概率抛出异常
if random.random() < 0.5:
raise RuntimeError("数据读取失败:文件不存在")
# 正常执行逻辑
data = """用户行为数据:
日期,访问量,转化率,客单价
2025-12-01,1000,5.2%,199
2025-12-02,1200,5.5%,210
2025-12-03,1100,5.3%,205
"""
return {"data": data, "progress": "data_read", "error": None}
def clean_data(state: DataAnalysisState) -> dict:
"""节点2:数据清洗"""
if not state["data"]:
raise ValueError("无原始数据,清洗失败")
# 模拟数据清洗(去除空行、格式化)
cleaned_data = "\n".join([line.strip() for line in state["data"].split("\n") if line.strip()])
return {"cleaned_data": cleaned_data, "progress": "data_cleaned", "error": None}
def generate_report(state: DataAnalysisState) -> dict:
"""节点3:生成分析报告"""
prompt = PromptTemplate(
template="""请基于清洗后的用户行为数据生成分析报告:
数据:{cleaned_data}
报告要求:包含趋势分析、关键指标解读,字数200字左右。
报告:""",
input_variables=["cleaned_data"]
)
report_chain = prompt | llm | StrOutputParser()
report = report_chain.invoke({"cleaned_data": state["cleaned_data"]})
return {"report": report, "progress": "report_generated", "error": None}
def visualize_data(state: DataAnalysisState) -> dict:
"""节点4:生成可视化描述(模拟)"""
visualize_desc = f"可视化图表:访问量趋势折线图、转化率柱状图、客单价饼图"
return {"progress": "done", "error": None, "visualize_desc": visualize_desc}
# 修复后的读取数据函数(独立定义)
def fixed_read_data(state):
"""修复后的读取数据函数(确保100%成功)"""
data = """用户行为数据:
日期,访问量,转化率,客单价
2025-12-01,1000,5.2%,199
2025-12-02,1200,5.5%,210
2025-12-03,1100,5.3%,205
"""
return {"data": data, "progress": "data_read", "error": None}
# 自定义本地持久化工具函数
def save_state_to_file(state: dict, thread_id: str, filepath: str = "./analysis_checkpoints"):
"""将状态保存到本地文件"""
os.makedirs(filepath, exist_ok=True)
with open(f"{filepath}/{thread_id}.json", "w", encoding="utf-8") as f:
json.dump(state, f, ensure_ascii=False, indent=2)
def load_state_from_file(thread_id: str, filepath: str = "./analysis_checkpoints") -> Optional[dict]:
"""从本地文件加载状态"""
try:
with open(f"{filepath}/{thread_id}.json", "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return None
# 4. 构建状态机(支持动态替换节点函数)
def build_analysis_graph(checkpointer, read_data_func=read_data):
"""
构建状态机
:param checkpointer: 检查点实例
:param read_data_func: 读取数据的函数(支持动态替换)
"""
graph_builder = StateGraph(DataAnalysisState)
# 添加节点(使用传入的函数)
graph_builder.add_node("read_data", read_data_func)
graph_builder.add_node("clean_data", clean_data)
graph_builder.add_node("generate_report", generate_report)
graph_builder.add_node("visualize_data", visualize_data)
# 设置入口和边
graph_builder.set_entry_point("read_data")
graph_builder.add_edge("read_data", "clean_data")
graph_builder.add_edge("clean_data", "generate_report")
graph_builder.add_edge("generate_report", "visualize_data")
graph_builder.add_edge("visualize_data", END)
# 编译图
return graph_builder.compile(checkpointer=checkpointer)
# 5. 异常恢复与断点续跑逻辑
def run_with_recovery():
# 初始化内存检查点(1.x版本稳定支持)
checkpointer = MemorySaver()
# 定义唯一任务ID
thread_id = "analysis_task_001"
config = {"configurable": {"thread_id": thread_id}}
# 尝试从本地加载历史状态
saved_state = load_state_from_file(thread_id)
if saved_state:
initial_state = saved_state
print("=== 加载历史状态 ===")
else:
# 初始状态
initial_state = {
"progress": "init",
"error": None,
"data": None,
"cleaned_data": None,
"report": None,
"visualize_desc": None
}
# ========== 第一次运行(使用原始函数,模拟异常) ==========
print("=== 第一次运行(模拟异常) ===")
# 构建包含原始函数的图
graph = build_analysis_graph(checkpointer, read_data_func=read_data)
current_state = initial_state
try:
# 执行图
current_state = graph.invoke(initial_state, config=config)
save_state_to_file(current_state, thread_id)
print("第一次运行成功(未触发异常)")
except Exception as e:
print(f"执行异常:{e}")
# 记录异常状态并保存
current_state["error"] = str(e)
current_state["progress"] = "failed"
save_state_to_file(current_state, thread_id)
# ========== 断点续跑(使用修复后的函数) ==========
print("\n=== 断点续跑(恢复执行) ===")
# 构建包含修复后函数的新图(关键:重新编译图)
recovery_graph = build_analysis_graph(checkpointer, read_data_func=fixed_read_data)
# 从保存的状态恢复执行
recovery_state = load_state_from_file(thread_id)
if recovery_state and recovery_state["progress"] == "failed":
# 重置错误状态,重新执行
recovery_state["error"] = None
recovery_state["progress"] = "init"
# 使用修复后的图执行
final_state = recovery_graph.invoke(recovery_state, config=config)
# 保存最终状态
save_state_to_file(final_state, thread_id)
else:
final_state = recovery_state or current_state
# 输出结果
print("=== 断点续跑执行结果 ===")
print(f"进度:{final_state['progress']}")
print(f"清洗后数据:{final_state['cleaned_data']}")
print(f"分析报告:{final_state['report']}")
print(f"可视化描述:{final_state['visualize_desc']}")
# 运行示例
if __name__ == "__main__":
run_with_recovery()
运行输出:
=== 加载历史状态 ===
=== 第一次运行(模拟异常) ===
执行异常:数据读取失败:文件不存在
=== 断点续跑(恢复执行) ===
=== 断点续跑执行结果 ===
进度:done
清洗后数据:用户行为数据:
日期,访问量,转化率,客单价
2025-12-01,1000,5.2%,199
2025-12-02,1200,5.5%,210
2025-12-03,1100,5.3%,205
分析报告:2025年12月上旬用户行为数据显示整体呈积极趋势。访问量从1000稳步增长至1200后略有回落至1100,保持高位运行;转化率由5.2%持续提升至5.5%,表明营销策略或页面优化效果显著,用户购买意愿增强;客单价同步上升,从199元增至210元,反映出用户消费能力提升或商品组合优化成功。综合来看,关键指标均呈向好趋势,尤其转化率与客单价双增长,显著提升整体营收潜力。建议继续保持用户体验优化,并针对高价值用户精准投放,以进一步放大增长效应。后续需关注访问量波动原因,确保流量稳定性。
可视化描述:可视化图表:访问量趋势折线图、转化率柱状图、客单价饼图

7.3.3 案例代码解析
该案例代码是一个基于LangGraph实现的、带有异常处理和断点续跑能力的数据分析工作流,核心目标是模拟数据读取偶发异常、捕获异常状态、修复问题后从断点恢复执行,最终完成数据清洗、分析报告生成和可视化描述输出。整体架构分为6个核心模块,以下是分层解析。
1. 环境与依赖配置层
# 核心依赖导入 + 通义千问兼容配置
load_dotenv() # 加载.env文件中的环境变量
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY")
DASHSCOPE_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
llm = ChatOpenAI(
model="qwen-plus",
api_key=DASHSCOPE_API_KEY,
base_url=DASHSCOPE_BASE_URL,
temperature=0.3
)
- 核心作用:解决LLM适配问题,因ChatDashScope导入路径变更,改用ChatOpenAI+通义千问兼容接口,复用OpenAI生态代码的同时实现对千问模型的调用。
- 关键配置:base_url指向阿里云通义千问的OpenAI兼容接口,保证LLM调用逻辑无须大幅修改。
2. 状态结构定义层
class DataAnalysisState(TypedDict):
data: Optional[str] # 原始数据
cleaned_data: Optional[str] # 清洗后数据
report: Optional[str] # 分析报告
error: Optional[str] # 异常信息
progress: str # 流程进度标识
visualize_desc: Optional[str] # 可视化描述
- 核心作用:通过TypedDict强类型定义工作流的状态载体,明确所有可能的状态字段,避免运行时因键缺失报错。
- 设计思路:状态是LangGraph工作流的核心,所有节点的输入/输出都围绕该状态结构。progress字段作为流程节点的“进度标记”,error字段专门存储异常信息,为断点续跑提供判断依据。
3. 核心业务节点函数层
分为“异常模拟函数”和“正常业务函数”两类。
(1)异常模拟函数:read_data(50%概率抛出文件不存在异常)、fixed_read_data(修复后100%成功读取数据)。
(2)核心逻辑:模拟生产环境中“偶发的数据源异常”,fixed_read_data作为修复后的替代函数,保证续跑时能跳过异常。
(3)正常业务函数:
- clean_data:清洗原始数据(去除空行、格式化)。
- generate_report:通过PromptTemplate+LLM链生成分析报告,输出解析为字符串。
- visualize_data:模拟可视化生成,返回图表描述(可扩展为真实绘图逻辑)。
设计思路:每个节点函数接收state状态字典,返回更新后的状态片段(仅修改自身负责的字段),符合LangGraph“节点只处理局部状态,全局状态自动聚合”的设计原则。
4. 本地状态持久化工具层
def save_state_to_file/load_state_from_file:
# 保存/加载状态到本地JSON文件
- 核心作用:弥补MemorySaver(内存检查点)重启后状态丢失的问题,实现状态的持久化存储。
- 关键逻辑:按thread_id(任务唯一标识)分文件存储,保证多任务状态不冲突,同时处理文件不存在的异常(加载时返回None)。
5. 动态图构建层
def build_analysis_graph(checkpointer, read_data_func=read_data):
graph_builder = StateGraph(DataAnalysisState)
graph_builder.add_node("read_data", read_data_func) # 动态传入函数
# 节点关联 + 编译
return graph_builder.compile(checkpointer=checkpointer)
- 核心突破:解决LangGraph“编译后函数引用固化”的问题—将read_data_func作为参数传入,支持第一次运行使用异常函数、续跑时使用修复函数。
- 图结构设计:线性节点关联(read→clean→report→visualize→END),符合数据分析的自然流程,入口节点设为read_data,保证流程从数据读取开始。
6. 断点续跑执行层(核心逻辑)
def run_with_recovery():
# 1. 初始化检查点 + 加载历史状态
checkpointer = MemorySaver()
saved_state = load_state_from_file(thread_id)
# 2. 第一次运行(用异常函数)
graph = build_analysis_graph(checkpointer, read_data)
try:
current_state = graph.invoke(initial_state, config)
except Exception as e:
current_state["error"] = str(e) # 记录异常
save_state_to_file(current_state, thread_id) # 保存错误状态
# 3. 断点续跑(用修复函数)
recovery_graph = build_analysis_graph(checkpointer, fixed_read_data)
recovery_state["error"] = None # 重置错误状态
final_state = recovery_graph.invoke(recovery_state, config) # 重新执行
1)核心流程
(1)第一次运行:用带异常的read_data构建图,触发异常后保存错误状态。
(2)续跑准备:用修复后的fixed_read_data重新构建图,此步骤是关键步骤。
(3)续跑执行:重置错误状态,从保存的失败状态开始执行,完成整个流程。
2)关键设计
(1)config中的thread_id是检查点的“任务标识”,保证续跑时能匹配到对应任务的状态。
(2)两次运行分别构建不同的图(异常函数与修复函数),解决LangGraph编译后函数无法修改的问题。
该代码是LangGraph实现“有状态工作流+异常恢复”的典型示例,可直接作为生产级智能体工作流的基础模板,扩展后可适配真实的数据分析、任务调度等场景。

&spm=1001.2101.3001.5002&articleId=163796099&d=1&t=3&u=b69adfeaf0a04280bbb3cea01920d624)
321

被折叠的 条评论
为什么被折叠?



