LangGraph深度拆解:从链式调用到状态图的Agent进化

4 阅读30分钟

引言:一个真实的困境

想象你正在开发一个 AI 研究助手。用户输入:"帮我分析特斯拉 2024 年的竞争力"。

如果用 LangChain 的 Chain,你会这样设计:

# 简单的线性 Chain
chain = (
    search_financial_data      # ① 搜索财务数据
    | analyze_with_llm         # ② LLM 分析
    | generate_report          # ③ 生成报告
)

result = chain.invoke("特斯拉 2024")

看起来很简洁!但现实很快给你上了一课:

执行第①步:搜索财务数据
  → 只找到了 Q1 和 Q2 的数据,Q3、Q4 没找到

执行第②步:LLM 分析
  → LLM 说:"数据不完整,无法分析"

执行第③步:生成报告
  → 生成了一份"数据不足"的报告 ❌

用户看到报告:"这也太敷衍了吧!"

问题出在哪里?

Chain 是线性的:A → B → C,一旦开始就按固定顺序执行完。它没法:

  • 检查中间结果:"数据够不够?不够的话再搜一次"
  • 条件分支:"如果财报搜不到,就搜新闻报道"
  • 循环重试:"分析不够深入,回去补充数据"
  • 人机交互:"用户说'再深入看看电动车市场',重新分析"

你真正需要的是:

┌────────────────────────────────────────────┐
│ 智能研究助手的实际工作流                    │
└────────────────────────────────────────────┘

① 搜索财报
    ↓
② 检查数据完整性
    ├─ 完整 → 继续
    └─ 不完整 → 搜索新闻补充 → 回到②

③ LLM 分析
    ↓
④ 评估分析深度
    ├─ 足够深 → 生成报告
    └─ 不够深 → 生成新问题 → 回到①

⑤ 生成报告
    ↓
⑥ 用户反馈
    ├─ 满意 → 结束
    └─ 要求深入 → 记录需求 → 回到①

特点:
  - 有循环(可以回到前面的步骤)
  - 有分支(根据情况选择路径)
  - 有交互(等待用户反馈)
  - 有状态(记住已搜索的内容、中间结果)

这就是 LangGraph 要解决的问题:用状态图构建真正智能的 Agent 工作流


Part 1:Chain 的天花板在哪里?

场景 1:多轮对话的客服 Agent

让我们看一个更具体的例子:

用户:"我的订单怎么还没到?"

理想的客服流程:
─────────────────────────────────────
① 理解意图:用户在查询订单状态

② 查询数据库
    ├─ 找到订单 → 返回物流信息 → 结束 ✓
    └─ 没找到 → 继续 ③

③ 澄清信息:"请问您的订单号是多少?"
    → 等待用户输入(暂停执行!)

④ 用户回复:"OH12345"
    → 更新查询条件,重新查询(回到 ②)

⑤ 还是没找到
    → 转人工客服(降级处理)

关键点:
  - 第③步需要**暂停**,等待用户输入
  - 收到输入后要**恢复**执行
  - 第④步要**回到第②步**(循环)
  - 整个过程要**记住上下文**(已经问过什么、查过什么)

用 Chain 能实现吗?

# Chain 的尝试(注定失败)
chain = (
    understand_intent          # ① 理解意图
    | query_database           # ② 查询数据库
    | ??? # 怎么在这里暂停等待用户?
    | ??? # 怎么根据查询结果决定是返回还是澄清?
    | ??? # 怎么回到第②步重新查询?
)

# Chain 是线性的,执行一遍就结束了
# 无法暂停、无法循环、无法根据结果分支

核心矛盾:

Chain 的设计假设:
  "任务是可以一次性从头到尾完成的"

现实的 Agent 任务:
  "需要尝试、判断、回退、重来,是一个探索过程"

场景 2:数据分析的探索过程

再看一个技术场景:

用户:"分析我们的销售数据,找出问题"

数据分析 Agent 的真实工作流:
──────────────────────────────────────

① 加载数据
    查看数据结构:100 列,50 万行
    → 太大了,先采样看看

② LLM 初步分析
    发现:销售额 Q3 比 Q2 下降了 15%
    生成假设:"可能是地区差异?产品问题?季节因素?"

③ 根据假设选择分析方法:
    ├─ 假设 1:地区差异 → 按地区分组分析
    ├─ 假设 2:产品问题 → 按产品维度分析
    └─ 假设 3:季节因素 → 时间序列分析

④ 执行分析(假设选择了"地区分析")
    结果:华东地区下降 30%,其他地区正常

⑤ 判断结果
    ├─ 结果清晰 → 继续深入华东地区
    └─ 结果模糊 → 生成新假设,回到③

⑥ 深入华东地区
    按城市细分:上海、杭州、南京...
    发现:主要是上海下降,其他城市还好

⑦ 继续深入上海
    按产品分析...
    (递归深入,直到找到根因)

⑧ 生成报告

⑨ 用户看后说:"能不能对比一下竞品?"
    → 回到③,增加竞品对比维度

特点:
  - 动态路径(根据假设选择不同分析方法)
  - 递归深入(层层拆解问题)
  - 迭代优化(结果不清晰就换个角度)
  - 状态累积(保存所有中间分析结果)

Chain 的困境:

# Chain 无法表达这种动态、递归的流程
chain = (
    load_data
    | llm_analyze
    | ??? # 怎么根据 LLM 的分析动态选择下一步?
    | ??? # 怎么递归深入?
    | ??? # 怎么在用户反馈后重新执行部分流程?
)

# 你需要的不是一条链,而是一张图

Part 2:LangGraph 的核心洞察

从"链"到"图"的思维转变

让我们用一个生活化的类比来理解这个转变:

Chain = 流水线

想象一个汽车装配流水线:
  底盘 → 发动机 → 车身 → 喷漆 → 检验 → 出厂

特点:
  - 固定顺序,不能跳跃
  - 不能回头(喷完漆发现发动机有问题?废了,重来!)
  - 一条线走到底
  - 高效但死板

适合:
  标准化、流程化的制造任务

Graph = 工作流程图

想象一个产品研发流程:

    设计
     ↓
    评审 ──┐
     ↓     │ 不通过
    原型   │
     ↓     │
    测试 ──┘
     ↓
   ┌─判断─┐
   ↓      ↓
 通过    失败
   ↓      ↓
  生产   修改→回到设计

特点:
  - 有分支(通过/不通过)
  - 有循环(失败了回到前面改进)
  - 根据情况选路径
  - 灵活但需要规划

适合:
  需要判断、尝试、优化的探索任务

Agent 任务更像"研发"而非"制造"

为什么?

制造(Chain):
  - 目标明确:做出标准产品
  - 路径固定:按 SOP 执行
  - 一次成功:质量控制在流程中

研发(Graph):
  - 目标模糊:用户说"帮我分析数据"
  - 路径不定:分析过程中才知道下一步该做什么
  - 需要迭代:第一次分析不够深入,重来

LangGraph 的四大核心概念

1. StateGraph:工作流的蓝图

from langgraph.graph import StateGraph

# StateGraph = 你的 Agent 工作流的"设计图纸"
graph = StateGraph(State)

# 定义节点(执行单元)
graph.add_node("search", search_node)
graph.add_node("analyze", analyze_node)
graph.add_node("report", report_node)

# 定义边(流程路径)
graph.add_edge("search", "analyze")  # 搜索完 → 分析

# 定义条件边(根据情况选路径)
graph.add_conditional_edges(
    "analyze",
    should_continue,  # 判断函数
    {
        "continue": "search",  # 数据不够 → 回到搜索
        "finish": "report"     # 数据够了 → 生成报告
    }
)

# 编译成可执行的 Agent
agent = graph.compile()

类比理解:

StateGraph = 游戏的关卡地图

节点 = 关卡:
  - 新手村(search_node)
  - 森林迷宫(analyze_node)
  - Boss 战(report_node)

边 = 路径:
  - 新手村 → 森林迷宫(固定路径)
  - 森林迷宫 → ?(根据战斗结果决定)
      如果 HP < 50% → 回新手村补给
      如果 HP > 50% → 继续前进 Boss 战

State = 角色属性:
  - 等级、装备、HP、已完成的任务...
  - 在关卡间保持,不会丢失

2. State:共享的"工作台"

from typing import TypedDict, Annotated
from langgraph.graph import add_messages

class AgentState(TypedDict):
    """Agent 的状态结构"""

    # 对话历史(自动累积)
    messages: Annotated[list, add_messages]

    # 当前查询
    query: str

    # 搜索结果(累积)
    search_results: list

    # 迭代次数
    iteration: int

    # 用户是否满意
    user_satisfied: bool

    # 最终答案
    final_answer: str

State 的作用:

想象一个办公桌(State):
─────────────────────────────────────

桌面上放着:
  - 📋 用户的原始需求(query)
  - 📁 已收集的资料(search_results)
  - 📝 分析笔记(messages)
  - 🔢 工作进度(iteration: 3)
  - ✓ 是否完成(user_satisfied: False)

工作流程:
  ① 搜索员工来到桌前
     - 看到桌上的需求和已有资料
     - 去搜索新资料
     - 把新资料放到桌上(更新 search_results)
     - 离开

  ② 分析员工来到桌前
     - 看到所有资料(搜索员工刚放的)
     - 分析资料
     - 写分析笔记放桌上(更新 messages)
     - 更新进度标记(iteration: 3 → 4)
     - 离开

  ③ 判断员工来到桌前
     - 看进度:已经第 4 轮了
     - 看资料:够不够充分?
     - 决定:回到搜索员工 or 继续报告员工

关键:
  - 所有员工共享同一个桌面(State)
  - 每个员工看到的都是最新状态
  - 员工离开后,桌面保持(不会消失)

State 更新的合并策略:

# 节点函数的返回值会被合并到 State

def search_node(state: AgentState):
    """搜索节点"""
    query = state["query"]
    results = search_api(query)

    # 返回要更新的字段(部分 State)
    return {
        "search_results": results,  # 新搜索的结果
        "iteration": state["iteration"] + 1  # 迭代次数 +1
    }

# State 合并:
#   原 State: {"query": "xxx", "iteration": 2, "search_results": []}
#   节点返回: {"search_results": [新结果], "iteration": 3}
#   合并后:   {"query": "xxx", "iteration": 3, "search_results": [新结果]}

特殊字段:累积而非覆盖

class AgentState(TypedDict):
    # messages 使用 add_messages 注解
    # 表示:新消息追加到列表,而非替换整个列表
    messages: Annotated[list, add_messages]

# 例子:
#   原 State: {"messages": [msg1, msg2]}
#   节点返回: {"messages": [msg3]}
#   合并后:   {"messages": [msg1, msg2, msg3]}  # 追加,不是覆盖!

3. Node:执行单元的设计

节点是一个简单的函数:输入 State,输出 State 更新

def search_node(state: AgentState):
    """搜索节点:执行搜索,更新结果"""

    query = state["query"]
    iteration = state["iteration"]

    print(f"[搜索节点] 第 {iteration} 次搜索:{query}")

    # 执行搜索
    results = search_api(query)

    # 返回要更新的 State 字段
    return {
        "search_results": results,
        "iteration": iteration + 1,
        "messages": [f"搜索完成,找到 {len(results)} 条结果"]
    }

节点的类型:

# 1. 工具节点:执行具体操作
def search_node(state):
    results = search_api(state["query"])
    return {"search_results": results}

# 2. LLM 节点:调用语言模型
def analyze_node(state):
    llm_response = llm.invoke(state["search_results"])
    return {"messages": [llm_response]}

# 3. 决策节点:不调用外部服务,只做判断
def check_quality(state):
    if len(state["search_results"]) >= 10:
        return {"quality": "sufficient"}
    else:
        return {"quality": "insufficient"}

# 4. 人机交互节点:暂停执行,等待输入
def human_review(state):
    # 这里会暂停,等待人工反馈
    return {"status": "waiting_human_input"}

节点设计的最佳实践:

# ✓ 好的节点设计:单一职责
def search_financial_data(state):
    """只负责搜索财务数据"""
    return {"financial_data": ...}

def search_news(state):
    """只负责搜索新闻"""
    return {"news_data": ...}

def analyze_data(state):
    """只负责分析"""
    return {"analysis": ...}

# ✗ 差的节点设计:职责混乱
def do_everything(state):
    """又搜索又分析,什么都干"""
    financial_data = search_financial(...)
    news_data = search_news(...)
    analysis = analyze(financial_data, news_data)
    return {"everything": ...}
    # 问题:难以测试、难以复用、难以调试

4. Edge:路径的选择

普通边:固定路径

# A 完成后,总是执行 B
graph.add_edge("search", "analyze")

条件边:根据 State 决定路径

def should_continue(state: AgentState):
    """判断是否继续搜索"""

    # 已经搜了 5 轮,够了
    if state["iteration"] >= 5:
        return "max_iterations"

    # 用户满意了,结束
    if state["user_satisfied"]:
        return "finish"

    # 数据不够,继续搜索
    if len(state["search_results"]) < 10:
        return "need_more_data"

    # 数据够了,去分析
    return "ready_to_analyze"


# 添加条件边
graph.add_conditional_edges(
    source="analyze",       # 从 analyze 节点出发
    path=should_continue,   # 用这个函数判断
    path_map={              # 判断结果 → 目标节点
        "need_more_data": "search",    # 回到搜索
        "ready_to_analyze": "report",  # 去生成报告
        "finish": END,                 # 结束
        "max_iterations": END          # 结束
    }
)

可视化理解:

              analyze 节点
                  ↓
          [should_continue 判断]
                  ↓
      ┌───────────┼───────────┐
      ↓           ↓           ↓
 need_more   ready_to    max_iterations
    ↓           ↓               ↓
  search      report           END
    ↓
  analyze ← 循环回来

Part 3:核心机制深度解析

机制 1:循环的实现——ReAct 模式

让我们看一个经典的 AI Agent 模式:ReAct(Reasoning + Acting)

ReAct 的工作流程:

循环执行,直到任务完成:

  ① Thought(思考)
     LLM 分析当前情况,决定下一步
     例如:"我需要先搜索公司的财报数据"

  ② Action(行动)
     执行工具调用
     例如:search("特斯拉 2024 财报")

  ③ Observation(观察)
     查看工具返回的结果
     例如:找到 3 份财报

  ④ 判断
     任务完成了吗?
       - 否 → 回到 ①(继续思考下一步)
       - 是 → 生成最终答案

特点:
  - 思考 → 行动 → 观察,循环往复
  - 每一轮都在接近目标
  - 类似人类解决问题的方式

用 LangGraph 实现:

from langgraph.graph import StateGraph, END

class ReActState(TypedDict):
    messages: Annotated[list, add_messages]
    iteration: int
    task_complete: bool

# 1. 思考节点
def think_node(state: ReActState):
    """LLM 分析情况,决定下一步行动"""

    messages = state["messages"]

    # 构造提示词
    prompt = f"""
    已有信息:{messages}

    思考:下一步应该做什么?
    可以选择:
    - search(query): 搜索信息
    - calculate(expression): 计算
    - finish: 任务完成

    请输出你的决策。
    """

    llm_response = llm.invoke(prompt)

    return {
        "messages": [{"role": "assistant", "content": llm_response}]
    }

# 2. 行动节点
def act_node(state: ReActState):
    """执行工具调用"""

    last_message = state["messages"][-1]["content"]

    # 解析 LLM 的决策(简化示例)
    if "search" in last_message:
        query = extract_query(last_message)
        result = search_api(query)
        observation = f"搜索结果:{result}"
    elif "calculate" in last_message:
        expr = extract_expression(last_message)
        result = eval(expr)  # 简化处理,生产环境要更安全
        observation = f"计算结果:{result}"
    elif "finish" in last_message:
        return {"task_complete": True}
    else:
        observation = "无法理解该行动"

    return {
        "messages": [{"role": "tool", "content": observation}],
        "iteration": state["iteration"] + 1
    }

# 3. 决策函数:是否继续循环
def should_continue(state: ReActState):
    """判断是否继续 ReAct 循环"""

    # 任务完成
    if state["task_complete"]:
        return "finish"

    # 达到最大迭代次数
    if state["iteration"] >= 10:
        return "max_iterations"

    # 继续循环
    return "continue"

# 构建图
graph = StateGraph(ReActState)

# 添加节点
graph.add_node("think", think_node)
graph.add_node("act", act_node)

# 设置入口
graph.set_entry_point("think")

# think → act(固定)
graph.add_edge("think", "act")

# act → ?(根据判断)
graph.add_conditional_edges(
    "act",
    should_continue,
    {
        "continue": "think",      # 回到思考(循环!)
        "finish": END,
        "max_iterations": END
    }
)

# 编译
react_agent = graph.compile()

# 使用
result = react_agent.invoke({
    "messages": [{"role": "user", "content": "分析特斯拉的竞争力"}],
    "iteration": 0,
    "task_complete": False
})

执行示例:

用户:"分析特斯拉 2024 年的竞争力"1 轮:
  [think] LLM 思考:"需要先获取财务数据"
  [act] 执行:search("特斯拉 2024 财报")
  [observe] 结果:找到 Q1、Q2 财报
  [judge] 继续 → 回到 think

第 2 轮:
  [think] LLM 思考:"数据不完整,需要 Q3、Q4"
  [act] 执行:search("特斯拉 Q3 Q4 财报")
  [observe] 结果:找到了
  [judge] 继续 → 回到 think

第 3 轮:
  [think] LLM 思考:"数据齐全,开始分析"
  [act] 执行:calculate("增长率、利润率...")
  [observe] 结果:计算完成
  [judge] 继续 → 回到 think

第 4 轮:
  [think] LLM 思考:"分析完成,可以输出"
  [act] 执行:finish
  [judge] task_complete = True → END

最终输出:完整的竞争力分析报告

循环的安全保护:

def should_continue(state):
    """加上所有必要的安全检查"""

    # 1. 最大迭代次数
    if state["iteration"] >= 10:
        return "max_iterations"

    # 2. 超时检测
    if time.time() - state["start_time"] > 300:  # 5 分钟
        return "timeout"

    # 3. 成本控制
    if state["llm_calls"] >= 20:  # 最多 20 次 LLM 调用
        return "cost_limit"

    # 4. 异常检测
    if state["consecutive_failures"] >= 3:  # 连续失败 3 次
        return "error"

    # 5. 任务完成
    if state["task_complete"]:
        return "finish"

    # 继续
    return "continue"

机制 2:条件分支——智能路由

现实中的 Agent 需要根据情况选择不同的路径。

场景:智能客服的意图路由

# State 定义
class CustomerServiceState(TypedDict):
    user_message: str
    intent: str  # 意图:查询订单、退款、咨询等
    order_info: dict
    issue_resolved: bool

# 1. 意图识别节点
def intent_classifier(state):
    """识别用户意图"""

    message = state["user_message"]

    # LLM 分类(简化示例)
    prompt = f"""
    用户说:"{message}"

    这属于哪种意图?
    A. 查询订单
    B. 申请退款
    C. 产品咨询
    D. 投诉
    E. 其他

    只回答字母。
    """

    intent_code = llm.invoke(prompt)

    intent_map = {
        "A": "查询订单",
        "B": "退款",
        "C": "咨询",
        "D": "投诉",
        "E": "其他"
    }

    return {"intent": intent_map.get(intent_code, "其他")}

# 2. 路由函数
def route_by_intent(state):
    """根据意图路由到不同节点"""

    intent = state["intent"]

    # 返回目标节点名称
    if intent == "查询订单":
        return "order_lookup"
    elif intent == "退款":
        return "refund_process"
    elif intent == "咨询":
        return "knowledge_base"
    elif intent == "投诉":
        return "complaint_handler"
    else:
        return "human_handoff"  # 转人工

# 3. 不同路径的节点
def order_lookup_node(state):
    """查询订单"""
    order_info = query_database(state["user_message"])
    return {"order_info": order_info, "issue_resolved": True}

def refund_process_node(state):
    """退款流程"""
    # 退款逻辑...
    return {"issue_resolved": True}

def knowledge_base_node(state):
    """知识库检索"""
    answer = search_kb(state["user_message"])
    return {"response": answer, "issue_resolved": True}

def complaint_handler_node(state):
    """投诉处理"""
    # 记录投诉,升级...
    return {"issue_resolved": True}

def human_handoff_node(state):
    """转人工"""
    return {"status": "transferred_to_human"}

# 构建图
graph = StateGraph(CustomerServiceState)

# 添加所有节点
graph.add_node("intent_classifier", intent_classifier)
graph.add_node("order_lookup", order_lookup_node)
graph.add_node("refund_process", refund_process_node)
graph.add_node("knowledge_base", knowledge_base_node)
graph.add_node("complaint_handler", complaint_handler_node)
graph.add_node("human_handoff", human_handoff_node)

# 入口:先识别意图
graph.set_entry_point("intent_classifier")

# 条件路由:根据意图选择路径
graph.add_conditional_edges(
    "intent_classifier",
    route_by_intent,
    {
        "order_lookup": "order_lookup",
        "refund_process": "refund_process",
        "knowledge_base": "knowledge_base",
        "complaint_handler": "complaint_handler",
        "human_handoff": "human_handoff"
    }
)

# 所有路径最后都结束
for node in ["order_lookup", "refund_process", "knowledge_base",
             "complaint_handler", "human_handoff"]:
    graph.add_edge(node, END)

agent = graph.compile()

可视化:

                intent_classifier
                       ↓
         [根据 intent 判断路径]
                       ↓
      ┌────┬────┬─────┼─────┬────┐
      ↓    ↓    ↓     ↓     ↓    ↓
    查询  退款  咨询  投诉  转人工
    订单
      ↓    ↓    ↓     ↓     ↓
      └────┴────┴─────┴─────┘
                ↓
               END

运行示例:

# 场景 1:查询订单
result = agent.invoke({
    "user_message": "我的订单 OH12345 怎么还没到?"
})
# 路径:intent_classifier → order_lookup → END

# 场景 2:退款
result = agent.invoke({
    "user_message": "我要退货,这个产品质量太差了"
})
# 路径:intent_classifier → refund_process → END

# 场景 3:奇怪的问题
result = agent.invoke({
    "user_message": "你们老板是谁?"
})
# 路径:intent_classifier → human_handoff → END

机制 3:并行执行——提升效率

有些任务可以同时进行,无需等待。

场景:综合研究任务

class ResearchState(TypedDict):
    topic: str
    financial_data: dict  # 财务数据
    news_data: list       # 新闻数据
    industry_data: dict   # 行业数据
    stock_data: dict      # 股价数据
    all_completed: bool

# 四个独立的搜索节点
def search_financial(state):
    """搜索财务数据(可能需要 5 秒)"""
    results = search_api(f"{state['topic']} 财报")
    return {"financial_data": results}

def search_news(state):
    """搜索新闻(可能需要 3 秒)"""
    results = search_api(f"{state['topic']} 新闻")
    return {"news_data": results}

def search_industry(state):
    """搜索行业报告(可能需要 4 秒)"""
    results = search_api(f"{state['topic']} 行业报告")
    return {"industry_data": results}

def search_stock(state):
    """搜索股价数据(可能需要 2 秒)"""
    results = stock_api(state['topic'])
    return {"stock_data": results}

# 综合分析节点(需要等待所有搜索完成)
def comprehensive_analysis(state):
    """综合分析所有数据"""

    # 检查是否所有数据都有了
    if not all([
        state.get("financial_data"),
        state.get("news_data"),
        state.get("industry_data"),
        state.get("stock_data")
    ]):
        return {"error": "数据不完整"}

    # 综合分析
    analysis = llm.invoke(f"""
    财务数据:{state['financial_data']}
    新闻数据:{state['news_data']}
    行业数据:{state['industry_data']}
    股价数据:{state['stock_data']}

    请综合分析 {state['topic']} 的情况。
    """)

    return {"analysis": analysis, "all_completed": True}

# 构建图
graph = StateGraph(ResearchState)

# 添加节点
graph.add_node("search_financial", search_financial)
graph.add_node("search_news", search_news)
graph.add_node("search_industry", search_industry)
graph.add_node("search_stock", search_stock)
graph.add_node("comprehensive_analysis", comprehensive_analysis)

# 入口:直接开始四个搜索(并行)
graph.set_entry_point("search_financial")  # 其实任何一个都可以

# 关键:四个搜索节点都指向分析节点
graph.add_edge("search_financial", "comprehensive_analysis")
graph.add_edge("search_news", "comprehensive_analysis")
graph.add_edge("search_industry", "comprehensive_analysis")
graph.add_edge("search_stock", "comprehensive_analysis")

# 分析完成后结束
graph.add_edge("comprehensive_analysis", END)

# 编译
research_agent = graph.compile()

执行效果:

串行执行(传统 Chain):
  search_financial (5秒)
    → search_news (3秒)
    → search_industry (4秒)
    → search_stock (2秒)
    → analysis (3秒)

  总时间:5 + 3 + 4 + 2 + 3 = 17 秒


并行执行(LangGraph):
  ┌─ search_financial (5秒) ─┐
  ├─ search_news (3秒) ───────┤
  ├─ search_industry (4秒) ───┼─→ analysis (3秒)
  └─ search_stock (2秒) ──────┘

  总时间:max(5, 3, 4, 2) + 3 = 8 秒

  节省:17 - 8 = 9 秒(53% 提升!)

注意:并行的前提

# ✓ 可以并行的任务:
#   - 互不依赖
#   - 各自访问独立的 API
#   - 不修改相同的 State 字段

def search_a(state):
    return {"data_a": ...}  # 只修改 data_a

def search_b(state):
    return {"data_b": ...}  # 只修改 data_b

# ✗ 不能并行的任务:
#   - 有依赖关系(B 需要 A 的结果)
#   - 修改同一字段(会冲突)

def step1(state):
    return {"counter": state["counter"] + 1}  # 修改 counter

def step2(state):
    return {"counter": state["counter"] + 2}  # 也修改 counter
    # 如果并行,counter 的最终值不确定!

机制 4:人机交互(Human-in-the-Loop)

有些决策需要人工确认,LangGraph 原生支持暂停和恢复。

场景:报告生成的审核流程

from langgraph.checkpoint import MemorySaver

class ReportState(TypedDict):
    topic: str
    draft: str
    human_feedback: str
    approved: bool

# 1. 生成草稿
def generate_draft(state):
    """LLM 生成报告草稿"""

    draft = llm.invoke(f"写一份关于 {state['topic']} 的报告")

    return {"draft": draft}

# 2. 人工审核节点(关键!)
def human_review(state):
    """这个节点会暂停执行,等待人工输入"""

    # 返回特殊标记,表示需要中断
    return {"status": "awaiting_human_review"}

# 3. 处理反馈
def process_feedback(state):
    """根据人工反馈决定下一步"""

    feedback = state.get("human_feedback", "")

    if "approve" in feedback.lower():
        return {"approved": True}
    elif "reject" in feedback.lower():
        # 需要重写
        return {"approved": False}
    else:
        # 修改建议
        revised = llm.invoke(f"""
        原草稿:{state['draft']}
        修改建议:{feedback}

        请根据建议修改草稿。
        """)
        return {"draft": revised, "approved": False}

# 4. 路由函数
def check_approval(state):
    if state.get("approved"):
        return "approved"
    else:
        return "revise"

# 构建图
# 关键:使用 checkpointer 支持暂停和恢复
checkpointer = MemorySaver()
graph = StateGraph(ReportState)

graph.add_node("generate_draft", generate_draft)
graph.add_node("human_review", human_review)  # 中断点
graph.add_node("process_feedback", process_feedback)

graph.set_entry_point("generate_draft")

# 草稿 → 人工审核
graph.add_edge("generate_draft", "human_review")

# 人工审核 → 处理反馈
graph.add_edge("human_review", "process_feedback")

# 处理反馈后,根据结果决定
graph.add_conditional_edges(
    "process_feedback",
    check_approval,
    {
        "approved": END,
        "revise": "generate_draft"  # 回到重新生成
    }
)

# 编译,启用检查点
report_agent = graph.compile(checkpointer=checkpointer)

使用方式:

# 第一次执行:生成草稿,然后暂停
config = {"configurable": {"thread_id": "report_123"}}

state1 = report_agent.invoke({
    "topic": "AI 发展趋势"
}, config)

print(state1["draft"])
# 输出:
# "AI 发展趋势报告
#  1. 大模型持续进步...
#  2. 多模态融合...
#  3. ..."

print(state1["status"])
# 输出:"awaiting_human_review"  ← 暂停了!

# 用户看到草稿,决定修改
# 几小时后...用户回来了

# 第二次执行:提供人工反馈,恢复执行
state2 = report_agent.invoke({
    "human_feedback": "第 2 点需要更详细,加上具体例子"
}, config)  # 相同的 thread_id!

# Agent 从暂停的地方恢复
# 根据反馈修改草稿
# 再次暂停等待审核

print(state2["draft"])
# 输出:修改后的草稿

# 第三次执行:批准
state3 = report_agent.invoke({
    "human_feedback": "approve"
}, config)

print(state3["approved"])
# 输出:True
# 执行完成!

关键点:

# 1. thread_id 的作用
#    相同 thread_id = 同一个会话
#    不同 thread_id = 独立的会话

# 会话 1
agent.invoke(input1, config={"thread_id": "session_1"})

# 会话 2(独立的)
agent.invoke(input2, config={"thread_id": "session_2"})

# 2. checkpointer 的作用
#    保存每一步的 State
#    支持暂停和恢复
#    可以回溯到历史状态

# 3. 多轮交互
#    每次 invoke 都是"继续执行"
#    直到遇到 END 或新的中断点

Part 4:实战案例——研究助手 Agent

现在我们用 LangGraph 实现一个完整的研究助手。

需求:

用户输入:"帮我研究量子计算的最新进展"

Agent 需要:
  1. 理解查询,拆解子问题
  2. 搜索相关资料(可能多轮)
  3. 评估信息是否充分,不够就继续搜
  4. 综合分析,生成报告
  5. 等待用户反馈,如果要深入某方面,重新搜索

完整实现:

from langgraph.graph import StateGraph, END
from langgraph.checkpoint import MemorySaver
from typing import TypedDict, Annotated, List
from langgraph.graph import add_messages

# 1. State 定义
class ResearchState(TypedDict):
    # 用户原始查询
    original_query: str

    # 拆解的子问题列表
    sub_questions: List[str]

    # 搜索历史(所有搜索过的内容)
    search_history: List[dict]

    # 当前收集的文档
    documents: List[str]

    # 迭代次数
    iteration: int

    # 信息是否充分
    info_sufficient: bool

    # 生成的报告草稿
    report_draft: str

    # 用户反馈
    user_feedback: str

    # 任务完成
    task_complete: bool

    # 对话历史
    messages: Annotated[List, add_messages]

# 2. 节点定义

def query_understanding(state):
    """理解查询,拆解子问题"""

    query = state["original_query"]

    prompt = f"""
    用户查询:"{query}"

    请将这个查询拆解成 3-5 个子问题,每个子问题对应需要搜索的方向。

    例如,"量子计算的最新进展"可以拆解为:
    1. 量子计算的基本原理和定义
    2. 2023-2024 年的重大突破
    3. 主要研究机构和团队
    4. 商业应用前景
    5. 面临的挑战

    请输出子问题列表(JSON 格式)。
    """

    response = llm.invoke(prompt)
    sub_questions = parse_json(response)  # 解析 JSON

    return {
        "sub_questions": sub_questions,
        "messages": [{"role": "assistant", "content": f"已拆解为 {len(sub_questions)} 个子问题"}]
    }

def parallel_search(state):
    """对每个子问题进行搜索"""

    sub_questions = state["sub_questions"]
    iteration = state["iteration"]

    all_documents = []
    search_records = []

    for question in sub_questions:
        # 搜索(简化示例,实际会调用搜索 API)
        results = search_api(question)

        all_documents.extend(results)
        search_records.append({
            "iteration": iteration,
            "question": question,
            "num_results": len(results)
        })

    return {
        "documents": state.get("documents", []) + all_documents,  # 累积
        "search_history": state.get("search_history", []) + search_records,  # 累积
        "iteration": iteration + 1,
        "messages": [{"role": "tool", "content": f"搜索完成,找到 {len(all_documents)} 份文档"}]
    }

def evaluate_sufficiency(state):
    """评估信息是否充分"""

    documents = state["documents"]
    sub_questions = state["sub_questions"]

    prompt = f"""
    子问题:{sub_questions}
    已收集文档数量:{len(documents)}
    文档概览:{documents[:3]}  # 只看前 3 份

    判断:信息是否足够回答所有子问题?

    请回答:
    - sufficient(充分)
    - need_more_data(需要更多数据)

    如果需要更多数据,请说明缺少哪方面的信息。
    """

    response = llm.invoke(prompt)

    if "sufficient" in response.lower():
        return {
            "info_sufficient": True,
            "messages": [{"role": "assistant", "content": "信息充分,开始分析"}]
        }
    else:
        # 提取缺失的信息
        missing_info = extract_missing_info(response)

        return {
            "info_sufficient": False,
            "sub_questions": [missing_info],  # 更新子问题为缺失的部分
            "messages": [{"role": "assistant", "content": f"信息不足,需要补充:{missing_info}"}]
        }

def comprehensive_analysis(state):
    """综合分析,生成报告"""

    documents = state["documents"]
    original_query = state["original_query"]

    prompt = f"""
    原始查询:{original_query}

    已收集资料(共 {len(documents)} 份):
    {format_documents(documents)}

    请基于这些资料,撰写一份结构化的研究报告。

    报告结构:
    1. 概述
    2. 详细分析(分点阐述)
    3. 总结

    长度:1500-2000 字。
    """

    report = llm.invoke(prompt)

    return {
        "report_draft": report,
        "messages": [{"role": "assistant", "content": "报告生成完成"}]
    }

def human_feedback_node(state):
    """等待用户反馈"""
    return {"status": "awaiting_feedback"}

def process_user_feedback(state):
    """处理用户反馈"""

    feedback = state.get("user_feedback", "")

    if not feedback or "满意" in feedback:
        return {"task_complete": True}

    # 提取用户想要深入的方向
    prompt = f"""
    用户反馈:"{feedback}"

    用户想要深入了解哪个方面?请提取关键词作为新的搜索方向。
    """

    new_direction = llm.invoke(prompt)

    return {
        "sub_questions": [new_direction],  # 新的搜索方向
        "task_complete": False,
        "messages": [{"role": "user", "content": feedback}]
    }

# 3. 路由函数

def route_after_search(state):
    """搜索后的路由"""

    # 达到最大迭代次数
    if state["iteration"] >= 5:
        return "max_iterations_force_analyze"

    # 评估信息充分性
    return "evaluate"

def route_after_evaluate(state):
    """评估后的路由"""

    if state["info_sufficient"]:
        return "analyze"  # 信息够了,去分析
    else:
        return "search"  # 信息不够,继续搜索

def route_after_feedback(state):
    """反馈后的路由"""

    if state.get("task_complete"):
        return "finish"
    else:
        return "search"  # 用户要深入,重新搜索

# 4. 构建图

checkpointer = MemorySaver()
graph = StateGraph(ResearchState)

# 添加所有节点
graph.add_node("understand", query_understanding)
graph.add_node("search", parallel_search)
graph.add_node("evaluate", evaluate_sufficiency)
graph.add_node("analyze", comprehensive_analysis)
graph.add_node("feedback", human_feedback_node)
graph.add_node("process_feedback", process_user_feedback)

# 入口
graph.set_entry_point("understand")

# 理解 → 搜索
graph.add_edge("understand", "search")

# 搜索 → 评估
graph.add_conditional_edges(
    "search",
    route_after_search,
    {
        "evaluate": "evaluate",
        "max_iterations_force_analyze": "analyze"  # 强制进入分析
    }
)

# 评估 → 分析 or 继续搜索
graph.add_conditional_edges(
    "evaluate",
    route_after_evaluate,
    {
        "analyze": "analyze",
        "search": "search"  # 循环!
    }
)

# 分析 → 人工反馈
graph.add_edge("analyze", "feedback")

# 反馈 → 处理
graph.add_edge("feedback", "process_feedback")

# 处理反馈 → 结束 or 继续搜索
graph.add_conditional_edges(
    "process_feedback",
    route_after_feedback,
    {
        "finish": END,
        "search": "search"  # 又循环回去!
    }
)

# 编译
research_agent = graph.compile(checkpointer=checkpointer)

# 5. 使用

# 第一轮:开始研究
config = {"configurable": {"thread_id": "research_001"}}

state1 = research_agent.invoke({
    "original_query": "量子计算的最新进展",
    "iteration": 0,
    "documents": [],
    "search_history": [],
    "messages": []
}, config)

# Agent 自动执行:
#   understand → search → evaluate → search(循环)→ evaluate → analyze → feedback
# 在 feedback 节点暂停

print(state1["report_draft"])
# 输出:生成的报告

# 第二轮:用户看了报告,提出反馈
state2 = research_agent.invoke({
    "user_feedback": "能否深入讲讲商业应用?特别是金融领域"
}, config)

# Agent 继续执行:
#   process_feedback → search(新方向)→ evaluate → analyze → feedback
# 再次暂停

print(state2["report_draft"])
# 输出:更新后的报告(增加了金融应用部分)

# 第三轮:用户满意
state3 = research_agent.invoke({
    "user_feedback": "满意,谢谢"
}, config)

# Agent 执行:process_feedback → END
# 完成!

流程可视化:

                understand
                    ↓
┌──────────────→ search ←──────────────┐
│                   ↓                   │
│               evaluate                │
│                   ↓                   │
│         ┌────────┴────────┐          │
│         ↓                 ↓          │
│   info_sufficient?    info_lacking   │
│         ↓                 ↓          │
│      analyze              │          │
│         ↓                 └──────────┘
│      feedback
│         ↓
│   process_feedback
│         ↓
│   ┌─────┴─────┐
│   ↓           ↓
│ finish    continue
│   ↓           │
│  END          │
└───────────────┘

关键特性:

  1. 多轮搜索循环:信息不够就继续搜,最多 5 轮
  2. 动态调整:根据评估结果决定是继续搜索还是分析
  3. 人机交互:生成报告后暂停,等待用户反馈
  4. 迭代改进:用户要求深入,Agent 重新搜索和分析
  5. 状态保持:所有搜索历史、文档、迭代次数都在 State 中

Part 5:最佳实践与设计原则

原则 1:节点的单一职责

❌ 错误示例:一个节点做太多事

def mega_node(state):
    """又搜索又分析又生成,什么都干"""

    # 搜索
    search_results = search_api(state["query"])

    # 分析
    analysis = llm.invoke(search_results)

    # 格式化
    formatted = format_output(analysis)

    # 保存到数据库
    save_to_db(formatted)

    return {"result": formatted}

# 问题:
#   - 难以测试(一个节点太多逻辑)
#   - 难以复用(搜索逻辑和分析逻辑耦合)
#   - 难以调试(出错了不知道哪一步有问题)
#   - 难以优化(无法单独优化搜索性能)

✅ 正确示例:每个节点只做一件事

def search_node(state):
    """只负责搜索"""
    results = search_api(state["query"])
    return {"search_results": results}

def analyze_node(state):
    """只负责分析"""
    analysis = llm.invoke(state["search_results"])
    return {"analysis": analysis}

def format_node(state):
    """只负责格式化"""
    formatted = format_output(state["analysis"])
    return {"formatted_output": formatted}

def save_node(state):
    """只负责保存"""
    save_to_db(state["formatted_output"])
    return {"saved": True}

# 优点:
#   ✓ 每个节点可以独立测试
#   ✓ 逻辑清晰,易于理解
#   ✓ 可以灵活组合(跳过格式化?只改 graph,不改节点)
#   ✓ 容易找到性能瓶颈(哪个节点慢?)

原则 2:State 的最小化

❌ 错误示例:State 包含所有东西

class BlogState(TypedDict):
    # 必要的
    original_query: str
    analysis: str

    # 不必要的(调试信息)
    debug_log: List[str]
    function_call_times: dict
    memory_usage: float

    # 不必要的(可以计算得出)
    word_count: int  # 可以从 analysis 计算
    char_count: int  # 可以从 analysis 计算

    # 不必要的(临时变量)
    temp_buffer: str
    intermediate_step_1: str
    intermediate_step_2: str

# 问题:
#   - State 太大,序列化慢
#   - 检查点占用空间大
#   - 难以理解(哪些字段是关键的?)

✅ 正确示例:只存必要信息

class ResearchState(TypedDict):
    # 只存真正需要在节点间传递的信息

    # 用户输入
    query: str

    # 搜索结果(必须保存,后续节点要用)
    documents: List[str]

    # 分析结果
    analysis: str

    # 控制流状态
    iteration: int
    task_complete: bool

# 临时变量在节点内部处理,不放 State
def analyze_node(state):
    documents = state["documents"]

    # 这些是临时变量,不需要放 State
    word_count = sum(len(doc.split()) for doc in documents)
    temp_summary = summarize(documents)

    # 只返回最终结果
    analysis = llm.invoke(temp_summary)
    return {"analysis": analysis}

原则 3:防御性编程

必须的安全保护:

def should_continue(state):
    """完善的循环控制"""

    # 1. 最大迭代次数(防止死循环)
    if state["iteration"] >= 10:
        logger.warning("达到最大迭代次数")
        return "max_iterations"

    # 2. 超时控制
    elapsed = time.time() - state["start_time"]
    if elapsed > 300:  # 5 分钟
        logger.warning(f"执行超时:{elapsed}秒")
        return "timeout"

    # 3. 成本控制
    if state["llm_token_used"] > 100000:  # 10 万 tokens
        logger.warning("Token 用量超限")
        return "cost_limit"

    # 4. 错误检测
    if state["consecutive_failures"] >= 3:
        logger.error("连续失败 3 次")
        return "error_handler"

    # 5. 任务完成
    if state["task_complete"]:
        return "finish"

    # 继续执行
    return "continue"

异常处理节点:

def error_handler_node(state):
    """统一的错误处理"""

    error_type = state.get("error_type")
    error_message = state.get("error_message")

    logger.error(f"错误类型:{error_type},信息:{error_message}")

    # 降级策略
    if error_type == "search_api_failure":
        # API 失败,尝试备用搜索
        return {"use_fallback_search": True}

    elif error_type == "llm_timeout":
        # LLM 超时,使用更小的模型
        return {"use_smaller_model": True}

    elif error_type == "data_insufficient":
        # 数据不足,返回部分结果
        return {"partial_result": state.get("partial_data")}

    else:
        # 未知错误,转人工
        return {"transfer_to_human": True}

# 在图中添加错误处理路径
graph.add_node("error_handler", error_handler_node)

# 从任何节点都可能跳转到错误处理
graph.add_conditional_edges(
    "search",
    lambda state: "error" if state.get("error") else "continue",
    {
        "continue": "analyze",
        "error": "error_handler"
    }
)

Part 6:LangGraph vs 其他框架

与 LangChain 的关系

LangChain = 基础设施
LangGraph = 工作流引擎

关系:
  - LangGraph 基于 LangChain 构建
  - LangGraph 的节点可以使用 LangChain 的组件
  - LangGraph 是 LangChain 生态的一部分

类比:
  LangChain = 零件库(prompts、models、tools)
  LangGraph = 装配线(把零件组装成复杂机器)

何时用哪个:

  LangChain(简单任务):
    "翻译这段话"
    "总结这篇文章"
    "回答这个问题"
    → 用 Chain 就够了

  LangGraph(复杂任务):
    "研究这个课题,如果信息不足就继续搜"
    "分析数据,发现异常就深入调查"
    "多轮对话,根据用户反馈调整"
    → 需要 Graph 的灵活性

  混合使用:
    graph.add_node(
        "summarize",
        LangChainNode(summarize_chain)  # 在 Graph 中用 Chain
    )

与 AutoGPT 的对比

AutoGPT(完全自主):
────────────────────────────────
理念:
  "给 LLM 一个目标,让它自己规划和执行"

优点:
  - 极度自主(用户只需要说目标)
  - 能处理开放性任务

缺点:
  - 不可控(可能偏离目标)
  - 成本不可预测(可能无限循环)
  - 难以调试(不知道它在想什么)
  - 不适合生产环境

适合:
  研究和探索(AGI 的尝试)


LangGraph(人类规划):
────────────────────────────────
理念:
  "人类设计工作流,LLM 执行节点"

优点:
  - 可控(流程是人定义的)
  - 成本可预测(最多执行多少步)
  - 易于调试(每个节点都清晰)
  - 生产就绪(稳定可靠)

缺点:
  - 需要人工设计图结构
  - 自主性较低(按流程执行)

适合:
  生产环境的 Agent 应用


本质区别:
────────────────────────────────
AutoGPT:
  AI 决定"做什么"和"怎么做"

LangGraph:
  人类决定"做什么"(工作流设计)
  AI 决定"怎么做"(节点内的决策)

类比:
  AutoGPT = 给孩子一个任务,让他自己想办法
  LangGraph = 给员工一个流程,让他按流程执行

哪个更好?
  看场景:
    探索未知领域 → AutoGPT(更大胆)
    完成具体任务 → LangGraph(更靠谱)

Part 7:总结与展望

LangGraph 的核心价值

回到文章开头的那个困境:当你的 Agent 需要思考、尝试、回退、再重来时,简单的 Chain 已经不够用了。

LangGraph 解决的核心问题:

"一次性执行""迭代探索"
  Chain:A → B → C,执行完就结束
  Graph:A → B → 判断 → C or 回到 A,持续探索

从"固定流程""动态路径"
  Chain:路径固定,无法改变
  Graph:根据情况选择路径,灵活应对

从"黑盒执行""透明可控"
  Chain:执行过程不可见
  Graph:每个节点、每次判断都清晰可见

从"一次性对话""持续交互"
  Chain:执行后就没了
  Graph:支持暂停、恢复、人机协作

三个关键认知

1. Graph 不是银弹

LangGraph 增加了灵活性,但也增加了复杂度

权衡:
  简单任务("翻译这段话")
    → 用 Chain,别过度设计

  中等复杂任务("多轮搜索分析")
    → 用 LangGraph,物有所值

  超复杂任务("完全自主的 AGI")
    → LangGraph 也不够,需要更高层的架构

记住:
  能用 Chain 解决的,不要用 Graph
  需要 Graph 才能解决的,果断用 Graph

2. Agent = 人机协作,不是人类替代

LangGraph 的设计理念:

人类的角色:
  - 定义任务的结构(有哪些步骤)
  - 设计执行流程(什么顺序、什么条件)
  - 提供工具(LLM 可以调用什么)
  - 监控和干预(关键决策点人工确认)

AI 的角色:
  - 理解自然语言
  - 做出判断(选择哪条路径)
  - 生成内容
  - 与工具交互

这不是"让 AI 替代人",而是"让 AI 辅助人"

3. 从 Chain 到 Graph 是思维方式的转变

Chain 思维(流水线思维):
  "按顺序做 A、B、C"
  线性、确定、简单

Graph 思维(工作流思维):
  "先做 A,然后根据结果选择:
    - 如果满足条件 X → 做 B
    - 如果满足条件 Y → 做 C
    - 如果条件 Z → 回到 A 重试
   持续执行,直到达成目标"

  非线性、动态、灵活

需要:
  - 更好的规划能力(设计图结构)
  - 更多的测试(覆盖所有路径)
  - 更强的调试能力(追踪状态变化)

未来展望

方向 1:AI 辅助的图设计

当前:人工设计图结构

未来:AI 辅助设计
  用户:"我要一个能分析财报的 Agent"
  AI 设计师:"建议流程是:
    1. 搜索财报 → 2. 预处理 → 3. 分析 → 4. 生成报告
    其中第 3 步如果发现异常,需要回到第 1 步补充数据"

  生成 LangGraph 代码
  人工审核、微调
  部署上线

方向 2:自我优化

当前:静态的图结构

未来:从执行历史中学习
  "过去 100 次执行,第 5 步通常需要 3 轮循环才够
   → 自动调整:从第 1 步就多搜一点"

  "用户经常在报告生成后要求深入某方面
   → 自动优化:生成报告时先预判可能的追问,提前准备"

  强化学习 + 图结构优化

方向 3:多模态 Agent

当前:主要处理文本

未来:融合视觉、语音、文本
  "分析这家公司"
  → 节点 1:搜索财报(文本)
  → 节点 2:分析产品图片(视觉)
  → 节点 3:观看 CEO 访谈视频(视频+语音)
  → 节点 4:综合分析