导语:在前面的章节中,我们已经掌握了 LangGraph 的基础用法:如何定义节点、如何连接边、如何构建简单的 Agent 工作流。但在实际的生产环境中,我们往往需要处理更复杂的场景:根据运行时的状态动态决定下一步走向、构建可以循环执行的工作流、实现多 Agent 协作的复杂系统。本章是直播课程的精华总结,我们将深入探讨 LangGraph 的高级特性:动态路由(Dynamic Routing)和循环图(Cyclic Graphs),通过实际案例展示如何用这些技术构建真正强大、灵活的 Agent 系统。
目录
-
从静态到动态:为什么需要动态路由?
- 静态图的局限性
- 动态路由的核心价值
- 实际应用场景
-
动态路由实战:构建一个智能任务分发系统
- 场景:根据任务类型动态选择处理节点
- 实现:使用条件边实现动态路由
- 进阶:基于 LLM 决策的智能路由
-
循环图的力量:让 Agent 学会"迭代优化"
- 什么是循环图?
- 为什么需要循环?
- 循环终止条件的设计
-
实战案例一:构建一个自我改进的代码审查 Agent
- 需求:代码审查 → 发现问题 → 生成修复建议 → 重新审查
- 实现:使用循环图实现迭代优化
- 优化:添加最大迭代次数和早期终止条件
-
实战案例二:多 Agent 协作的复杂工作流
- 场景:研究助手 + 代码生成器 + 测试工程师的协作
- 实现:使用动态路由和循环图构建多 Agent 系统
- 状态管理:如何在多个 Agent 之间共享状态
-
高级技巧:状态检查点与错误恢复
- 保存和恢复图执行状态
- 错误处理和重试机制
- 优雅降级策略
-
性能优化:并行执行与资源管理
- 并行节点的设计
- 资源池管理
- 避免死锁和资源竞争
-
总结:从基础到高级的完整进阶路径
1. 从静态到动态:为什么需要动态路由?
1.1 静态图的局限性
在基础教程中,我们构建的图通常是静态的:节点和边的连接关系在编译时就确定了。例如:
from langgraph.graph import StateGraph
workflow = StateGraph(AgentState)
# 定义节点
workflow.add_node("agent", agent_node)
workflow.add_node("tools", tools_node)
# 定义边(静态)
workflow.add_edge("agent", "tools")
workflow.add_edge("tools", "agent")
workflow.set_entry_point("agent")
这种静态图适合简单的线性工作流,但在实际应用中,我们往往需要根据运行时的状态动态决定下一步。
1.2 动态路由的核心价值
**动态路由(Dynamic Routing)**允许我们根据图执行过程中的状态,动态决定下一个要执行的节点。这带来了几个关键优势:
- 灵活性:可以根据不同的输入或中间结果,选择不同的处理路径
- 效率:避免执行不必要的节点
- 智能性:让 Agent 能够根据情况做出决策
1.3 实际应用场景
- 任务分类:根据用户输入,将任务路由到不同的专业 Agent
- 条件分支:根据工具执行结果,决定是否需要进一步处理
- 错误恢复:根据错误类型,选择不同的恢复策略
- 多轮对话:根据对话历史,决定是否需要调用工具或直接回复
2. 动态路由实战:构建一个智能任务分发系统
2.1 场景:根据任务类型动态选择处理节点
假设我们要构建一个智能助手,它能够处理三种不同类型的任务:
- 代码相关:路由到代码生成/审查节点
- 研究相关:路由到研究助手节点
- 通用问题:路由到通用问答节点
2.2 实现:使用条件边实现动态路由
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, END
from langchain_core.messages import HumanMessage, AIMessage
class TaskState(TypedDict):
messages: list
task_type: str
result: str
def classify_task(state: TaskState) -> TaskState:
"""分类任务类型"""
last_message = state["messages"][-1].content
# 简单的关键词匹配(实际应用中可以使用 LLM)
if any(keyword in last_message.lower() for keyword in ["代码", "编程", "函数", "class"]):
task_type = "code"
elif any(keyword in last_message.lower() for keyword in ["研究", "论文", "资料", "分析"]):
task_type = "research"
else:
task_type = "general"
return {
**state,
"task_type": task_type
}
def code_handler(state: TaskState) -> TaskState:
"""处理代码相关任务"""
# 这里可以调用代码生成 Agent
result = f"代码处理结果:{state['messages'][-1].content}"
return {
**state,
"result": result
}
def research_handler(state: TaskState) -> TaskState:
"""处理研究相关任务"""
# 这里可以调用研究助手 Agent
result = f"研究处理结果:{state['messages'][-1].content}"
return {
**state,
"result": result
}
def general_handler(state: TaskState) -> TaskState:
"""处理通用问题"""
# 这里可以调用通用问答 Agent
result = f"通用回答:{state['messages'][-1].content}"
return {
**state,
"result": result
}
def route_task(state: TaskState) -> Literal["code", "research", "general"]:
"""路由函数:根据任务类型决定下一步"""
return state["task_type"]
# 构建图
workflow = StateGraph(TaskState)
# 添加节点
workflow.add_node("classify", classify_task)
workflow.add_node("code", code_handler)
workflow.add_node("research", research_handler)
workflow.add_node("general", general_handler)
# 设置入口
workflow.set_entry_point("classify")
# 添加条件边:根据任务类型动态路由
workflow.add_conditional_edges(
"classify",
route_task,
{
"code": "code",
"research": "research",
"general": "general"
}
)
# 所有处理节点都连接到结束
workflow.add_edge("code", END)
workflow.add_edge("research", END)
workflow.add_edge("general", END)
# 编译图
app = workflow.compile()
2.3 进阶:基于 LLM 决策的智能路由
上面的例子使用了简单的关键词匹配。在实际应用中,我们可以使用 LLM 来做出更智能的决策:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4", temperature=0)
def smart_route_task(state: TaskState) -> Literal["code", "research", "general"]:
"""使用 LLM 智能路由任务"""
last_message = state["messages"][-1].content
prompt = f"""
分析以下用户请求,判断它属于哪种类型:
1. code: 代码相关(编程、代码生成、代码审查等)
2. research: 研究相关(资料查找、论文分析、深度研究等)
3. general: 通用问题(日常问答、闲聊等)
用户请求:{last_message}
只返回类型名称(code、research 或 general),不要返回其他内容。
"""
response = llm.invoke(prompt)
task_type = response.content.strip().lower()
# 验证返回的类型
if task_type not in ["code", "research", "general"]:
task_type = "general" # 默认路由到通用处理
return task_type
3. 循环图的力量:让 Agent 学会"迭代优化"
3.1 什么是循环图?
循环图(Cyclic Graph)是指图中存在循环路径,允许节点可以多次执行。这与静态的线性流程不同,循环图允许 Agent 进行迭代优化。
3.2 为什么需要循环?
许多任务需要迭代优化:
- 代码审查:发现问题 → 修复 → 重新审查 → 可能还有问题 → 继续修复
- 内容生成:生成初稿 → 评估质量 → 如果不满意 → 改进 → 重新评估
- 问题解决:尝试方案 A → 失败 → 尝试方案 B → 成功或继续尝试
- 多轮对话:用户提问 → Agent 回答 → 用户追问 → Agent 继续回答
3.3 循环终止条件的设计
循环图必须要有终止条件,否则会无限循环。常见的终止条件:
- 最大迭代次数:限制循环次数
- 目标达成:达到预期结果后退出
- 用户确认:需要用户确认是否继续
- 超时:超过时间限制后退出
4. 实战案例一:构建一个自我改进的代码审查 Agent
4.1 需求
我们要构建一个代码审查 Agent,它能够:
- 审查代码,发现问题
- 生成修复建议
- 应用修复
- 重新审查
- 如果还有问题,继续循环;如果没有问题,结束
4.2 实现:使用循环图实现迭代优化
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, END
from langchain_core.messages import HumanMessage
class CodeReviewState(TypedDict):
code: str
issues: list
fixes: list
iteration: int
max_iterations: int
is_complete: bool
def review_code(state: CodeReviewState) -> CodeReviewState:
"""审查代码,发现问题"""
code = state["code"]
iteration = state["iteration"]
# 这里可以调用代码审查工具或 LLM
# 简化示例
issues = []
if "TODO" in code:
issues.append("发现 TODO 注释,建议完成或删除")
if "print(" in code:
issues.append("发现 print 语句,建议使用日志系统")
return {
**state,
"issues": issues,
"iteration": iteration + 1
}
def generate_fixes(state: CodeReviewState) -> CodeReviewState:
"""生成修复建议"""
issues = state["issues"]
code = state["code"]
# 这里可以调用 LLM 生成修复建议
fixes = []
for issue in issues:
if "TODO" in issue:
fixes.append("删除或完成 TODO 注释")
if "print" in issue:
fixes.append("将 print 替换为 logger.info")
return {
**state,
"fixes": fixes
}
def apply_fixes(state: CodeReviewState) -> CodeReviewState:
"""应用修复"""
code = state["code"]
fixes = state["fixes"]
# 简化示例:实际应用中可以使用代码修改工具
fixed_code = code
for fix in fixes:
if "删除" in fix or "完成" in fix:
# 移除 TODO 注释(简化处理)
fixed_code = fixed_code.replace("# TODO", "# DONE")
if "logger" in fix:
# 替换 print 为 logger(简化处理)
fixed_code = fixed_code.replace("print(", "logger.info(")
return {
**state,
"code": fixed_code,
"is_complete": len(state["issues"]) == 0
}
def should_continue(state: CodeReviewState) -> Literal["review", "end"]:
"""决定是否继续循环"""
# 条件 1:达到最大迭代次数
if state["iteration"] >= state["max_iterations"]:
return "end"
# 条件 2:没有发现问题,审查完成
if state["is_complete"]:
return "end"
# 条件 3:还有问题,继续审查
return "review"
# 构建循环图
workflow = StateGraph(CodeReviewState)
# 添加节点
workflow.add_node("review", review_code)
workflow.add_node("generate_fixes", generate_fixes)
workflow.add_node("apply_fixes", apply_fixes)
# 设置入口
workflow.set_entry_point("review")
# 添加边:形成循环
workflow.add_edge("review", "generate_fixes")
workflow.add_edge("generate_fixes", "apply_fixes")
# 添加条件边:决定是否继续循环
workflow.add_conditional_edges(
"apply_fixes",
should_continue,
{
"review": "review", # 继续循环
"end": END # 结束
}
)
# 编译图
app = workflow.compile()
# 使用示例
initial_state = {
"code": """
def calculate_sum(a, b):
# TODO: 添加错误处理
print(f"计算 {a} + {b}")
return a + b
""",
"issues": [],
"fixes": [],
"iteration": 0,
"max_iterations": 5,
"is_complete": False
}
result = app.invoke(initial_state)
print(f"最终代码:\n{result['code']}")
print(f"迭代次数:{result['iteration']}")
4.3 优化:添加最大迭代次数和早期终止条件
def should_continue_optimized(state: CodeReviewState) -> Literal["review", "end"]:
"""优化的终止条件判断"""
# 检查最大迭代次数
if state["iteration"] >= state["max_iterations"]:
print(f"达到最大迭代次数 {state['max_iterations']},停止审查")
return "end"
# 检查是否完成
if state["is_complete"]:
print("代码审查完成,未发现问题")
return "end"
# 检查问题数量是否减少(避免无限循环)
if state["iteration"] > 1:
# 如果迭代次数超过 1 次,但问题数量没有减少,可能陷入循环
if len(state["issues"]) >= state.get("previous_issue_count", 0):
print("问题数量未减少,可能陷入循环,停止审查")
return "end"
return "review"
5. 实战案例二:多 Agent 协作的复杂工作流
5.1 场景:研究助手 + 代码生成器 + 测试工程师的协作
假设我们要构建一个系统,包含三个 Agent:
- 研究助手:负责查找资料、分析问题
- 代码生成器:负责根据需求生成代码
- 测试工程师:负责测试代码、发现问题
工作流程:
- 研究助手分析需求
- 代码生成器生成代码
- 测试工程师测试代码
- 如果测试失败,研究助手分析问题,代码生成器修复,测试工程师重新测试(循环)
- 如果测试通过,结束
5.2 实现:使用动态路由和循环图构建多 Agent 系统
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, END
class MultiAgentState(TypedDict):
requirement: str
research_result: str
code: str
test_result: str
test_passed: bool
iteration: int
max_iterations: int
def research_agent(state: MultiAgentState) -> MultiAgentState:
"""研究助手:分析需求"""
requirement = state["requirement"]
# 简化示例:实际可以使用 LangChain 的搜索工具
research_result = f"研究结果:{requirement} 需要实现一个计算器功能"
return {
**state,
"research_result": research_result
}
def code_generator(state: MultiAgentState) -> MultiAgentState:
"""代码生成器:生成代码"""
research_result = state["research_result"]
requirement = state["requirement"]
# 简化示例:实际可以使用代码生成 LLM
code = f"""
def calculate(a, b, operation):
if operation == '+':
return a + b
elif operation == '-':
return a - b
elif operation == '*':
return a * b
elif operation == '/':
return a / b
else:
raise ValueError("不支持的操作")
"""
return {
**state,
"code": code
}
def test_engineer(state: MultiAgentState) -> MultiAgentState:
"""测试工程师:测试代码"""
code = state["code"]
# 简化示例:实际可以执行代码并运行测试
# 这里模拟测试结果
test_passed = state.get("iteration", 0) >= 2 # 假设第 2 次迭代后通过
test_result = "测试通过" if test_passed else "测试失败:缺少错误处理"
return {
**state,
"test_result": test_result,
"test_passed": test_passed
}
def route_after_test(state: MultiAgentState) -> Literal["research", "end"]:
"""测试后的路由决策"""
# 如果测试通过,结束
if state["test_passed"]:
return "end"
# 如果达到最大迭代次数,结束
if state["iteration"] >= state["max_iterations"]:
return "end"
# 如果测试失败,继续研究(形成循环)
return "research"
# 构建图
workflow = StateGraph(MultiAgentState)
# 添加节点
workflow.add_node("research", research_agent)
workflow.add_node("code_gen", code_generator)
workflow.add_node("test", test_engineer)
# 设置入口
workflow.set_entry_point("research")
# 添加边:线性流程
workflow.add_edge("research", "code_gen")
workflow.add_edge("code_gen", "test")
# 添加条件边:根据测试结果决定下一步
workflow.add_conditional_edges(
"test",
route_after_test,
{
"research": "research", # 测试失败,重新研究(循环)
"end": END # 测试通过或达到最大迭代次数,结束
}
)
# 编译图
app = workflow.compile()
# 使用示例
initial_state = {
"requirement": "创建一个计算器",
"research_result": "",
"code": "",
"test_result": "",
"test_passed": False,
"iteration": 0,
"max_iterations": 5
}
result = app.invoke(initial_state)
print(f"最终代码:\n{result['code']}")
print(f"测试结果:{result['test_result']}")
print(f"迭代次数:{result['iteration']}")
5.3 状态管理:如何在多个 Agent 之间共享状态
在 LangGraph 中,状态是通过 TypedDict 定义的,所有节点都可以读取和修改状态。关键点:
- 状态是共享的:所有节点访问同一个状态对象
- 状态更新是累积的:节点返回的字典会合并到现有状态中
- 使用
Annotated处理列表:对于列表类型的字段,使用Annotated[List, operator.add]来累积添加
from typing import Annotated
from operator import add
class AgentState(TypedDict):
messages: Annotated[list, add] # 消息列表会累积
current_task: str # 当前任务
results: dict # 结果字典
6. 高级技巧:状态检查点与错误恢复
6.1 保存和恢复图执行状态
在生产环境中,我们可能需要保存图的执行状态,以便在出错时恢复:
import json
from datetime import datetime
def save_checkpoint(state: dict, checkpoint_path: str):
"""保存检查点"""
checkpoint = {
"timestamp": datetime.now().isoformat(),
"state": state
}
with open(checkpoint_path, "w") as f:
json.dump(checkpoint, f, indent=2)
def load_checkpoint(checkpoint_path: str) -> dict:
"""加载检查点"""
with open(checkpoint_path, "r") as f:
checkpoint = json.load(f)
return checkpoint["state"]
# 在节点中使用
def robust_node(state: MultiAgentState) -> MultiAgentState:
try:
# 执行节点逻辑
result = do_something(state)
return result
except Exception as e:
# 保存检查点
save_checkpoint(state, "checkpoint.json")
raise e
6.2 错误处理和重试机制
from typing import Optional
class MultiAgentState(TypedDict):
# ... 其他字段 ...
error_count: int
last_error: Optional[str]
def handle_error(state: MultiAgentState) -> MultiAgentState:
"""错误处理节点"""
error_count = state.get("error_count", 0) + 1
# 如果错误次数过多,停止重试
if error_count >= 3:
return {
**state,
"error_count": error_count,
"last_error": "错误次数过多,停止重试"
}
# 否则,记录错误并继续
return {
**state,
"error_count": error_count,
"last_error": "发生错误,将重试"
}
def should_retry(state: MultiAgentState) -> Literal["retry", "end"]:
"""决定是否重试"""
if state.get("error_count", 0) >= 3:
return "end"
return "retry"
# 在图中添加错误处理节点
workflow.add_node("error_handler", handle_error)
workflow.add_conditional_edges(
"error_handler",
should_retry,
{
"retry": "research", # 重试
"end": END # 结束
}
)
6.3 优雅降级策略
def fallback_handler(state: MultiAgentState) -> MultiAgentState:
"""降级处理:当主要流程失败时使用"""
# 返回一个基本的、可用的结果
return {
**state,
"code": "# 基础实现(降级模式)",
"test_passed": True, # 标记为通过,避免无限循环
"test_result": "使用降级方案"
}
7. 性能优化:并行执行与资源管理
7.1 并行节点的设计
在某些场景下,我们可以并行执行多个节点:
from langgraph.graph import StateGraph
from concurrent.futures import ThreadPoolExecutor
def parallel_node_1(state: dict) -> dict:
"""可以并行执行的节点 1"""
# 执行任务 1
return {"result_1": "done"}
def parallel_node_2(state: dict) -> dict:
"""可以并行执行的节点 2"""
# 执行任务 2
return {"result_2": "done"}
def merge_results(state: dict) -> dict:
"""合并并行执行的结果"""
return {
**state,
"merged_result": f"{state.get('result_1')} + {state.get('result_2')}"
}
# 注意:LangGraph 本身是顺序执行的
# 如果需要真正的并行,需要在节点内部使用并发
def parallel_executor(state: dict) -> dict:
"""在节点内部实现并行执行"""
with ThreadPoolExecutor(max_workers=2) as executor:
future1 = executor.submit(parallel_node_1, state)
future2 = executor.submit(parallel_node_2, state)
result1 = future1.result()
result2 = future2.result()
return {**state, **result1, **result2}
7.2 资源池管理
对于需要管理资源的场景(如数据库连接、API 客户端),可以使用资源池:
from queue import Queue
import threading
class ResourcePool:
def __init__(self, resource_factory, pool_size=5):
self.pool = Queue(maxsize=pool_size)
self.lock = threading.Lock()
# 初始化资源池
for _ in range(pool_size):
self.pool.put(resource_factory())
def acquire(self):
return self.pool.get()
def release(self, resource):
self.pool.put(resource)
# 使用资源池
resource_pool = ResourcePool(lambda: create_database_connection(), pool_size=10)
def node_with_resource(state: dict) -> dict:
"""使用资源的节点"""
conn = resource_pool.acquire()
try:
# 使用连接
result = conn.execute_query("SELECT * FROM table")
return {"result": result}
finally:
resource_pool.release(conn)
7.3 避免死锁和资源竞争
在设计循环图时,要注意避免死锁:
- 设置超时:为每个节点设置超时时间
- 限制迭代次数:避免无限循环
- 资源超时释放:如果资源占用时间过长,强制释放
import signal
from contextlib import contextmanager
@contextmanager
def timeout(seconds):
"""超时上下文管理器"""
def timeout_handler(signum, frame):
raise TimeoutError(f"操作超时({seconds} 秒)")
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(seconds)
try:
yield
finally:
signal.alarm(0)
def safe_node(state: dict) -> dict:
"""带超时的安全节点"""
try:
with timeout(30): # 30 秒超时
return do_something(state)
except TimeoutError:
return {
**state,
"error": "节点执行超时"
}
8. 总结:从基础到高级的完整进阶路径
通过本章的学习,我们掌握了 LangGraph 的高级特性:
-
动态路由:
- 使用条件边实现动态路由
- 基于状态或 LLM 决策的路由
- 多路径工作流的设计
-
循环图:
- 循环图的设计原则
- 终止条件的设置
- 迭代优化的实现
-
多 Agent 协作:
- 多个 Agent 的协调
- 状态共享和管理
- 复杂工作流的构建
-
生产级特性:
- 错误处理和重试
- 状态检查点
- 性能优化
关键要点:
- 动态路由让 Agent 更智能:能够根据情况做出决策
- 循环图让 Agent 更强大:能够迭代优化,不断改进
- 状态管理是核心:合理设计状态结构,确保 Agent 之间能够有效协作
- 错误处理不可忽视:生产环境中的 Agent 必须能够优雅地处理错误
现在,你已经掌握了 LangGraph 从基础到高级的完整技能栈。在下一章中,我们将通过作业点评,深入分析多智能体协同中的常见误区和最佳实践。