LangGraph 体系化课程 总览 1 原理内核 2 基础实例 3 进阶 4 实战案例 5 面试计划
第 2 章 · 基础实例 · 每个概念配最小可跑代码

第2章 · 基础实例

不堆 API、不抄文档。这份教程只讲一件事:怎么把一个 Agent 想成一张会循环的状态图,并亲手把它搭出来。

读法:概念配最小可跑代码,每段都能复制到本地跑。建议边读边敲——LangGraph 是"手感型"框架,看十遍不如跑一遍。

0 · 为什么是 LangGraph(而不是 Chain / AgentExecutor)

很多人学 LangChain 第一步就走错路:学了一堆 LLMChainAgentExecutor——它们正在被淘汰。现代建 Agent 的正确姿势是 LangGraph。先搞清它解决了什么问题。

传统的"链"(Chain)是一条直线:输入 → A → B → C → 输出。但真实的 Agent 需要:

  • 循环:想一步、调个工具、看结果、再想一步……直到任务完成。直线结构天然做不到回头。
  • 条件分支:根据模型输出决定"该调工具还是直接回答"。
  • 状态:多步之间要记住已经发生了什么(对话历史、中间结果)。
  • 可控:老 AgentExecutor 是个黑盒,你很难在中间插手、暂停、人工审批。

LangGraph 把 Agent 建模成一张 有状态的图(Stateful Graph):节点是"做事的步骤",边是"下一步去哪",而且边可以指回去形成循环。这正好对上了 Agent 的本质。

🔍 一句话定位

LangChain Core 给你"积木"(模型、提示、工具),LangGraph 给你"把积木拼成会循环的 Agent 的骨架"。两者配合用,不是二选一。

1 · 核心心智模型:Agent 就是一张状态图

这一节是全篇地基。把这四个词刻进脑子,后面所有代码都是它们的组合。

概念是什么类比
State 状态一个贯穿全程的"共享数据包",每个节点读它、改它白板:谁都能看、谁都能写
Node 节点一个函数,接收 State、干点事、返回要更新的字段流水线上的一个工位
Edge 边规定"这个节点跑完去哪个节点"工位之间的传送带
Conditional Edge 条件边根据当前 State 动态决定下一步去哪(if/else)带岔路的传送带

运行时,LangGraph 做的事极其简单:从 START 出发,执行当前节点 → 节点返回的更新合并进 State → 沿着边走到下一个节点 → 直到走到 END。循环就是"边指回了前面的节点"。

┌─────────────────────────────────────────┐ │ │ ┌──▼───┐ ┌──────────┐ 条件边 ┌──────┐ │ ──►│ START│────►│ agent │──────┬─────►│ END │ │ └──────┘ │(LLM思考) │ │ └──────┘ │ └──────────┘ │ 需要工具? │ ▼ 是 │ ┌──────────┐ │ │ tools │────────────┘ │(执行工具)│ 结果写回 State,回到 agent └──────────┘

上面这张图就是一个最经典的 ReAct Agent:agent 节点思考 → 条件边判断要不要调工具 → 要就去 tools 节点 → 工具结果写回 → 回到 agent 再想……不需要工具了就走 END。整篇教程都在把这张图一点点搭出来。

2 · 环境准备(含第一天就开 trace)

# 安装核心包(以 OpenAI 为例,换别的模型就换对应集成包)
pip install langgraph langchain langchain-openai

# 环境变量
export OPENAI_API_KEY="sk-..."

# 强烈建议:第一天就接 LangSmith,能看到图内部每一步在干啥
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="ls-..."   # smith.langchain.com 免费注册
💡 为什么强调 trace

LangGraph 是多步循环,光看 print 很难知道"模型第二轮到底想了啥、为什么调了那个工具"。LangSmith 把每个节点的输入输出画成时间线,是学习期最值钱的辅助——它把黑盒变透明

3 · 第一个图:从 0 手搭一个最小 Graph

先不碰工具、不碰循环。我们搭一个最简单的图:一个节点,调用模型回答问题。目的是把 StateGraph 的五步骨架跑通。

from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI

# ① 定义 State:这个图里要传递的数据。messages 用特殊 reducer(下一节细讲)
class State(TypedDict):
    messages: Annotated[list, add_messages]

llm = ChatOpenAI(model="gpt-4o-mini")

# ② 定义节点:就是个普通函数,收 State,返回要更新的字段(dict)
def chatbot(state: State):
    return {"messages": [llm.invoke(state["messages"])]}

# ③ 建图,挂节点
builder = StateGraph(State)
builder.add_node("chatbot", chatbot)

# ④ 连边:START → chatbot → END
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)

# ⑤ 编译成可运行对象
graph = builder.compile()

# 跑!
result = graph.invoke({"messages": [{"role": "user", "content": "用一句话解释 LangGraph"}]})
print(result["messages"][-1].content)
💡 记住这五步

定义 State → 写节点函数 → add_node → add_edge → compile。无论图多复杂,骨架永远是这五步。后面只是往里加节点和条件边。

⚠️ 节点返回的是"更新",不是"全量"

节点 return {"messages": [...]} 时,返回的是要合并进 State 的增量,不是替换整个 State。怎么合并?由 State 字段上的 reducer 决定——这正是下一节的核心。

4 · State 与 Reducer:整个框架的灵魂

如果只能记一个 LangGraph 的核心概念,就记 Reducer。它决定"节点返回的更新,如何合并进 State"。

看回上面那行:messages: Annotated[list, add_messages]。这里 add_messages 就是一个 reducer。它的含义是:当节点返回新的 messages 时,不要覆盖,而是追加到已有列表后面。

对比:有没有 reducer 的区别

写法合并行为
foo: str默认覆盖——新值直接替换旧值
history: Annotated[list, add_messages]追加——把新消息接到历史后面(对话场景必须这样)
count: Annotated[int, operator.add]累加——每次节点返回的数字相加

为什么这事关键?因为 Agent 在循环里会反复经过同一个节点,你需要"积累"信息(对话越聊越长),而不是每轮把前面冲掉。Reducer 就是控制"积累 vs 覆盖"的开关。

import operator
from typing import Annotated
from typing_extensions import TypedDict

class State(TypedDict):
    messages: Annotated[list, add_messages]   # 追加:对话历史
    step_count: Annotated[int, operator.add]  # 累加:走了几步
    final_answer: str                          # 覆盖:最终答案,只保留最新
🔍 深入:State 让多节点协作变干净

没有共享 State 时,你得手动把上个函数的输出当参数传给下个函数,层层透传,很快就乱。LangGraph 让所有节点都读写同一个 State——节点之间解耦,只通过"白板"沟通。这也是它能优雅支持循环、分支、并行的根本原因。

5 · 条件边与循环:让图学会"自己决定下一步"

直线图没意思。Agent 的灵魂是"根据当前情况决定去哪"。这靠 add_conditional_edges 实现。

条件边 = 一个路由函数。它读 State,返回一个字符串(下一个节点的名字)。LangGraph 按这个名字决定往哪走。

from langgraph.graph import StateGraph, START, END

# 路由函数:看最后一条消息,决定继续还是结束
def route(state: State) -> str:
    last = state["messages"][-1]
    # 如果模型要求调用工具,就去 "tools" 节点;否则结束
    if hasattr(last, "tool_calls") and last.tool_calls:
        return "tools"
    return END

builder.add_conditional_edges(
    "agent",            # 从哪个节点出发判断
    route,               # 路由函数
    {"tools": "tools", END: END}  # 返回值 → 目标节点的映射(可省略)
)

# 关键:tools 跑完,边指回 agent —— 这一条就是"循环"!
builder.add_edge("tools", "agent")
START ──► agent ──► route() ──┬── "tools" ──► tools ──┐ │ │ └── END │ agent ◄───────────────────────────────────┘ (循环回去,再想一轮)
⚠️ 循环要有"出口"

因为图能循环,理论上可能无限转圈。务必保证路由函数在某个条件下返回 END。生产里还会用 graph.invoke(inputs, {"recursion_limit": 25}) 设一个步数上限做兜底,防失控。

6 · 工具调用:ToolNode 让模型动手

Agent 之所以强,是因为它能调工具(搜索、算数、查数据库)。LangGraph 内置了 ToolNode,帮你省掉手写"解析 tool_calls → 执行 → 把结果包成消息"的样板代码。

from langchain_core.tools import tool
from langgraph.prebuilt import ToolNode, tools_condition

# ① 用 @tool 定义工具,docstring 会被模型当成"使用说明"
@tool
def get_weather(city: str) -> str:
    """查询某个城市的天气。"""
    return f"{city} 今天晴,26°C"

tools = [get_weather]

# ② 把工具"绑"给模型,模型才知道有哪些工具可用
llm_with_tools = llm.bind_tools(tools)

def agent(state: State):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}

# ③ ToolNode 自动执行模型要求的工具调用
tool_node = ToolNode(tools)

builder = StateGraph(State)
builder.add_node("agent", agent)
builder.add_node("tools", tool_node)
builder.add_edge(START, "agent")
# tools_condition 是官方预置的路由:有 tool_calls 就去 tools,否则去 END
builder.add_conditional_edges("agent", tools_condition)
builder.add_edge("tools", "agent")
graph = builder.compile()
💡 tools_condition = 上一节路由函数的官方版

第 5 节我们手写了 route()。LangGraph 直接给了你一个等价的预置版 tools_condition——绝大多数 ReAct 场景直接用它就行,不用自己写。理解了手写版,你就知道它内部在干嘛。

7 · 完整 ReAct Agent:手搭 vs 一行预置

把前面所有零件拼起来,你就有了一个能思考、会调工具、自动循环的完整 Agent。其实第 6 节的代码已经是了。这里给你官方的一行版,以及"什么时候该用哪个"。

一行版:create_react_agent

from langgraph.prebuilt import create_react_agent

# 一行得到一个完整 ReAct Agent(内部就是第6节那张图)
agent = create_react_agent(llm, tools=[get_weather])

result = agent.invoke({"messages": [{"role": "user", "content": "北京天气怎么样?"}]})
print(result["messages"][-1].content)
场景用哪个
快速验证想法 / 标准 ReAct 流程create_react_agent 一行搞定
要自定义节点、加审批、改循环逻辑、多 Agent手搭 StateGraph(前 6 节)
🔍 学习建议:两个都写一遍

先用 create_react_agent 跑通找手感,再用 StateGraph 手搭一个等价的——你会真正理解"那一行背后到底是什么"。这正是面试时能讲清原理的关键,也是你 JD 里"懂核心架构"的体现。

8 · 记忆与持久化:Checkpointer + thread_id

到这里 Agent 还是"一次性"的——每次 invoke 都从零开始,不记得上次聊了啥。要做多轮对话,需要 Checkpointer(检查点)

原理:每走完一步,LangGraph 把当前 State 存一份快照(checkpoint),用 thread_id 标识是哪个会话。下次带同样的 thread_id 进来,它自动把历史 State 加载回来接着聊。

from langgraph.checkpoint.memory import MemorySaver

# 开发用内存版;生产换 SqliteSaver / PostgresSaver 落盘
memory = MemorySaver()
graph = builder.compile(checkpointer=memory)

# 用 config 里的 thread_id 区分不同会话
config = {"configurable": {"thread_id": "user-42"}}

graph.invoke({"messages": [{"role":"user","content":"我叫小镰刀"}]}, config)
# 第二次,同一个 thread_id,它记得你的名字
r = graph.invoke({"messages": [{"role":"user","content":"我叫什么?"}]}, config)
print(r["messages"][-1].content)   # → 你叫小镰刀
🔍 Checkpointer 不只是"记忆"

它存的是每一步的完整 State 快照,所以还顺带解锁了三个高级能力:① 断点续跑(崩了能从上一步恢复);② 时间旅行(回到任意历史步重跑);③ 下一节的人工介入。这是 LangGraph 比裸框架强的核心竞争力之一。

9 · 人工介入(Human-in-the-loop)

真实业务里,Agent 在执行"危险操作"(转账、删数据、发邮件)前,往往需要人点头。LangGraph 让你在图的任意位置暂停,等人工批准再继续

最简单的方式:编译时声明"在某个节点前先停下"。

# 在进入 tools 节点前暂停,把控制权交回给你
graph = builder.compile(
    checkpointer=memory,
    interrupt_before=["tools"]   # 执行工具前先停
)

config = {"configurable": {"thread_id": "t1"}}
graph.invoke({"messages":[{"role":"user","content":"帮我删除日志"}]}, config)

# 此刻图停在 tools 前。你检查它想干啥:
state = graph.get_state(config)
print(state.next)  # → ('tools',) 说明它正等着调工具

# 人工确认 OK → 传 None 让它从断点继续
graph.invoke(None, config)
💡 新版 API:interrupt()

较新的 LangGraph 还提供在节点内部调用 interrupt(value) 主动暂停、并用 Command(resume=...) 把人工输入送回去的写法,更灵活。先掌握 interrupt_before 这个简单版,再升级。

10 · 流式输出:看 Agent 一步步在想什么

invoke 你只拿到最终结果。用 stream,你能实时看到每个节点跑完后的状态——调试和做"打字机效果"都靠它。

# stream_mode="values":每步后吐出完整 State
for chunk in graph.stream(
        {"messages":[{"role":"user","content":"北京天气?"}]},
        config, stream_mode="values"):
    chunk["messages"][-1].pretty_print()
stream_mode吐什么用途
"values"每步后的完整 State看整体进展
"updates"每步只吐"哪个节点改了啥"调试节点行为
"messages"逐 token 流式前端打字机效果

11 · 观测与评测:对接"自进化"

这一节直接对应你 JD 里的"反思 / 数据飞轮 / 效果调优"。LangGraph + LangSmith 是落地自进化的天然组合。

① Trace:把循环看透

第 2 节配好 LANGCHAIN_TRACING_V2 后,每次运行都会在 LangSmith 里生成一条完整时间线:每个节点的输入输出、模型每轮的 token 消耗、调了哪些工具、为什么。排查"Agent 为什么绕圈/调错工具",看 trace 几秒钟定位。

② Eval:用数字说话

# 思路:建一个评估集,跑 Agent,用 LLM-as-judge 打分,量化成功率
from langsmith import Client
from langsmith.evaluation import evaluate

dataset = [
    {"input":"北京天气?", "expect_tool":"get_weather"},
    {"input":"1+1?",      "expect_tool": None},  # 不该乱调工具
]
# 对每条 case 跑 agent → 判断行为是否符合预期 → 算通过率
# 改 prompt / 工具描述前后各跑一次,对比成功率 = A/B 调优
🔍 这就是"数据飞轮"的起点

把每次运行的 trajectory 留下来 → 挑失败案例 → 改进 prompt/工具/路由 → 用同一评估集验证是否真的变好。这个闭环跑起来,你的 Agent 就"越用越好"——正是 JD 里 LearningLoop 的核心,也是你最该在简历上量化的东西("成功率从 X 提到 Y")。

12 · 多智能体:Supervisor 模式

单个 Agent 工具一多就容易"选择困难"。把任务拆给多个专精 Agent、由一个"主管"调度,往往更稳。这就是多智能体。

最常用的是 Supervisor(主管)模式:一个 supervisor 节点根据当前任务,决定把活派给哪个子 Agent(研究员 / 写手 / 审核),子 Agent 干完把结果交回 supervisor,它再决定下一步——本质还是"条件边 + 循环",只是节点变成了一个个 Agent。

┌───────────────┐ START ────► │ supervisor │ ◄─────────┐ │ (决定派给谁) │ │ └───┬───┬───┬────┘ │ │ │ │ │ ┌─────▼┐ ┌▼────┐ ┌▼─────┐ │ │研究员│ │ 写手│ │ 审核 │───────┘ └──────┘ └─────┘ └──────┘ 交回结果
💡 别一上来就上多 Agent

多 Agent 调试成本高、容易失控。先把单 Agent(前 11 节)吃透,确实遇到"一个 Agent 扛不住"再拆。官方有 langgraph-supervisor 预置库,理解原理后直接用。

13 · 常见坑 & 最佳实践

怎么破
还在学老的 AgentExecutor/LLMChain直接学 LangGraph,老 API 已是遗留
State 字段忘了加 reducer,历史被覆盖对话/累积类字段必须配 add_messagesoperator.add
循环停不下来 / 报 recursion 错路由函数留 END 出口 + 设 recursion_limit
工具不被调用 / 调错工具 docstring 写清楚(模型靠它判断),并确认 bind_tools
多轮对话不记事compile 时传 checkpointer,invoke 时带 thread_id
不知道 Agent 在干嘛开 LangSmith trace,别靠 print 猜

14 · 学习路径 Checklist

按顺序打勾,每一项都"亲手敲一个能跑的 demo"才算过。

  • 跑通最小 Graph(第 3 节五步骨架)
  • 给 State 加一个 operator.add 字段,理解 reducer
  • 手写一个条件边,做一个会循环的图(第 5 节)
  • ToolNode + tools_condition 让模型调一个自定义工具
  • create_react_agent 跑一遍,再用 StateGraph 手搭等价版
  • MemorySaver,做一个记得住名字的多轮对话
  • interrupt_before,做一个"工具执行前要人工批准"的流程
  • 接 LangSmith,看一条完整 trace
  • 建 10 条评估集,做一次"改前 vs 改后"的成功率对比(自进化起点)
  • 读官方 LangChain Academy 的 LangGraph 课,补齐细节
💡 配套资源

① 官方文档 langchain-ai.github.io/langgraph ② 免费课 LangChain Academy(专教 LangGraph,质量高)③ 学每一节时把 Claude 当陪练:让它 review 你的图、出题考你、模拟面试官追问。

学完这份 checklist,你就具备了 JD 里"懂 Agent 核心架构 + 主流框架 + 自进化落地"的硬实力。下一步:把你某个 launchd 任务用 LangGraph 重写成带评测闭环的真 Agent,作为面试案例。