一、为什么要学 LangGraph?
1.1 LangChain 的局限:线性流程不够用了
如果你用过 LangChain,你一定写过这样的代码:
chain = prompt_template | model | output_parser
result = chain.invoke({"topic": "人工智能"})
这种写法非常优雅,但它隐含了一个前提:流程是固定的、单向的、无环的。数据从起点流到终点,不会回头,也不会中途分叉。
但现实中的 AI 应用往往更复杂:
- 智能客服:需要先判断用户意图,然后走不同的流程分支
- 代码生成 Agent:写完代码 → 跑测试 → 发现 bug → 修改代码 → 再跑测试(循环)
- 金融审批系统:需要在关键节点暂停,等人工审核员确认后再继续
这些场景需要的不是一个线性管道,而是一个有状态、可循环、可中断的执行引擎——这就是 LangGraph 诞生的原因。
1.2 LangChain vs LangGraph:互补关系
| LangChain | LangGraph | |
|---|---|---|
| 核心模型 | 线性管道(Pipeline) | 有向图(Graph) |
| 流程方向 | 单向、固定 | 可循环、可分叉 |
| 状态管理 | 无状态,数据流过即消失 | 有状态,节点共享数据 |
| 控制流 | 固定顺序 | 条件分支、循环、中断 |
| 适用场景 | 简单的 LLM 调用链 | 复杂的 Agent、多轮对话、工作流 |
一句话总结:LangChain 让你快速跑起来,LangGraph 让你跑得复杂、跑得可控、跑得持久。两者是互补关系——你用 LangChain 的组件(模型、工具、检索器)作为积木,用 LangGraph 作为把这些积木粘合在一起的蓝图。
1.3 什么时候该用 LangGraph?
如果你遇到以下任何一个问题,就该考虑从 LangChain 升级到 LangGraph:
- 你的 Agent 需要"反思" :写完代码后自己检查,发现 bug 后重新修改
- 你的流程需要"分叉" :根据用户的输入,走完全不同的处理路径
- 你的任务需要"暂停等人" :在某个节点停下来,等领导审批通过后再继续
- 你的系统需要"断点续跑" :服务器重启后,Agent 能从上次中断的地方继续
- 你有多个 Agent 需要协作:一个负责规划,一个负责执行,一个负责质检
二、LangGraph 三大核心要素
LangGraph 的所有能力都建立在三个核心概念之上:State(状态) 、Node(节点) 、Edge(边) 。理解这三者,你就掌握了 LangGraph 的 80%。
2.1 State(状态):图流转的"公共黑板"
什么是 State?
State 是 LangGraph 的灵魂。它是一份全局共享的数据结构,图中的每一个节点都可以读取它、修改它。
你可以把 State 想象成一块挂在墙上的公共黑板:
- 所有人(节点)都能看到黑板上写了什么
- 每个人都可以在上面添加新内容
- 后一个人能看到前一个人写下的所有内容
如何定义 State?
在 LangGraph 中,State 通常用 Python 的 TypedDict来定义:
from typing import TypedDict, List, Annotated
from langgraph.graph.message import add_messages
import operator
class AgentState(TypedDict):
# 消息列表:使用 add_messages Reducer,实现追加而非覆盖
messages: Annotated[List, add_messages]
# 步骤计数器:使用 operator.add,每次累加
step: Annotated[int, operator.add]
# 用户意图:普通字段,会被直接覆盖
intent: str
# 是否已完成:布尔标志
is_complete: bool
什么是 Annotated?
Annotated是 Python 标准库 typing模块里的一个工具,它的作用是给类型提示附加额外的元信息。
对于 Python 本身来说,Annotated几乎不做任何事情——它只是给类型标注"贴了个标签"。但 LangGraph 会读取这个标签,从中提取出第二个参数(比如 add_messages),把它当作这个字段的更新策略。
from typing import Annotated
# 语法:Annotated[实际类型, 额外信息1, 额外信息2, ...]
x: Annotated[int, "这是一个年龄字段"]
Reducer(归约器)
Reducer 是一个函数,它定义了"当多个节点都想修改同一个 State 字段时,如何把这些修改合并成一个最终结果"。
默认情况下,节点返回的状态会覆盖旧状态。但在很多场景中,我们需要的是追加或累加。
# 不使用 Reducer:覆盖行为
state["messages"] = ["你好"] # 第一次设置
state["messages"] = ["世界"] # 第二次设置 → 覆盖了"你好"
# 使用 add_messages Reducer:追加行为
state["messages"] = ["你好"] # 第一次设置
state["messages"] = ["世界"] # 第二次设置 → 结果是 ["你好", "世界"]
LangGraph 内置了几种常用的 Reducer:
add_messages:消息列表追加(最常用)operator.add:数值累加或列表拼接operator.set:集合合并
你也可以自定义 Reducer 函数,实现任何你想要的合并逻辑。
2.2 Node(节点):图里的"执行工人"
什么是 Node?
Node 就是图中的一个个执行单元。每个节点都是一个普通的 Python 函数,它接收当前的 State,执行一些业务逻辑,然后返回一个状态更新字典。
def my_node(state: AgentState) -> dict:
"""
参数 state:当前的全局状态
返回值 dict:要更新的状态字段
"""
# 读取当前状态
last_message = state["messages"][-1] if state["messages"] else ""
# 执行业务逻辑(比如调用 LLM)
# response = llm.invoke(last_message)
# 返回要更新的状态
return {
"messages": ["这是节点处理后的回复"],
"step": 1, # 由于 Reducer 是 operator.add,实际效果是 step += 1
}
节点的设计原则
- 无副作用:理想的节点应该只通过返回值修改 State,不要直接操作文件、数据库或全局变量
- 单一职责:一个节点只做一件事。比如"调用 LLM"是一个节点,"格式化输出"是另一个节点
- 可观测:节点的输入(State)和输出(State 更新)都应该清晰可辨
将节点添加到图中
from langgraph.graph import StateGraph
workflow = StateGraph(AgentState)
workflow.add_node("llm_call", my_node) # 节点名称 + 节点函数
workflow.add_node("tool_execute", tool_node)
workflow.add_node("format_output", format_node)
2.3 Edge(边):控制流的"神经脉络"
如果说 Node 是图中的"名词",那么 Edge 就是图中的"动词"——它决定了节点之间的连接关系和执行顺序。
普通边(Normal Edge)
普通边定义的是确定性的先后顺序:A 执行完之后,一定执行 B。
workflow.add_edge(START, "llm_call") # 入口 → 调用模型
workflow.add_edge("llm_call", "tool_execute") # 顺序执行
workflow.add_edge("tool_execute", END) # 结束
这段代码的含义是:START → llm_call → tool_execute → END,一个标准的线性流程。
条件边(Conditional Edge)
条件边是 LangGraph 真正的杀手锏。它允许你根据当前 State的内容,动态决定下一步去哪一个节点。
def router_function(state: AgentState) -> str:
"""返回值是目标节点的名称"""
last_message = state["messages"][-1].content if state["messages"] else ""
if "需要查资料" in last_message:
return "search_tool" # 去搜索节点
elif "需要计算" in last_message:
return "calculator_tool" # 去计算节点
elif "已完成" in last_message:
return END # 结束
else:
return "llm_call" # 继续对话(循环回 LLM 节点)
workflow.add_conditional_edges(
"llm_call", # 源节点
router_function, # 路由函数
{
"search_tool": "search_tool",
"calculator_tool": "calculator_tool",
END: END,
"llm_call": "llm_call",
}
)
条件边的威力:它让图具备了动态决策能力。同样的输入,可能走完全不同的路径;同一个节点,可以被反复访问(形成循环)。
START 与 END
LangGraph 预定义了两个特殊节点:
START:图的入口点。所有从外部invoke()传入的数据,都会首先到达 START 节点END:图的终止点。当执行流到达 END 时,图执行完毕,返回最终 State
注意:你必须至少有一条路径从 START 出发,也必须至少有一条路径到达 END,否则编译会报错。
三、完整示例:带反思机制的 Agent
下面展示从零构建一张可运行图的完整流程。
from typing import TypedDict, List, Annotated
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
import operator
# ===== Step 1: 定义 State =====
class AgentState(TypedDict):
messages: Annotated[List, add_messages] # 消息历史(追加)
reflection_count: Annotated[int, operator.add] # 反思次数(累加)
max_reflections: int # 最大反思次数(覆盖)
# ===== Step 2: 定义 Node 函数 =====
llm = ChatOpenAI(model="gpt-4o-mini")
def call_model(state: AgentState) -> dict:
"""调用 LLM 生成回复"""
response = llm.invoke(state["messages"])
return {"messages": [response]}
def should_continue(state: AgentState) -> str:
"""条件路由:判断是否需要继续反思"""
if state["reflection_count"] >= state.get("max_reflections", 3):
return END
last_message = state["messages"][-1]
if isinstance(last_message, AIMessage):
if "让我再想想" in last_message.content:
return "reflect"
return END
def reflect_node(state: AgentState) -> dict:
"""反思节点:对之前的回答进行自我审查"""
reflection_prompt = f"""
请对你之前的回答进行反思,找出可能的不足或遗漏。
如果需要改进,请给出更好的版本。
原始回答:{state['messages'][-1].content}
"""
response = llm.invoke([HumanMessage(content=reflection_prompt)])
return {"messages": [response], "reflection_count": 1}
# ===== Step 3: 构建图 =====
workflow = StateGraph(AgentState)
workflow.add_node("call_model", call_model)
workflow.add_node("reflect", reflect_node)
workflow.add_edge(START, "call_model")
workflow.add_conditional_edges(
"call_model",
should_continue,
{"reflect": "reflect", END: END}
)
workflow.add_edge("reflect", "call_model") # 反思后回到模型调用(形成循环)
# ===== Step 4: 编译图 =====
app = workflow.compile()
# ===== Step 5: 运行图 =====
initial_state = {
"messages": [HumanMessage(content="帮我写一首关于秋天的诗,要有意境")],
"reflection_count": 0,
"max_reflections": 2,
}
result = app.invoke(initial_state)
print(result["messages"][-1].content)
执行流程:
START → call_model → should_continue
│
┌─────┴─────┐
│ │
reflect END
│
└──→ call_model(循环)
四、总结
| 核心概念 | 说明 | 类比 |
|---|---|---|
| State | 全局共享的数据结构 | 公共黑板 |
| Node | 执行单元,读取并更新 State | 工人 |
| Edge | 控制节点间的执行顺序 | 传送带 |
| Reducer | 定义 State 字段的更新策略 | 合并规则 |
LangGraph 的核心思想:把复杂的 AI 流程拆解成一个个节点,用边把它们连接起来,通过 State 在节点间传递数据,最终形成一个可执行的计算图。
五、相关面试题
面试题1:LangChain 和 LangGraph 的本质区别是什么?
参考答案:
LangChain 的核心模型是线性管道,数据沿着固定的方向单向流动,不支持循环和分支。LangGraph 的核心模型是有向图,支持条件分支、循环、中断等复杂控制流。
打个比方:LangChain 是一条传送带,工件从一头进去从另一头出来;LangGraph 是一个车间,工件可以在不同工位之间来回移动,甚至可以在某个工位前停下来等人。
面试题2:什么是 Reducer?为什么需要它?
参考答案:
Reducer 是定义 State 字段更新策略的函数。当多个节点都想修改同一个字段时,Reducer 决定如何合并这些修改。
需要 Reducer 的原因是:默认情况下,后执行的节点会覆盖先执行节点的修改。但在很多场景中(比如消息列表),我们需要的是追加而不是覆盖。Reducer 让我们可以自定义这种行为。
面试题3:Annotated在 LangGraph 中的作用是什么?
参考答案:
Annotated是 Python 的类型注解工具,用于给类型提示附加额外信息。在 LangGraph 中,它用来给 State 字段绑定 Reducer。
例如 messages: Annotated[List, add_messages]表示:messages字段的类型是 List,更新策略是 add_messages(追加模式)。
面试题4:条件边和普通边有什么区别?
参考答案:
- 普通边:确定性执行,A 完成后一定执行 B
- 条件边:根据当前 State 的内容,动态决定下一步去哪个节点
条件边让图具备了动态决策能力,是实现循环、分支、路由等功能的基础。
面试题5:START 和 END 节点有什么作用?
参考答案:
START:图的入口点,所有外部输入首先到达 STARTEND:图的终止点,执行流到达 END 时图执行完毕
必须至少有一条路径从 START 出发,也必须至少有一条路径到达 END,否则编译会报错。