2.7 【直播精华】LangGraph 高阶玩法:动态路由与循环图,解锁无限可能

2 阅读1分钟

导语:在前面的章节中,我们已经掌握了 LangGraph 的基础用法:如何定义节点、如何连接边、如何构建简单的 Agent 工作流。但在实际的生产环境中,我们往往需要处理更复杂的场景:根据运行时的状态动态决定下一步走向、构建可以循环执行的工作流、实现多 Agent 协作的复杂系统。本章是直播课程的精华总结,我们将深入探讨 LangGraph 的高级特性:动态路由(Dynamic Routing)和循环图(Cyclic Graphs),通过实际案例展示如何用这些技术构建真正强大、灵活的 Agent 系统。

目录

  1. 从静态到动态:为什么需要动态路由?

    • 静态图的局限性
    • 动态路由的核心价值
    • 实际应用场景
  2. 动态路由实战:构建一个智能任务分发系统

    • 场景:根据任务类型动态选择处理节点
    • 实现:使用条件边实现动态路由
    • 进阶:基于 LLM 决策的智能路由
  3. 循环图的力量:让 Agent 学会"迭代优化"

    • 什么是循环图?
    • 为什么需要循环?
    • 循环终止条件的设计
  4. 实战案例一:构建一个自我改进的代码审查 Agent

    • 需求:代码审查 → 发现问题 → 生成修复建议 → 重新审查
    • 实现:使用循环图实现迭代优化
    • 优化:添加最大迭代次数和早期终止条件
  5. 实战案例二:多 Agent 协作的复杂工作流

    • 场景:研究助手 + 代码生成器 + 测试工程师的协作
    • 实现:使用动态路由和循环图构建多 Agent 系统
    • 状态管理:如何在多个 Agent 之间共享状态
  6. 高级技巧:状态检查点与错误恢复

    • 保存和恢复图执行状态
    • 错误处理和重试机制
    • 优雅降级策略
  7. 性能优化:并行执行与资源管理

    • 并行节点的设计
    • 资源池管理
    • 避免死锁和资源竞争
  8. 总结:从基础到高级的完整进阶路径


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)**允许我们根据图执行过程中的状态,动态决定下一个要执行的节点。这带来了几个关键优势:

  1. 灵活性:可以根据不同的输入或中间结果,选择不同的处理路径
  2. 效率:避免执行不必要的节点
  3. 智能性:让 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 循环终止条件的设计

循环图必须要有终止条件,否则会无限循环。常见的终止条件:

  1. 最大迭代次数:限制循环次数
  2. 目标达成:达到预期结果后退出
  3. 用户确认:需要用户确认是否继续
  4. 超时:超过时间限制后退出

4. 实战案例一:构建一个自我改进的代码审查 Agent

4.1 需求

我们要构建一个代码审查 Agent,它能够:

  1. 审查代码,发现问题
  2. 生成修复建议
  3. 应用修复
  4. 重新审查
  5. 如果还有问题,继续循环;如果没有问题,结束

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:

  • 研究助手:负责查找资料、分析问题
  • 代码生成器:负责根据需求生成代码
  • 测试工程师:负责测试代码、发现问题

工作流程:

  1. 研究助手分析需求
  2. 代码生成器生成代码
  3. 测试工程师测试代码
  4. 如果测试失败,研究助手分析问题,代码生成器修复,测试工程师重新测试(循环)
  5. 如果测试通过,结束

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 定义的,所有节点都可以读取和修改状态。关键点:

  1. 状态是共享的:所有节点访问同一个状态对象
  2. 状态更新是累积的:节点返回的字典会合并到现有状态中
  3. 使用 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 避免死锁和资源竞争

在设计循环图时,要注意避免死锁:

  1. 设置超时:为每个节点设置超时时间
  2. 限制迭代次数:避免无限循环
  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 的高级特性:

  1. 动态路由

    • 使用条件边实现动态路由
    • 基于状态或 LLM 决策的路由
    • 多路径工作流的设计
  2. 循环图

    • 循环图的设计原则
    • 终止条件的设置
    • 迭代优化的实现
  3. 多 Agent 协作

    • 多个 Agent 的协调
    • 状态共享和管理
    • 复杂工作流的构建
  4. 生产级特性

    • 错误处理和重试
    • 状态检查点
    • 性能优化

关键要点

  • 动态路由让 Agent 更智能:能够根据情况做出决策
  • 循环图让 Agent 更强大:能够迭代优化,不断改进
  • 状态管理是核心:合理设计状态结构,确保 Agent 之间能够有效协作
  • 错误处理不可忽视:生产环境中的 Agent 必须能够优雅地处理错误

现在,你已经掌握了 LangGraph 从基础到高级的完整技能栈。在下一章中,我们将通过作业点评,深入分析多智能体协同中的常见误区和最佳实践。