你学过 LangChain,现在来搞懂 LangGraph 的灵魂架构。
为什么要"构建图"?这和 Agent、LLM 又有什么关系?
你学过 LangChain,已经知道怎么调用 LLM、拼接 Chain、使用 Tool。LangChain 的核心模型是"流水线":输入 → 处理步骤 A → 处理步骤 B → 输出,线性地往下走。
| 维度 | LangChain (Chain) | LangGraph (Graph) |
|---|---|---|
| 执行流程 | 线性 / 顺序 | 图结构,可以分支、循环、回退 |
| 状态管理 | 靠开发者手动传递变量 | 内置 State 对象,自动流转 |
| 条件判断 | 需要在代码里手写 if/else | Conditional Edge,图结构原生支持 |
| 循环 / 迭代 | 实现复杂,易出 bug | 图天然支持环(loop),Agent 循环是第一等公民 |
| 适合场景 | 简单问答、RAG、固定步骤任务 | 复杂 Agent、多步推理、人机协作、有记忆的对话 |
LangGraph 是 LangChain 的"超集":它不替代 LangChain,而是在 LangChain 上层提供了一个可控的、有状态的执行引擎。你在 LangGraph 的 Node 里仍然可以调用 LangChain 的 Chain、LLM、Tool。
LangChain 解决了"怎么调用 LLM"的问题,LangGraph 解决了"LLM 调用的流程控制和状态管理"的问题。当你的 AI 应用需要"根据上一步结果决定下一步做什么",你就需要 LangGraph。
什么是"图"?在计算机科学中,图(Graph)由节点(Node)和边(Edge)组成。LangGraph 借用了这个概念:
想象你在规划一段旅程:
这就是 Module 1 那个 notebook 的完整逻辑:node_1 执行后,根据某个条件(随机/天气/用户输入),决定走 node_2 还是 node_3,最终都到达 END。
当你的 Agent 需要循环调用工具(搜索 → 判断是否够 → 再搜索)、并行执行(同时查多个数据源)、或者人工介入中断(Human-in-the-loop),用 if/else 写代码会迅速变成意大利面条。图结构让这些复杂流程可视化、可测试、可维护。
State 是一个 Python 字典(TypedDict),它在整张图的执行过程中一直存在,从 START 到 END,每个 Node 都可以读取它、修改它。你可以把它理解为节点之间传递的"便签纸"或"共享白板"。
from typing_extensions import TypedDict
# 定义 State 的"模板":图里有哪些共享数据
class State(TypedDict):
graph_state: str # 这张图只有一个字段:graph_state
在这个最简单的例子里,State 只有一个字段 graph_state,类型是字符串。但在真实 Agent 里,State 可以包含:
每个 Node 接收当前 State,做完处理后返回一个部分更新的字典,LangGraph 会自动把这个更新合并进 State:
默认情况下,Node 返回的值会覆盖掉 State 中对应字段的旧值。如果你想要追加(比如 messages 列表),需要使用 Reducer。这会在后面的 Module 2 讲到。
from langchain_core.messages import BaseMessage
from typing import Annotated, List
from langgraph.graph.message import add_messages
class AgentState(TypedDict):
# Annotated + add_messages 表示"追加"而不是"覆盖"
messages: Annotated[List[BaseMessage], add_messages]
# 其他字段可以根据需要添加
user_query: str
search_results: list
iteration_count: int
这是 LangGraph 最优雅的设计之一:Node 就是一个普通的 Python 函数,没有任何魔法。它只有一个约定:
# 这就是一个完整的 Node!
def node_1(state):
print("---Node 1---")
# 读取 State 中的值
current = state['graph_state']
# 返回要更新的字段
return {"graph_state": current + " I am"}
def node_2(state):
print("---Node 2---")
return {"graph_state": state['graph_state'] + " happy!"}
def node_3(state):
print("---Node 3---")
return {"graph_state": state['graph_state'] + " sad!"}
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
def call_llm(state):
# 从 State 里取消息历史
messages = state["messages"]
# 调用 LLM(这里就用到了 LangChain!)
response = llm.invoke(messages)
# 把 LLM 回复追加到 messages
return {"messages": [response]}
from langchain_community.tools import TavilySearchResults
search_tool = TavilySearchResults()
def search_web(state):
query = state["user_query"]
results = search_tool.invoke(query)
return {"search_results": results}
Node 是"纯粹的业务逻辑",它不知道自己是图的一部分,也不知道下一步去哪儿。它只管读 State、做事、更新 State。谁去哪儿,是 Edge 决定的。
Edge 连接 Node,告诉图"执行完这个节点之后,下一步去哪儿"。LangGraph 有两种 Edge:
始终从 A 走到 B,没有条件判断。
# 总是从 node_2 走到 END
builder.add_edge("node_2", END)
builder.add_edge("node_3", END)
# 总是从 START 走到 node_1
builder.add_edge(START, "node_1")
根据一个路由函数的返回值,决定下一步去哪个 Node。路由函数接受 State,返回下一个 Node 的名字。
import random
from typing import Literal
# 路由函数:根据 State 决定下一步
def decide_mood(state) -> Literal["node_2", "node_3"]:
# 可以根据 state 里的任何信息来决定
if random.random() < 0.5:
return "node_2" # 走 happy 路线
return "node_3" # 走 sad 路线
# 把路由函数挂到 node_1 之后
builder.add_conditional_edges("node_1", decide_mood)
def should_continue(state) -> Literal["tools", "__end__"]:
messages = state["messages"]
last_message = messages[-1]
# 如果 LLM 要调用工具 → 去 tools 节点
if last_message.tool_calls:
return "tools"
# 否则 LLM 已经给出最终答案 → 结束
return "__end__"
# 这就构成了 Agent 的"思考→工具→思考→工具→…→结束"循环
builder.add_conditional_edges("agent", should_continue)
你在 LangChain 里用过的 AgentExecutor,其底层就是这样一个循环:LLM 节点 → 条件判断(要不要调用工具)→ 工具节点(如果要)→ 回到 LLM 节点。LangGraph 让这个循环透明、可控、可定制。
StateGraph 是 LangGraph 的核心类,它是一个"图的构建器"(Builder Pattern)。你把所有 Node 和 Edge 的定义"告诉"它,最后 compile() 生成一个可以运行的图。
StateGraph(State) — 这确定了图中所有节点共享的数据结构模板
builder.add_node("名字", 函数) — "名字"是在图里引用这个节点的标识符,"函数"是实际执行的逻辑
普通边用 add_edge,需要判断条件的路线用 add_conditional_edges
LangGraph 在这一步做基本的正确性检查(比如有没有孤立节点),返回一个 CompiledGraph 对象
graph.invoke({"graph_state": "输入"}) — 传入初始 State,图从 START 开始执行,返回最终 State
START 和 END 是 LangGraph 内置的两个特殊节点:
invoke() 时,从 START 连接的第一个 Node 开始执行。它让你可以用 add_edge(START, "my_first_node") 来明确声明"从哪里开始"现在来逐行分析 notebook 里那段你"完全摸不着头脑"的代码:
from IPython.display import Image, display
from langgraph.graph import StateGraph, START, END
# ① 创建一个图的"蓝图",使用 State 作为共享数据结构
builder = StateGraph(State)
# ② 注册三个节点("标识符名" → Python函数)
builder.add_node("node_1", node_1) # node_1 函数处理第一步
builder.add_node("node_2", node_2) # node_2 = happy 路线
builder.add_node("node_3", node_3) # node_3 = sad 路线
# ③ 定义固定边:START → node_1(图从 node_1 开始)
builder.add_edge(START, "node_1")
# ④ 定义条件边:node_1 执行完后,调用 decide_mood() 决定去 node_2 还是 node_3
builder.add_conditional_edges("node_1", decide_mood)
# ⑤ 定义固定边:无论走哪条路,最终都到 END
builder.add_edge("node_2", END)
builder.add_edge("node_3", END)
# ⑥ 编译:校验图的结构,生成可执行的 CompiledGraph
graph = builder.compile()
# ⑦ 可视化(仅用于学习/调试,生产代码里不需要)
display(Image(graph.get_graph().draw_mermaid_png()))
执行这段代码后,图的形状如下:
# 传入初始 State,运行图
result = graph.invoke({"graph_state": "Hi, this is Lance."})
# 可能的输出 1(走 node_2):
# ---Node 1---
# ---Node 2---
# {'graph_state': 'Hi, this is Lance. I am happy!'}
# 可能的输出 2(走 node_3):
# ---Node 1---
# ---Node 3---
# {'graph_state': 'Hi, this is Lance. I am sad!'}
完整的 State 演变过程:
| 阶段 | graph_state 的值 | 发生了什么 |
|---|---|---|
| invoke 调用时 | "Hi, this is Lance." | 你传入的初始 State |
| node_1 执行后 | "Hi, this is Lance. I am" | node_1 追加了 " I am" |
| decide_mood 路由 | (不修改 State) | 返回 "node_3"(假设这次随机到了 sad) |
| node_3 执行后 | "Hi, this is Lance. I am sad!" | node_3 追加了 " sad!" |
| 到达 END | "Hi, this is Lance. I am sad!" | 图执行完毕,返回最终 State |
这不只是示例。StateGraph + Node + Edge 这套模式就是 LangGraph 的全部基础。从最简单的 chatbot 到最复杂的多 Agent 系统,都是用这套模式搭建的。
Module 1 用"随机选择 happy/sad"只是为了屏蔽 LLM 的复杂性,让你先理解图的流程控制机制。在后续 Module 里,你会看到:
| Module | 在图里加入了什么 | 对应场景 |
|---|---|---|
| Module 1 | LLM 作为 Node;Tool 作为 Node;条件 Edge 判断是否调用工具 | 完整的 ReAct Agent |
| Module 2 | State 里加 messages 列表;Reducer 做追加而非覆盖 | 有记忆的对话 chatbot |
| Module 3 | 图执行中途暂停,等待人工审批 | Human-in-the-loop |
| Module 4 | 多个 Node 并行执行;子图(Sub-graph) | 研究助手,同时查多个数据源 |
| Module 5 | 图连接外部记忆存储(Memory Store) | 跨对话的长期记忆 Agent |
LangGraph 的 StateGraph 模式被 LinkedIn、Elastic、Uber 等公司的 AI 团队用于生产系统中。这个入门示例教你的,是这些生产系统的共同骨架。
现在把 Module 1 的简单示例,升级成一个真正调用 LLM 和工具的 Agent,你会发现结构完全相同:
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import HumanMessage
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from typing_extensions import TypedDict, Annotated
from langgraph.graph.message import add_messages
from typing import Literal
# ─── Step 1: 定义 State(比 Module 1 更丰富)───
class AgentState(TypedDict):
messages: Annotated[list, add_messages] # 追加而非覆盖
# ─── Step 2: 准备 LLM 和工具 ───
tools = [TavilySearchResults(max_results=2)]
llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools(tools) # 告诉 LLM 有哪些工具可用
# ─── Step 3: 定义 Nodes(和 Module 1 结构完全一样!)───
def call_llm(state: AgentState):
# Node:调用 LLM,LLM 会决定是直接回答还是调用工具
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
# ─── Step 4: 定义路由函数(就是 Module 1 的 decide_mood)───
def should_continue(state: AgentState) -> Literal["tools", END]:
last_msg = state["messages"][-1]
if last_msg.tool_calls: # LLM 决定要调用工具
return "tools"
return END # LLM 给出了最终答案
# ─── Step 5: 构建图(和 Module 1 完全一样的模式!)───
builder = StateGraph(AgentState)
builder.add_node("agent", call_llm)
builder.add_node("tools", ToolNode(tools)) # 内置的工具执行节点
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", should_continue)
builder.add_edge("tools", "agent") # 工具执行完回到 LLM!这就是循环
agent = builder.compile()
# ─── Step 6: 运行 ───
result = agent.invoke({
"messages": [HumanMessage(content="2024年奥运会在哪里举行?")]
})
注意 add_edge("tools", "agent") 这一行:它把 tools 节点的输出连回了 agent 节点,形成了一个循环。这就是 Agent 反复"思考→工具→思考→工具"直到得出答案的机制。LangChain 的 AgentExecutor 在底层就是这个图,只是以前你看不到它。
Module 1 的学习重点不是代码细节,而是建立这个心智模型:
Module 1 的这张"简单图"就是你以后所有 LangGraph 代码的骨架。无论多复杂的 Agent,都不过是:定义 State → 写 Node 函数 → 用 Edge 连起来 → compile → invoke。理解了这个模式,你就理解了 LangGraph 的全部基础。