本文面向:有 Python 基础,学过 LangChain Chain/Agent 但还没接触 LangGraph 的开发者。读完本文,你将清楚地知道:LangGraph 解决什么问题、什么场景用它、它有哪些优缺点,以及如何一步步开发第一个 LangGraph 应用。
前期回顾
写给零基础开发者
先问自己这 4 个问题,如果有 2 个"是",你就需要 LangGraph:
| 问题 | 你的答案 |
|---|---|
| 我的 AI 流程需要根据条件走不同分支吗? | □ 是 □ 否 |
| 我的 AI 任务需要循环执行直到满足条件吗? | □ 是 □ 否 |
| 我需要在关键步骤让人工审核/确认吗? | □ 是 □ 否 |
| 我需要多个 AI 角色互相协作完成任务吗? | □ 是 □ 否 |
答了 2 个以上"是" → 继续读。全是"否" → 用 LangChain Chain 就够了。
一、为什么需要 LangGraph?
1.1 Chain 和 Agent 的局限
在学习 LangGraph 之前,你已经学过:
- Chain:把任务串成流水线,A → B → C,每步固定,不能分支,不能循环
- Agent:LLM 自主决策,循环调用工具,灵活但像"黑箱",难以控制和调试
它们能覆盖大多数场景,但有些问题它们解决不了:
❌ Chain 做不到的事
场景:文章审核流程
要求:AI 审核文章 → 如果有问题则修改 → 修改后再审核 → 直到通过
用 Chain 实现:
审核链 → 修改链 → 审核链 → 修改链 → ...(无限嵌套!怎么循环?)
❌ 问题:Chain 是线性的,不支持循环和条件分支
❌ Agent 做不到的事
场景:资金报销审批
要求:AI 分析报销单 → 金额超过5000元必须人工审批 → 人工批准后才能入账
用 Agent 实现:
create_react_agent(llm, tools=[analyze_tool, approve_tool])
❌ 问题:Agent 自主决策,根本不会等人工!它会直接"假装"已批准后继续执行
❌ 多 Agent 协作做不到的事
场景:AI 写作系统
要求:研究员收集资料 → 写作者撰写草稿 → 审核者评审 → 不通过则发回写作者修改
用单个 Agent 实现:
一个 Agent 需要"扮演"多个角色,提示词复杂,行为难以控制
❌ 问题:单 Agent 难以稳定地模拟多角色协作
1.2 LangGraph 的解决方案
LangGraph 将 AI 工作流表示为有向图(Directed Graph):
- 节点(Node) = Python 函数,处理数据
- 边(Edge) = 固定顺序连接
- 条件边(Conditional Edge) = 根据结果动态选择下一步
- 状态(State) = 所有节点共享的数据(相当于工作流的"白板")
1.3 Chain vs Agent vs LangGraph 对比
| 特性 | Chain | Agent | LangGraph |
|---|---|---|---|
| 执行结构 | 线性,固定顺序 | 动态循环,LLM 决定 | 图状态机,你控制 |
| 循环支持 | ❌ | ✅ (ReAct) | ✅ (显式循环) |
| 条件分支 | ❌ | ✅ (隐式) | ✅ (显式,你定义) |
| 人工介入 | ❌ | ❌ | ✅ |
| 多 Agent 协作 | ❌ | ❌ | ✅ |
| 可调试性 | 高 | 低 | 高 |
| 适合新手 | ✅ | ✅ | 需要一定基础 |
| 灵活性 | 低 | 高 | 最高 |
1.4 LangGraph 的优缺点
✅ 优点:
- 显式控制流:你定义图结构,行为可预测、可调试
- 支持复杂流程:循环、分支、并行、多 Agent 协作都原生支持
- Human-in-the-Loop:可以在任意节点暂停等待人工确认
- 持久化与恢复:通过 Checkpointer 保存状态,支持断点续传
- 流式输出:原生支持流式执行,用户体验好
- 可视化:内置
draw_mermaid()和print_ascii()查看图结构
❌ 缺点:
- 学习曲线:需要理解状态机概念,比 Chain/Agent 复杂
- 代码量更多:简单任务用 LangGraph 反而繁琐
- 过度工程:如果只是线性任务,用 Chain 即可
1.5 什么场景该用 LangGraph?
典型场景举例:
| 场景 | 用 LangGraph 的理由 |
|---|---|
| 代码生成→执行→报错→修复→再执行 | 需要循环,根据执行结果决定是否继续 |
| 文章审核→不合格→修改→再审核 | 需要循环 + 条件分支 |
| 报销单审批(超额需人工批准) | 需要 Human-in-the-Loop |
| 研究员+写作者+审核者协作写文章 | 需要多 Agent 协作 |
| 客服:问题分类→路由到专业客服 | 需要条件分支(路由) |
二、三大核心概念
2.1 State(状态)—— 工作流的"共享白板"
State 是所有节点共享的数据字典,类似于团队协作中大家都能看到和修改的白板。
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
class MyState(TypedDict):
# ─── 普通字段:后写的值覆盖先写的值(像普通变量赋值)───
user_name: str # "Alice"
task_status: str # "pending" / "in_progress" / "done"
result: str # 最终结果
# ─── 特殊字段:使用 Reducer 函数控制更新行为 ───
# add_messages 是一个追加函数:新消息被追加到列表(而不是覆盖)
# 这样可以保留完整的对话历史
messages: Annotated[list, add_messages]
为什么要用 Annotated[list, add_messages]?
# 不带 Annotated(普通字段):
# 节点A 写入 result="第一次结果"
# 节点B 写入 result="第二次结果"
# 最终 state["result"] = "第二次结果"(被覆盖)
# 带 Annotated[list, add_messages](消息字段):
# 节点A 写入 messages=[消息1]
# 节点B 写入 messages=[消息2]
# 最终 state["messages"] = [消息1, 消息2](追加合并)
# ✅ 保留了完整的对话历史
2.2 Node(节点)—— 工作流的"处理步骤"
节点是一个普通的 Python 函数,接收 State,返回要更新的字段。
def my_node(state: MyState) -> dict:
# ─── 从状态中读取数据 ───
user_input = state["messages"][-1].content # 最新消息
current_status = state.get("task_status", "pending")
# ─── 执行业务逻辑(可以调用 LLM、数据库、API 等)───
result = process_input(user_input)
# ─── 返回要更新的字段(只需返回变更的部分,不用返回完整 state)───
return {
"result": result,
"task_status": "done",
# 注意:没有返回的字段保持原值不变
}
重要规则:
- 节点只返回需要更新的字段,不需要返回完整状态
- 节点可以修改任何 State 字段(包括读取其他节点写入的字段)
2.3 Edge(边)—— 工作流的"流转路径"
边决定节点的执行顺序。有两种类型:
from langgraph.graph import StateGraph, START, END
builder = StateGraph(MyState)
# ── 普通边:A 执行完后,一定执行 B ──
builder.add_edge("node_a", "node_b")
# ── 条件边:根据路由函数的返回值决定下一步 ──
def my_router(state: MyState) -> str:
"""路由函数:返回下一个节点的名称。"""
if state["task_status"] == "error":
return "retry_node"
elif state["result"] == "":
return "fallback_node"
else:
return "next_node"
builder.add_conditional_edges(
"node_a", # 源节点
my_router, # 路由函数(返回字符串)
{
"retry_node": "retry_node", # 路由函数返回 "retry_node" → 去这里
"fallback_node": "fallback_node", # 路由函数返回 "fallback_node" → 去这里
"next_node": "next_node", # 路由函数返回 "next_node" → 去这里
}
)
2.4 建图、编译、运行 —— 三步走
# 第1步:构建图
builder = StateGraph(MyState)
builder.add_node("step1", step1_function)
builder.add_node("step2", step2_function)
builder.add_edge(START, "step1") # START 是内置起点
builder.add_edge("step1", "step2")
builder.add_edge("step2", END) # END 是内置终点
# 第2步:编译(生成可执行对象)
graph = builder.compile()
# 第3步:运行(传入初始状态)
result = graph.invoke({
"user_name": "Alice",
"task_status": "pending",
"result": "",
"messages": [],
})
# result 是最终的完整状态字典
print(result["result"])
三、如何可视化验证图结构
这是 LangGraph 最重要的调试技巧之一!在运行图之前,先可视化检查一遍,确保图结构符合预期。
3.1 方式1:ASCII 图(最快,不需要额外依赖)
# 编译图后,直接打印 ASCII 图
graph = builder.compile()
graph.get_graph().print_ascii()
输出示例:
+-----------+
| __start__ |
+-----------+
*
*
*
+---------+
| classify |
+---------+
* *
* *
* *
+------+ +----------+
| math | | knowledge |
+------+ +----------+
* *
* *
* *
+---------+
| __end__ |
+---------+
3.2 方式2:Mermaid 格式(最直观,可在线查看)
mermaid_text = graph.get_graph().draw_mermaid()
print(mermaid_text)
输出的 Mermaid 文本,复制到以下任意工具即可看到精美流程图:
- mermaid.live (最方便,在线编辑器)
- VS Code 插件:"Markdown Preview Mermaid Support"
- GitHub/GitLab:直接在 Markdown 中用
```mermaid代码块
输出示例:
%%{init: {'flowchart': {'curve': 'linear'}}}%%
flowchart TD;
__start__([<p>__start__</p>]):::first
classify([classify])
math([math])
knowledge([knowledge])
__end__([<p>__end__</p>]):::last
__start__ --> classify;
classify -.-> math;
classify -.-> knowledge;
math --> __end__;
knowledge --> __end__;
classDef default fill:#f2f0ff,line-height:1.2
classDef first fill-opacity:0
classDef last fill:#bfb6fc
3.3 方式3:编程方式(用于测试/断言)
# 获取图的结构信息
graph_obj = graph.get_graph()
# 查看所有节点名称
node_names = list(graph_obj.nodes.keys())
print(f"节点列表:{node_names}")
# 查看所有边
for edge in graph_obj.edges:
print(f"边:{edge.source} → {edge.target}")
# 用于写自动化测试
assert "review" in node_names, "缺少 review 节点!"
assert "publish" in node_names, "缺少 publish 节点!"
💡 开发建议:每次构建完图后,先调用
print_ascii()或draw_mermaid()检查一遍,再运行。这样可以快速发现"少连了一条边"或"边的方向反了"等常见错误。
四、5步开发工作流
从需求到可运行代码,建议按这5步走:
第1步:先画图(最重要!)
在写代码前,先用纸/白板画出工作流:
示例:文章审核流程
[开始] → [AI生成草稿] → [人工审核] → 通过 → [发布] → [结束]
↓
不通过 → [AI修改] → [人工审核](循环)
画完后思考:
- 有几个节点?(对应几个函数)
- 有几条条件边?(对应几个路由函数)
- State 需要哪些字段?(节点间传递什么数据)
第2步:定义 State
根据第1步的分析,定义状态类:
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
class ArticleState(TypedDict):
topic: str # 写作主题(只读,不变)
draft: str # AI 生成的草稿(generate节点写,review节点读)
feedback: str # 审核意见(review节点写,revise节点读)
approved: bool # 是否通过审核(review节点写,路由函数读)
final: str # 最终内容(publish节点写)
messages: Annotated[list, add_messages] # 完整历史记录
第3步:实现节点(先 Mock,后接 LLM)
先写不调用 LLM 的 Mock 版本,验证逻辑正确:
# ── Mock 版本:快速验证流程 ──
def generate_node_mock(state: ArticleState) -> dict:
print(f" [生成] 模拟生成关于'{state['topic']}'的草稿")
return {"draft": f"关于{state['topic']}的模拟草稿内容"}
def review_node_mock(state: ArticleState) -> dict:
# 模拟:直接通过
print(" [审核] 模拟审核通过")
return {"approved": True, "feedback": ""}
def publish_node_mock(state: ArticleState) -> dict:
print(" [发布] 模拟发布")
return {"final": state["draft"]}
# ── 真实版本:接入 LLM ──
def generate_node(state: ArticleState) -> dict:
response = llm.invoke([
SystemMessage(content="你是专业写作者,请根据主题创作300字短文"),
HumanMessage(content=f"主题:{state['topic']}")
])
return {"draft": response.content}
第4步:构建图并可视化验证
builder = StateGraph(ArticleState)
# 添加节点
builder.add_node("generate", generate_node)
builder.add_node("review", review_node)
builder.add_node("publish", publish_node)
# 添加边
builder.add_edge(START, "generate")
builder.add_edge("generate", "review")
builder.add_conditional_edges(
"review",
lambda state: "publish" if state["approved"] else END,
{"publish": "publish", END: END},
)
builder.add_edge("publish", END)
# ✅ 编译前先验证图结构
graph = builder.compile()
print("图结构验证:")
graph.get_graph().print_ascii() # 先看 ASCII 验证连通性
print(graph.get_graph().draw_mermaid()) # 再看 Mermaid 确认方向
第5步:测试
# ── 先单独测试节点函数 ──
mock_state = {"topic": "AI", "draft": "", "feedback": "", "approved": False, "final": "", "messages": []}
result = generate_node(mock_state)
assert "draft" in result
assert len(result["draft"]) > 0
print("节点单元测试通过 ✅")
# ── 再做集成测试(用 Mock 节点)──
result = graph.invoke({
"topic": "人工智能",
"draft": "", "feedback": "", "approved": False, "final": "",
"messages": [],
})
assert result["final"] != ""
print("集成测试通过 ✅")
五、代码讲解:基础图
代码文件:lessons/07_langgraph/01_basic_graph.py
示例1:最简单的线性图
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
# ──────────────────────────────────────────────────────
# 第1步:定义状态
# ──────────────────────────────────────────────────────
class SimpleState(TypedDict):
input: str # 用户输入(不变)
processed: str # 预处理后的文本
result: str # 最终结果
# ──────────────────────────────────────────────────────
# 第2步:定义节点函数
# ──────────────────────────────────────────────────────
def preprocess_node(state: SimpleState) -> dict:
"""预处理:去掉首尾空格,转小写"""
text = state["input"]
processed = text.strip().lower()
return {"processed": processed} # 只返回变更的字段
def analysis_node(state: SimpleState) -> dict:
"""分析:统计文本特征"""
text = state["processed"]
result = f"词数: {len(text.split())}, 字符数: {len(text)}"
return {"result": result}
# ──────────────────────────────────────────────────────
# 第3步:构建图
# ──────────────────────────────────────────────────────
builder = StateGraph(SimpleState)
builder.add_node("preprocess", preprocess_node) # 添加节点
builder.add_node("analysis", analysis_node)
builder.add_edge(START, "preprocess") # 添加边
builder.add_edge("preprocess", "analysis")
builder.add_edge("analysis", END)
# ──────────────────────────────────────────────────────
# 第4步:编译并可视化验证
# ──────────────────────────────────────────────────────
graph = builder.compile()
graph.get_graph().print_ascii() # ✅ 先验证图结构
# ──────────────────────────────────────────────────────
# 第5步:运行
# ──────────────────────────────────────────────────────
result = graph.invoke({
"input": " Hello LangGraph World ",
"processed": "",
"result": ""
})
print(result["result"]) # 输出:词数: 3, 字符数: 21
示例2:带条件路由的图
class RoutingState(TypedDict):
question: str # 用户问题
category: str # 分类结果
answer: str # 答案
def classify_node(state: RoutingState) -> dict:
"""分类节点:判断问题类型"""
question = state["question"].lower()
if any(kw in question for kw in ["几", "多少", "计算"]):
category = "math"
elif any(kw in question for kw in ["是什么", "解释", "介绍"]):
category = "knowledge"
else:
category = "general"
return {"category": category}
def math_node(state: RoutingState) -> dict:
return {"answer": f"数学处理:{state['question']}"}
def knowledge_node(state: RoutingState) -> dict:
return {"answer": f"知识回答:{state['question']}"}
def general_node(state: RoutingState) -> dict:
return {"answer": f"通用回答:{state['question']}"}
# ── 路由函数:返回下一个节点名 ──
def route_question(state: RoutingState) -> str:
return state["category"] # "math" / "knowledge" / "general"
builder = StateGraph(RoutingState)
builder.add_node("classify", classify_node)
builder.add_node("math", math_node)
builder.add_node("knowledge", knowledge_node)
builder.add_node("general", general_node)
builder.add_edge(START, "classify")
# 条件边:classify 执行后,根据 route_question 返回值路由
builder.add_conditional_edges(
"classify",
route_question, # 路由函数
{ # 路由函数返回值 → 目标节点
"math": "math",
"knowledge": "knowledge",
"general": "general",
}
)
builder.add_edge("math", END)
builder.add_edge("knowledge", END)
builder.add_edge("general", END)
graph = builder.compile()
result = graph.invoke({"question": "什么是人工智能?", "category": "", "answer": ""})
print(result["answer"]) # 输出:知识回答:什么是人工智能?
运行方式:
python lessons/07_langgraph/01_basic_graph.py
六、代码讲解:Agent 循环图
代码文件:lessons/07_langgraph/02_agent_graph.py
这是 create_react_agent 的底层实现原理,手动构建后你才真正明白 Agent 是怎么运作的。
from langgraph.prebuilt import ToolNode # 自动处理工具调用的内置节点
# 状态:只需要一个 messages 字段(包含完整消息历史)
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
def agent_node(state: AgentState) -> dict:
"""
LLM 推理节点。
接收消息历史,调用绑定了工具的 LLM。
LLM 决定:调用工具?还是给出最终答案?
"""
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
def should_continue(state: AgentState) -> str:
"""
路由函数:判断 LLM 是否要继续调用工具。
- 有 tool_calls → "tools"(继续循环)
- 无 tool_calls → END(任务完成)
"""
last_message = state["messages"][-1]
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tools"
return END
builder = StateGraph(AgentState)
builder.add_node("agent", agent_node) # LLM 推理
builder.add_node("tools", ToolNode(tools)) # 工具执行
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
builder.add_edge("tools", "agent") # 工具执行完后,回到 LLM(形成循环!)
graph = builder.compile()
Agent 循环图的执行流程:
运行方式:
python lessons/07_langgraph/02_agent_graph.py
七、调试技巧
7.1 打印状态变化
在每个节点开头和结尾打印状态,快速定位问题:
def debug_node(state: MyState) -> dict:
print(f"\n[debug_node 进入] state = {state}")
# ... 处理逻辑 ...
result = {"field": "value"}
print(f"[debug_node 退出] 返回 = {result}")
return result
7.2 用 Mock 节点隔离问题
# 测试图结构时,先用不调用 LLM 的 Mock 节点
def mock_generate(state):
return {"draft": "模拟草稿"}
def mock_review(state):
return {"approved": True}
# 确认图流转正确后,再替换成真实的 LLM 节点
graph = builder.compile()
7.3 常见错误对照表
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
KeyError: 'field_name' | State 字段未初始化 | 在 invoke() 时提供所有字段的初始值 |
| 图无限循环 | 条件边路由函数永远不返回 END | 增加循环计数,超过上限强制结束 |
| 节点执行顺序不对 | 边的连接方向错误 | 用 print_ascii() 可视化验证 |
AttributeError: tool_calls | 消息类型检查不完整 | 用 hasattr(msg, "tool_calls") 判断 |
| 路由函数返回了不在映射表里的值 | 返回值和映射表不匹配 | 确保路由函数的所有返回值都在映射字典的 key 中 |
八、小结
本章(上)涵盖了 LangGraph 入门所需的所有知识:
LangGraph 基础
├── 为什么需要? ← Chain 无法循环,Agent 无法控制,LangGraph 解决这些
├── 什么场景用? ← 循环/分支/多角色协作/人工介入
├── 三大概念:
│ ├── State(共享白板) ← TypedDict,用 Annotated[list, add_messages] 追加消息
│ ├── Node(处理函数) ← 接收 State,返回变更字段
│ └── Edge(流转路径) ← 普通边 or 条件边
├── 图可视化: ← print_ascii() 快速看,draw_mermaid() 精美看
├── 5步开发工作流: ← 先画图 → 定义State → Mock节点 → 连图可视化 → 测试
├── 代码:01_basic_graph.py ← 线性图 + 条件路由
└── 代码:02_agent_graph.py ← Agent 循环(LLM + ToolNode)
📌 下篇预告:第07章(下)将讲解 LangGraph 的进阶特性:
- 检查点(Checkpointer):保存执行状态,支持断点续传
- Human-in-the-Loop:在指定节点暂停等待人工确认
- 多 Agent 协作:协调器 + 专业化 Agent 的完整实现
作者:阿聪谈架构
公众号:阿聪谈架构(分享后端架构 / AI / Java 技术文章)
相关代码关注公众号:【阿聪谈架构】 回复:AI专栏代码