LangGraph 工作流设计:状态图驱动的多 Agent 编排实战

一、为什么需要工作流引擎?

在构建 LLM 应用时,开发者很快会面临一个共同挑战:单个 LLM 调用不够用

真实场景往往需要:

  • 先分析问题,再检索文档,然后生成回答,最后验证答案
  • 根据意图类型,走不同的处理分支
  • 多个 Agent 并行处理,然后融合结果
  • 失败时重试,超时时降级

直接在业务代码中用 if-else 和循环来编排这些逻辑,很快就会变成难以维护的"意大利面条代码"。LangGraph 正是为应对这一挑战而生的------它用有向图来建模工作流,每个节点是一个处理单元,边定义了状态如何流转。

本文基于两个实际项目------LexRAG(法律 RAG 系统)HeritageMind(非遗多智能体系统) ------深入解析 LangGraph 的两种核心工作流模式。


二、LangGraph 核心概念速览

2.1 三要素

css 复制代码
┌────────────────────────────────────────┐
│              StateGraph                │
│                                       │
│  ┌──────┐    edge    ┌──────┐         │
│  │ Node │ ────────> │ Node │         │
│  │  A   │           │  B   │         │
│  └──────┘           └──────┘         │
│       │                               │
│       │  conditional edge              │
│       ▼                               │
│  ┌──────┐    ┌──────┐                 │
│  │ Node │    │ Node │                 │
│  │  C   │    │  D   │                 │
│  └──────┘    └──────┘                 │
│                                       │
│  State: 在节点间流转的共享数据结构      │
└────────────────────────────────────────┘
要素 说明 类比
State 在节点间传递的共享数据 流水线上的工件
Node 接收 State,返回更新后的 State 流水线上的工位
Edge 连接节点,定义流转方向 传送带

2.2 边的三种类型

python 复制代码
from langgraph.graph import StateGraph, END
​
# 1. 普通边 --- 固定流向
graph.add_edge("node_a", "node_b")  # A 完成后必定到 B
​
# 2. 条件边 --- 根据 State 动态路由
graph.add_conditional_edges(
    "node_a",
    router_function,        # (state) -> str  返回目标节点名
    {"path_a": "node_b", "path_b": "node_c"}
)
​
# 3. 入口点 --- 工作流的起点
graph.set_entry_point("node_a")

三、模式一:ReAct Agent 工作流(LexRAG)

3.1 ReAct 模式原理

ReAct(Reasoning + Acting)是 LLM Agent 的经典范式:

sql 复制代码
Thought → Action → Observation → Thought → Action → ... → Final Answer

LangGraph 提供了 create_react_agent 预置实现,将 ReAct 循环封装为现成的图结构。

3.2 LexRAG 的 ReAct 实现

ini 复制代码
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
​
# 1. 定义工具集
tools = [
    search_statute,      # 检索法条
    search_case,         # 检索案例
    lookup_article,      # 查阅具体条款
    analyze_legal_relation,  # 法律关系分析
]
​
# 2. 创建 ReAct Agent
llm = ChatOpenAI(model="deepseek-chat", temperature=0.1)
agent = create_react_agent(llm, tools, state_modifier=system_prompt)
​
# 3. 执行
result = agent.invoke({"messages": [HumanMessage(content=user_query)]})

3.3 工作流内部状态机

scss 复制代码
                    ┌─────────────┐
    用户输入 ─────>│   Agent     │
                    │   Node      │
                    └──────┬──────┘
                           │
              ┌────────────┼────────────┐
              │ 需要工具?  │             │
              ▼            │             ▼
        ┌──────────┐       │      ┌──────────┐
        │ 调用工具  │       │      │ 生成最终  │
        │ Tool Node│       │      │  回答    │
        └────┬─────┘       │      └────┬─────┘
             │             │           │
             └─────────────┘           ▼
                  (循环)              END

Agent 在每个回合自主决定:

需要调用哪个工具? ------ 法律检索?案例查找?条款查阅?

用什么参数调用? ------ 从用户问题中提取关键信息

何时停止? ------ 信息足够回答时,生成最终答案

3.4 ReAct Agent 的优势与局限

优势

  • 零样本工具使用------不需要为每个工具编写调用逻辑
  • 自适应推理------Agent 自主决定调用顺序和次数
  • 代码简洁------一个 create_react_agent 调用就完成了编排

局限

  • 可控性弱------你无法精确控制 Agent 的推理路径
  • 调试困难------Agent 为什么调用某个工具、为什么做出了某个决策,都是"黑盒"
  • 不适合确定性流程------如果业务逻辑要求"A 必须在 B 之前执行",ReAct 无法保证

四、模式二:StateGraph 自定义工作流(HeritageMind)

当业务流程有明确步骤时,自定义 StateGraph 是更好的选择。HeritageMind 的多 Agent 协作就采用了这种模式。

4.1 状态定义

python 复制代码
from typing import TypedDict, List, Dict, Optional
​
class WorkflowState(TypedDict):
    # ── 输入 ──
    question: str
    user_profile: str
​
    # ── 分析结果 ──
    question_analysis: dict        # {required_experts, key_entities, complexity}
    required_experts: List[str]    # ["craft_expert", "history_expert"]
    key_entities: List[str]        # ["景泰蓝", "掐丝珐琅"]
    complexity: str                # "simple" | "medium" | "complex"
​
    # ── Agent 响应 ──
    expert_responses: Dict[str, dict]  # {agent_name: AgentResponse}
​
    # ── 融合结果 ──
    fused_content: str
    use_debate: bool
    debate_session: Optional[dict]
    debate_mode: Optional[str]     # "progressive" | "parallel" | "multi_perspective"
​
    # ── 质量保障 ──
    gap_detection: dict
    has_gaps: bool
    gap_report: str
​
    # ── 输出 ──
    adapted_content: str
    final_response: str
    source_agents: List[str]
​
    # ── 异常 ──
    errors: List[str]
    metadata: dict

设计要点

单一数据源------所有节点读写同一个 State 对象,消除了跨节点的数据传递问题

显式错误字段 ------errors: List[str] 让错误信息随状态传播,而非通过异常中断流程

可追溯性------每个阶段的产出都有对应字段,可以完整还原推理链路

4.2 节点实现

每个节点是一个纯函数:(WorkflowState) -> WorkflowState

python 复制代码
def analyze_question_node(state: WorkflowState) -> WorkflowState:
    """节点1: 问题分析"""
    dispatcher = DispatcherAgent()
    analysis = dispatcher.analyze_question(state["question"])
​
    return {
        **state,
        "question_analysis": analysis,
        "required_experts": analysis["required_experts"],
        "key_entities": analysis["key_entities"],
        "complexity": analysis.get("complexity", "medium"),
    }
​
​
def dispatch_to_experts_node(state: WorkflowState) -> WorkflowState:
    """节点2: 分发到领域专家 Agent"""
    dispatcher = DispatcherAgent()
    responses = {}
​
    # 为每个所需的专家初始化 Agent 并调用 process()
    for expert_name in state["required_experts"]:
        agent_class = AGENT_REGISTRY[expert_name]  # 从注册表获取
        agent = agent_class()
        response = agent.process(
            question=state["question"],
            context={"key_entities": state["key_entities"]}
        )
        responses[expert_name] = response
​
    return {**state, "expert_responses": responses}

关键设计:Agent 注册表模式

makefile 复制代码
AGENT_REGISTRY = {
    "craft_expert": CraftExpert,
    "history_expert": HistoryExpert,
    "heritage_expert": HeritageExpert,
}

这种注册表模式带来两个好处:

可扩展------新增 Agent 只需在注册表中加一行

可配置------调度器通过字符串名称引用 Agent,无需硬编码

4.3 条件路由

工作流中最精妙的部分是条件边------它们根据 State 内容做出路由决策:

python 复制代码
def has_expert_responses(state: WorkflowState) -> str:
    """检查专家是否返回了有效响应"""
    if state.get("expert_responses") and len(state["expert_responses"]) > 0:
        return "fuse"
    return "error"
​
​
def should_detect_gaps(state: WorkflowState) -> str:
    """根据复杂度决定是否执行缺口检测"""
    if state.get("complexity") in ("medium", "complex"):
        return "detect_gaps"
    return "skip_gaps"
​
​
def should_include_narrative(state: WorkflowState) -> str:
    """是否启用传承人叙事模式"""
    if state.get("include_narrative", False):
        return "narrative"
    return "standard"

4.4 图的组装

python 复制代码
from langgraph.graph import StateGraph, END
​
def build_workflow() -> StateGraph:
    workflow = StateGraph(WorkflowState)
​
    # ── 注册节点 ──
    workflow.add_node("analyze_question", analyze_question_node)
    workflow.add_node("dispatch_to_experts", dispatch_to_experts_node)
    workflow.add_node("collect_responses", collect_expert_responses_node)
    workflow.add_node("fuse_knowledge", fuse_knowledge_node)
    workflow.add_node("detect_gaps", detect_gaps_node)
    workflow.add_node("generate_response_standard", generate_response_node)
    workflow.add_node("generate_response_narrative", generate_narrative_response_node)
    workflow.add_node("handle_error", error_handler_node)
​
    # ── 设置入口 ──
    workflow.set_entry_point("analyze_question")
​
    # ── 固定边 ──
    workflow.add_edge("analyze_question", "dispatch_to_experts")
    workflow.add_edge("dispatch_to_experts", "collect_responses")
​
    # ── 条件边 ──
    workflow.add_conditional_edges(
        "collect_responses",
        has_expert_responses,
        {"fuse": "fuse_knowledge", "error": "handle_error"}
    )
​
    workflow.add_edge("fuse_knowledge", "detect_gaps")  # 总是先到检测节点
    workflow.add_conditional_edges(
        "detect_gaps",
        should_detect_gaps,  # 但检测节点可以根据条件跳过
        {"detect_gaps": "generate_response_standard",  # 注意:实际执行检测
         "skip_gaps": "generate_response_standard"}      # 或跳过(都到同一节点)
    )
​
    workflow.add_conditional_edges(
        "generate_response_standard",
        should_include_narrative,
        {"narrative": "generate_response_narrative",
         "standard": END}
    )
​
    workflow.add_edge("generate_response_narrative", END)
    workflow.add_edge("handle_error", END)
​
    return workflow.compile()

4.5 完整工作流可视化

vbnet 复制代码
                        ┌──────────────┐
                        │ analyze_     │
                        │ question     │
                        └──────┬───────┘
                               │
                        ┌──────▼───────┐
                        │ dispatch_to_ │
                        │ experts      │
                        └──────┬───────┘
                               │
                        ┌──────▼───────┐
                        │ collect_     │
                        │ responses    │
                        └──────┬───────┘
                               │
                    ┌──────────┼──────────┐
                    │ 有响应?  │          │ 无响应
                    ▼          │          ▼
              ┌──────────┐    │    ┌──────────┐
              │ fuse_    │    │    │ handle_  │
              │ knowledge│    │    │ error    │──> END
              └────┬─────┘    │    └──────────┘
                   │          │
              ┌────▼─────┐    │
              │ detect_  │    │
              │ gaps     │    │
              └────┬─────┘    │
                   │          │
         ┌─────────┼──────────┘
         │ 复杂?  │
         ▼         ▼
    ┌─────────┐ ┌──────────┐
    │ (执行   │ │ (跳过    │
    │  检测)  │ │  检测)   │
    └────┬────┘ └────┬─────┘
         │           │
         └─────┬─────┘
               │
        ┌──────▼───────┐
        │ generate_    │
        │ response_    │
        │ standard     │
        └──────┬───────┘
               │
      ┌────────┼────────┐
      │ 叙事?  │        │ 标准
      ▼        │        ▼
  ┌────────┐  │      END
  │narrative│  │
  │ gen     │  │
  └───┬────┘  │
      │       │
      ▼       │
     END     │

五、两种模式的对比与选择

维度 ReAct Agent 自定义 StateGraph
灵活性 Agent 自主决策 预设路径
可控性 黑盒推理 每步可预期
可调试性 需解析 Agent 日志 State 快照可审计
开发成本 几行代码 需定义节点和边
适用场景 工具灵活组合 确定性业务流程
错误处理 依赖 Agent 自身 显式错误节点和路由

选择决策树

复制代码
需要 Agent 自主选择工具和推理路径?
  ├── 是 → 用 ReAct Agent(如:法律问答,不同问题需要不同的检索策略)
  └── 否 → 流程步骤是否可预先确定?
            ├── 是 → 用自定义 StateGraph(如:非遗问答,分析→分发→融合→输出)
            └── 否 → 回到 ReAct Agent

混合使用

两个模式并不互斥。LexRAG 项目就展示了混合使用的最佳实践:

ruby 复制代码
class LegalRAGWorkflow:
    def query(self, question: str) -> QueryResult:
        # 前置处理:StateGraph 式的确定性流程
        intent = self.intent_router.route(question)      # 步骤1: 意图分析
        rewritten = self.query_rewriter.rewrite(question) # 步骤2: 查询重写
​
        # 核心推理:ReAct Agent 的灵活工具使用
        agent_result = self.react_agent.invoke({          # 步骤3: Agent 推理
            "messages": [HumanMessage(content=rewritten)]
        })
​
        # 后置处理:StateGraph 式的验证流程
        verified = self.verifier.verify(agent_result)     # 步骤4: 自我验证
        if not verified.passed:
            verified = self.verifier.correct(verified)    # 步骤5: 纠错
​
        return verified

核心原则用 StateGraph 定义"骨架",用 ReAct Agent 填充"肌肉"。


六、实战要点

6.1 状态设计的三个原则

  1. 不可变性------每个节点返回新的 State 字典,而非修改传入的 State
  2. 最小化------State 中只放节点间需要传递的数据,不放节点的内部临时变量
  3. 可追溯 ------保留关键中间产出(如 question_analysis),便于调试和审计

6.2 错误处理的三种策略

python 复制代码
# 策略1: 错误随状态传播
def safe_node(state: WorkflowState) -> WorkflowState:
    try:
        result = risky_operation(state)
        return {**state, "result": result}
    except Exception as e:
        return {**state, "errors": [*state.get("errors", []), str(e)]}
​
# 策略2: 专用错误节点
workflow.add_conditional_edges(
    "risky_node",
    lambda s: "error" if s.get("errors") else "continue",
    {"error": "error_handler", "continue": "next_node"}
)
​
# 策略3: 降级路径
def router_with_fallback(state: WorkflowState) -> str:
    if state.get("primary_result"):
        return "use_primary"
    elif state.get("fallback_result"):
        return "use_fallback"
    return "error"

6.3 测试工作流

python 复制代码
def test_workflow_analyze_question():
    """测试问题分析节点"""
    state = create_initial_state("景泰蓝的制作工艺是什么?")
    result = analyze_question_node(state)
​
    assert "craft_expert" in result["required_experts"]
    assert "景泰蓝" in result["key_entities"]
​
​
def test_workflow_error_path():
    """测试错误处理路径"""
    state = create_initial_state("")
    result = workflow.invoke(state)
​
    assert len(result["errors"]) > 0
    assert result.get("final_response") is None

相关推荐
Solis2 小时前
百万并发的点赞计数,我们为什么要把“事实“和“计数“拆开?
java·架构
杉氧2 小时前
Framework 补完计划 (2):BufferQueue 与 SurfaceFlinger —— 像素的跨进程“接力赛”
android·架构·android jetpack
Loving_enjoy2 小时前
DFT扫描链设计与优化策略:构建高效可测试性电路架构
架构
heiyehk2 小时前
我用AI不断迭代到7W行代码的项目-像素画编辑器
前端·架构·设计
zx78542 小时前
Kubernetes 生产级实战:从零部署 LNMP 架构 ECShop 商城(完整指南)
容器·架构·kubernetes
宋哥转AI2 小时前
Agent实战 #01:Dream-SaaS 整体架构拆解
架构
@insist1232 小时前
系统规划与管理师-计算与存储资源规划核心考点解析
架构·软考·系统规划与管理师·软件水平考试·系统规划与管理工程师
国科安芯2 小时前
基于ASM1042S2S的箭载通信网络抗辐射加固方案研究
服务器·网络·嵌入式硬件·fpga开发·架构·信号处理