LangGraph 学习笔记

从"LangChain Agent 不够用了"出发 —— 理解 StateGraph、条件边、循环、多 Agent 协作和 Memory

LangChain Agent 的五大痛点

你已经学会了 LangChain LCEL(线性管道 A|B|C),但当你想构建"真正自主的 Agent"时:

痛点具体表现根因
流程硬编码 拆解→搜索→总结→报告的步骤写死,无法动态调整 线性管道无分支
没有状态管理 中间结果在函数内部,函数结束就丢了,无法追踪 Agent 干了什么 无显式 State
条件逻辑脆弱 if len(report) < 100: retry 硬编码检查,无法适应所有场景 逻辑散落各处
循环难实现 "搜索→不满意→改关键词再搜→满意→继续" 需要手写 while + 退出条件 无原生循环机制
多角色无法建模 Research / Write / Review 三个 Agent 怎么交接?手写函数调用顺序 无调度框架
一句话:LangChain LCEL 擅长"线性 Pipeline",但不擅长有循环、有分支、有状态、有多角色的复杂工作流

答案:用"图"来建模 Agent 工作流

LangChain LCEL(线性管道)
A
B
C
D
数据从左到右,一步到底,不能回头
LangGraph(状态图)
搜索
结果够了?
✓ 够了
总结报告
✗ 不够
⟲ 回到搜索(循环)

图 vs 管道的本质区别

LangChain LCELLangGraph
结构线性管道 A|B|C有向图(节点+边)
流程控制固定顺序动态路由(条件边)
循环不支持天然支持
状态隐式(管道内传递)显式(State TypedDict)
可观测只能看最终输出每个节点的输入输出都可追踪
多角色需要手动编排通过 State 天然共享

生动类比

🏭

LangChain LCEL = 工厂流水线

产品从 A 站 → B 站 → C 站,固定路线,不能回头。适合标准化、重复性任务。

🏢

LangGraph = 办公室协作

经理(Supervisor)分配任务,员工(Agent)干活,干完交回经理,不满意打回重做(循环!)。

State:图的"共享内存"

State 是整个图的数据载体。所有节点都能读写它。定义为一个 TypedDict

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

class AgentState(TypedDict):
    messages: Annotated[list, add_messages]   # 追加模式:新消息不会覆盖旧消息
    search_results: str                        # 覆盖模式:新值直接替换旧值
    final_answer: str

两个关键概念:Reducer(归约器)

add_messages(追加模式)

节点 A 返回 {messages: [msg1]},节点 B 返回 {messages: [msg2]}
→ State.messages = [msg1, msg2](追加,不覆盖)

普通字段(覆盖模式)

节点 A 返回 {search_results: "结果A"},节点 B 返回 {search_results: "结果B"}
→ State.search_results = "结果B"(后写的覆盖先写的)

直觉理解:State = Agent 的"工作台"。每个节点(工人)从工作台上拿东西,做完放回去。图(Graph)定义了工人之间的协作流程。

Node:图的执行单元

每个 Node 是一个 Python 函数,接收 State,返回要更新的字段:

def search_node(state: AgentState) -> dict:
    # 1. 读取 State
    query = state["messages"][-1].content

    # 2. 做处理...
    result = search_knowledge_base(query)

    # 3. 返回要更新的字段(不直接修改 State!)
    return {
        "search_results": result,
        "messages": [AIMessage(content=result)],
    }

规则 1

Node 不直接修改 State——返回一个 dict,框架负责合并

规则 2

返回值只包含要更新的字段——不需要返回整个 State

规则 3

一个 Node 只做一件事——搜索的只搜索,总结的只总结

直觉理解:Node = 办公室里的一个"岗位"。搜索员看到工作台上有问题 → 搜索 → 把结果放回工作台。写完就放回去,不关心谁接着用。

State + Node 协同工作示意图

State(工作台)在节点之间流转
AgentState
messages: [HumanMsg("什么是...")]
search_results: ""
final_answer: ""
search_node
↓ 返回 {search_results: "...", messages: [...]}
AgentState(已更新)
messages: [HumanMsg("..."), AIMsg("...")]
search_results: "LangGraph 是..."
final_answer: ""
answer_node
AgentState(最终)
messages: [..., AIMsg("LangGraph 是一个...")]
search_results: "LangGraph 是..."
final_answer: "LangGraph 是一个构建..."

Edge(普通边):固定路线

A 做完后必然去 B。就像工厂的固定传送带。

builder.add_edge("search", "answer")   # 搜索完 → 必然去回答
builder.add_edge("answer", END)        # 回答完 → 结束
START
search
answer
END

Conditional Edge(条件边):动态路由

A 做完后,根据 State 内容动态决定下一步去向。就像分拣机,检查标签后分流。

def router(state: AgentState) -> str:
    # 看 State 决定去哪——这就是"动态决策"
    if state.get("search_satisfied"):
        return "answer"    # 满意 → 去回答
    else:
        return "search"    # 不满意 → 重新搜索(循环!)

builder.add_conditional_edges(
    "search", router,
    {"answer": "answer", "search": "search"}
)
search
router(state)
满意 →
answer
END
不满意 →
search
⟲ 循环

Memory 的两个层次

层次 1:Short-term Memory(State)

一次 invoke() 内,State 在节点间传递

例:搜索结果 → 总结 → 报告
都在一次调用内完成

State TypedDict

层次 2:Long-term Memory(Checkpointer)

跨多次 invoke(),状态持久化

例:第1轮研究"AI趋势"→ 第2轮追问"详细说说第2点"→ Agent 记得上一轮结果

MemorySaver / SqliteSaver

Checkpointer 使用

from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)

# 每次调用传入 thread_id(标识一次对话)
config = {"configurable": {"thread_id": "conversation_1"}}

# 第1轮
graph.invoke({"messages": [HumanMessage(content="研究AI趋势")]}, config)

# 第2轮(同一个 thread_id,Agent 记得上一轮的所有中间状态!)
graph.invoke({"messages": [HumanMessage(content="详细说说第2点")]}, config)
直觉理解:
Short-term Memory (State) = 一次会议中的白板——会议结束就擦掉
Long-term Memory (Checkpointer) = 会议纪要存档——下次开会可以翻出来看
thread_id = 不同会议的编号——"项目A的第3次讨论"

Demo 1:简单工作流(问题 → 搜索 → 回答)

这是 LangGraph 的 "Hello World"——最简单的线性工作流。

START
search
搜索知识库
answer
生成回答
END

State 设计(2 字段)

class Demo1State(TypedDict):
    messages: Annotated[list, add_messages]
    search_results: str

构建图(3 条边)

builder = StateGraph(Demo1State)
builder.add_node("search", search_node)
builder.add_node("answer", answer_node)
builder.add_edge(START, "search")
builder.add_edge("search", "answer")
builder.add_edge("answer", END)
graph = builder.compile()
演示内容:State 定义(2 字段)、Node 定义(2 个节点)、线性边(无分支、无循环)。
和手写 pipeline 功能相同,但 LangGraph 版本可观测、可扩展

手写版 vs LangGraph 版

手写 pipeline
def ask_question(query):
    results = search(query)      # 步骤1
    answer = llm.invoke(          # 步骤2
        f"基于{results}回答{query}")
    return answer

# 痛点:
# - 中间状态不可见
# - 加一步要改整个函数
# - 没有错误处理
LangGraph
graph = build_demo1()
result = graph.invoke({
    "messages": [HumanMessage(content=q)],
    "search_results": "",
})

# 优势:
# - graph.stream() 可看每一步
# - 加节点只需 add_node + add_edge
# - 框架提供错误边界

Demo 2:研究 Agent(拆解 → 搜索 → 总结 → 报告)

真实的研究任务需要多步骤协作——每步的输出是下一步的输入。

START
decompose
LLM拆解问题
search
逐个子问题搜索
summarize
综合搜索结果
report
生成结构化报告
END

State 字段随流程"生长"

main_question sub_questions[] search_results summary final_report

每个节点的输出被下一个节点读取——State 就是"交接单"。

与 Demo 1 的关键区别

Demo 1Demo 2
节点数24
复杂度一问一答拆解 → 搜索 → 总结 → 报告
State 字段25(每步一个产出)
LLM 调用次数1 次(仅回答)4 次(拆解/总结/报告 + 搜索逻辑)
LLM 角色仅回答拆解、总结、报告——LLM 作为"处理器"
核心洞察:Demo 2 展示了 State 作为"交接单"的模式——decompose 写入 sub_questions → search 读取 sub_questions 写入 search_results → summarize 读取 search_results 写入 summary → report 读取 summary 写入 final_report。

Demo 3:多 Agent 协作系统

Supervisor 模式 + Reviewer 循环 + Memory —— 这是 LangChain 做不到的。

START
Supervisor
项目经理
↓ ↙         ↓         ↘
Research
研究员
搜索+分析
Writer
写手
撰写报告
Reviewer
审核员
质量检查
↖         ↑         ↗
完成后都回到 Supervisor(循环)

典型执行流程

👤 User: "写一篇 AI 趋势报告"
→ 🧑‍💼 Supervisor: "先派 Research Agent 查资料"
→ 🔬 Research Agent: 搜索 + 分析 → 研究结果写入 State
→ 🧑‍💼 Supervisor: "资料有了,派 Writer 写报告"
→ ✍️ Writer Agent: 基于研究结果撰写 → 草稿写入 State
→ 🧑‍💼 Supervisor: "报告写好了,派 Reviewer 审核"
→ 🔍 Reviewer Agent: 审核质量
✅ 通过 → FINISH
❌ 打回 → Supervisor 派 Writer 修改(循环!)
这就是 LangChain 做不到的部分:审核不通过 → 打回 Writer 重写 → 再审核。这是一个循环,LangChain LCEL 无法建模。

关键设计决策

为什么用 Supervisor 而不是并行?

并行执行:研究、写作、审核同时进行
→ 但写作需要研究结果、审核需要写作结果 → 不能并行!

Supervisor 模式:按依赖顺序调度
→ 研究完成 → 写作 → 审核

为什么需要 Reviewer?

没有 Reviewer:Writer 写什么就是什么,质量不可控
→ 可能输出格式混乱、遗漏关键信息

有 Reviewer:独立的质量关卡
→ 通过/打回 + 具体修改意见 → 质量闭环

防无限循环

revision_count 追踪修改次数
达到上限(如 3 次)→ 强制 FINISH
→ 即使不完全满意也结束,避免死循环

Memory:跨对话记忆

第1轮:研究 AI 趋势 → 搜索 → 写报告
第2轮:追问"详细说说第2点"
→ Agent 记得上一轮的所有中间状态
不需要重新搜索!

三个 Demo 的演进路径

1
简单工作流
2 节点线性
State + Node + Edge
2
研究 Agent
4 节点线性
多步骤 + State 流转
3
多 Agent 系统
Supervisor 模式
循环 + Memory

什么时候用哪个?

你的应用流程是?
➡️
线性流程
RAG、翻译、摘要
LangChain LCEL
🔄
有循环
搜索→不满意→再搜
LangGraph
🔀
动态分支
技术问题→技术Agent
LangGraph
👥
多角色协作
研究→写作→审核
LangGraph

两者配合使用(不是替代关系!)

LangGraph 内部大量使用 LangChain 的组件。LangChain 提供"积木块"(Prompt、LLM、Retriever、Tool...),LangGraph 提供"图纸"(循环、条件、状态图)。
# LangGraph 的 Node 里用 LangChain 的组件
def researcher_node(state):
    from langchain_openai import ChatOpenAI
    from langchain_core.prompts import ChatPromptTemplate

    llm = ChatOpenAI(...)                          # LangChain 组件
    prompt = ChatPromptTemplate.from_messages([...]) # LangChain 组件
    chain = prompt | llm | StrOutputParser()        # LangChain LCEL

    return {"research_findings": chain.invoke(state)}

# LangGraph 负责"什么时候调用谁"(流程控制)
# LangChain 负责"怎么调用"(组件实现)

生产环境五大注意事项

1. 防止无限循环

class State(TypedDict):
    revision_count: int

def supervisor(state):
    if state["revision_count"] > 3:
        return "FINISH"  # 强制结束

2. 错误处理

def safe_node(state):
    try:
        result = risky_op(state)
        return {"output": result}
    except Exception as e:
        return {"messages": [AIMessage(
            content=f"节点失败: {e}")]}

3. 超时控制

def search_with_timeout(state):
    signal.alarm(30)  # 30 秒超时
    try:
        result = search(state)
        signal.alarm(0)
        return result
    except TimeoutError:
        return {"search_results": "超时"}

4. Checkpointer 选择

类型场景
MemorySaver开发/测试,重启丢失
SqliteSaver单机部署,持久化
PostgresSaver生产环境,多实例

5. 可观测性:stream() 模式

逐步观察每个节点的输入输出——这是手写 pipeline 做不到的:

for step in graph.stream(initial_state):
    for node_name, output in step.items():
        print(f"[{node_name}] {output}")

# 输出示例:
# [search] {'search_results': 'LangGraph 是...'}
# [answer] {'messages': [AIMessage('LangGraph 是一个构建...')]}
每个节点的输入输出都可追踪 → 调试 Agent 行为不再是"黑盒"。

核心收获

LangGraph 做了什么?

  • 把流程控制 → 图结构可视化
  • 把状态管理 → 显式 State 类型
  • 把条件路由 → Conditional Edge + Router 函数
  • 把循环逻辑 → 图的天然能力
  • 把多角色协作 → Supervisor + Agent 模式
  • 把跨对话记忆 → Checkpointer + thread_id

核心原则

  • LangGraph 不是替代 LangChain——是互补
  • 线性任务用 LangChain LCEL,有循环/分支用 LangGraph
  • State 是核心——定义清楚"数据长什么样"就成功了一半
  • 先画图再写代码——图结构清晰了,代码自然清晰
一句话总结:LangGraph 的价值 = 让流程控制"可视化"和"可编程"。不是写死 A→B→C,而是定义"在什么状态下、谁去干什么、干完去哪"。

配套资源

资源说明
langgraph_guide.md完整教学指南(11 章)
langgraph_demo.py3 个可运行 Demo(python langgraph_demo.py
006-langchain-learning前置知识:LangChain LCEL、Tool Calling