引言:一个真实的困境
想象你正在开发一个 AI 研究助手。用户输入:"帮我分析特斯拉 2024 年的竞争力"。
如果用 LangChain 的 Chain,你会这样设计:
# 简单的线性 Chain
chain = (
search_financial_data # ① 搜索财务数据
| analyze_with_llm # ② LLM 分析
| generate_report # ③ 生成报告
)
result = chain.invoke("特斯拉 2024")
看起来很简洁!但现实很快给你上了一课:
执行第①步:搜索财务数据
→ 只找到了 Q1 和 Q2 的数据,Q3、Q4 没找到
执行第②步:LLM 分析
→ LLM 说:"数据不完整,无法分析"
执行第③步:生成报告
→ 生成了一份"数据不足"的报告 ❌
用户看到报告:"这也太敷衍了吧!"
问题出在哪里?
Chain 是线性的:A → B → C,一旦开始就按固定顺序执行完。它没法:
- 检查中间结果:"数据够不够?不够的话再搜一次"
- 条件分支:"如果财报搜不到,就搜新闻报道"
- 循环重试:"分析不够深入,回去补充数据"
- 人机交互:"用户说'再深入看看电动车市场',重新分析"
你真正需要的是:
┌────────────────────────────────────────────┐
│ 智能研究助手的实际工作流 │
└────────────────────────────────────────────┘
① 搜索财报
↓
② 检查数据完整性
├─ 完整 → 继续
└─ 不完整 → 搜索新闻补充 → 回到②
③ LLM 分析
↓
④ 评估分析深度
├─ 足够深 → 生成报告
└─ 不够深 → 生成新问题 → 回到①
⑤ 生成报告
↓
⑥ 用户反馈
├─ 满意 → 结束
└─ 要求深入 → 记录需求 → 回到①
特点:
- 有循环(可以回到前面的步骤)
- 有分支(根据情况选择路径)
- 有交互(等待用户反馈)
- 有状态(记住已搜索的内容、中间结果)
这就是 LangGraph 要解决的问题:用状态图构建真正智能的 Agent 工作流。
Part 1:Chain 的天花板在哪里?
场景 1:多轮对话的客服 Agent
让我们看一个更具体的例子:
用户:"我的订单怎么还没到?"
理想的客服流程:
─────────────────────────────────────
① 理解意图:用户在查询订单状态
② 查询数据库
├─ 找到订单 → 返回物流信息 → 结束 ✓
└─ 没找到 → 继续 ③
③ 澄清信息:"请问您的订单号是多少?"
→ 等待用户输入(暂停执行!)
④ 用户回复:"OH12345"
→ 更新查询条件,重新查询(回到 ②)
⑤ 还是没找到
→ 转人工客服(降级处理)
关键点:
- 第③步需要**暂停**,等待用户输入
- 收到输入后要**恢复**执行
- 第④步要**回到第②步**(循环)
- 整个过程要**记住上下文**(已经问过什么、查过什么)
用 Chain 能实现吗?
# Chain 的尝试(注定失败)
chain = (
understand_intent # ① 理解意图
| query_database # ② 查询数据库
| ??? # 怎么在这里暂停等待用户?
| ??? # 怎么根据查询结果决定是返回还是澄清?
| ??? # 怎么回到第②步重新查询?
)
# Chain 是线性的,执行一遍就结束了
# 无法暂停、无法循环、无法根据结果分支
核心矛盾:
Chain 的设计假设:
"任务是可以一次性从头到尾完成的"
现实的 Agent 任务:
"需要尝试、判断、回退、重来,是一个探索过程"
场景 2:数据分析的探索过程
再看一个技术场景:
用户:"分析我们的销售数据,找出问题"
数据分析 Agent 的真实工作流:
──────────────────────────────────────
① 加载数据
查看数据结构:100 列,50 万行
→ 太大了,先采样看看
② LLM 初步分析
发现:销售额 Q3 比 Q2 下降了 15%
生成假设:"可能是地区差异?产品问题?季节因素?"
③ 根据假设选择分析方法:
├─ 假设 1:地区差异 → 按地区分组分析
├─ 假设 2:产品问题 → 按产品维度分析
└─ 假设 3:季节因素 → 时间序列分析
④ 执行分析(假设选择了"地区分析")
结果:华东地区下降 30%,其他地区正常
⑤ 判断结果
├─ 结果清晰 → 继续深入华东地区
└─ 结果模糊 → 生成新假设,回到③
⑥ 深入华东地区
按城市细分:上海、杭州、南京...
发现:主要是上海下降,其他城市还好
⑦ 继续深入上海
按产品分析...
(递归深入,直到找到根因)
⑧ 生成报告
⑨ 用户看后说:"能不能对比一下竞品?"
→ 回到③,增加竞品对比维度
特点:
- 动态路径(根据假设选择不同分析方法)
- 递归深入(层层拆解问题)
- 迭代优化(结果不清晰就换个角度)
- 状态累积(保存所有中间分析结果)
Chain 的困境:
# Chain 无法表达这种动态、递归的流程
chain = (
load_data
| llm_analyze
| ??? # 怎么根据 LLM 的分析动态选择下一步?
| ??? # 怎么递归深入?
| ??? # 怎么在用户反馈后重新执行部分流程?
)
# 你需要的不是一条链,而是一张图
Part 2:LangGraph 的核心洞察
从"链"到"图"的思维转变
让我们用一个生活化的类比来理解这个转变:
Chain = 流水线
想象一个汽车装配流水线:
底盘 → 发动机 → 车身 → 喷漆 → 检验 → 出厂
特点:
- 固定顺序,不能跳跃
- 不能回头(喷完漆发现发动机有问题?废了,重来!)
- 一条线走到底
- 高效但死板
适合:
标准化、流程化的制造任务
Graph = 工作流程图
想象一个产品研发流程:
设计
↓
评审 ──┐
↓ │ 不通过
原型 │
↓ │
测试 ──┘
↓
┌─判断─┐
↓ ↓
通过 失败
↓ ↓
生产 修改→回到设计
特点:
- 有分支(通过/不通过)
- 有循环(失败了回到前面改进)
- 根据情况选路径
- 灵活但需要规划
适合:
需要判断、尝试、优化的探索任务
Agent 任务更像"研发"而非"制造"
为什么?
制造(Chain):
- 目标明确:做出标准产品
- 路径固定:按 SOP 执行
- 一次成功:质量控制在流程中
研发(Graph):
- 目标模糊:用户说"帮我分析数据"
- 路径不定:分析过程中才知道下一步该做什么
- 需要迭代:第一次分析不够深入,重来
LangGraph 的四大核心概念
1. StateGraph:工作流的蓝图
from langgraph.graph import StateGraph
# StateGraph = 你的 Agent 工作流的"设计图纸"
graph = StateGraph(State)
# 定义节点(执行单元)
graph.add_node("search", search_node)
graph.add_node("analyze", analyze_node)
graph.add_node("report", report_node)
# 定义边(流程路径)
graph.add_edge("search", "analyze") # 搜索完 → 分析
# 定义条件边(根据情况选路径)
graph.add_conditional_edges(
"analyze",
should_continue, # 判断函数
{
"continue": "search", # 数据不够 → 回到搜索
"finish": "report" # 数据够了 → 生成报告
}
)
# 编译成可执行的 Agent
agent = graph.compile()
类比理解:
StateGraph = 游戏的关卡地图
节点 = 关卡:
- 新手村(search_node)
- 森林迷宫(analyze_node)
- Boss 战(report_node)
边 = 路径:
- 新手村 → 森林迷宫(固定路径)
- 森林迷宫 → ?(根据战斗结果决定)
如果 HP < 50% → 回新手村补给
如果 HP > 50% → 继续前进 Boss 战
State = 角色属性:
- 等级、装备、HP、已完成的任务...
- 在关卡间保持,不会丢失
2. State:共享的"工作台"
from typing import TypedDict, Annotated
from langgraph.graph import add_messages
class AgentState(TypedDict):
"""Agent 的状态结构"""
# 对话历史(自动累积)
messages: Annotated[list, add_messages]
# 当前查询
query: str
# 搜索结果(累积)
search_results: list
# 迭代次数
iteration: int
# 用户是否满意
user_satisfied: bool
# 最终答案
final_answer: str
State 的作用:
想象一个办公桌(State):
─────────────────────────────────────
桌面上放着:
- 📋 用户的原始需求(query)
- 📁 已收集的资料(search_results)
- 📝 分析笔记(messages)
- 🔢 工作进度(iteration: 3)
- ✓ 是否完成(user_satisfied: False)
工作流程:
① 搜索员工来到桌前
- 看到桌上的需求和已有资料
- 去搜索新资料
- 把新资料放到桌上(更新 search_results)
- 离开
② 分析员工来到桌前
- 看到所有资料(搜索员工刚放的)
- 分析资料
- 写分析笔记放桌上(更新 messages)
- 更新进度标记(iteration: 3 → 4)
- 离开
③ 判断员工来到桌前
- 看进度:已经第 4 轮了
- 看资料:够不够充分?
- 决定:回到搜索员工 or 继续报告员工
关键:
- 所有员工共享同一个桌面(State)
- 每个员工看到的都是最新状态
- 员工离开后,桌面保持(不会消失)
State 更新的合并策略:
# 节点函数的返回值会被合并到 State
def search_node(state: AgentState):
"""搜索节点"""
query = state["query"]
results = search_api(query)
# 返回要更新的字段(部分 State)
return {
"search_results": results, # 新搜索的结果
"iteration": state["iteration"] + 1 # 迭代次数 +1
}
# State 合并:
# 原 State: {"query": "xxx", "iteration": 2, "search_results": []}
# 节点返回: {"search_results": [新结果], "iteration": 3}
# 合并后: {"query": "xxx", "iteration": 3, "search_results": [新结果]}
特殊字段:累积而非覆盖
class AgentState(TypedDict):
# messages 使用 add_messages 注解
# 表示:新消息追加到列表,而非替换整个列表
messages: Annotated[list, add_messages]
# 例子:
# 原 State: {"messages": [msg1, msg2]}
# 节点返回: {"messages": [msg3]}
# 合并后: {"messages": [msg1, msg2, msg3]} # 追加,不是覆盖!
3. Node:执行单元的设计
节点是一个简单的函数:输入 State,输出 State 更新。
def search_node(state: AgentState):
"""搜索节点:执行搜索,更新结果"""
query = state["query"]
iteration = state["iteration"]
print(f"[搜索节点] 第 {iteration} 次搜索:{query}")
# 执行搜索
results = search_api(query)
# 返回要更新的 State 字段
return {
"search_results": results,
"iteration": iteration + 1,
"messages": [f"搜索完成,找到 {len(results)} 条结果"]
}
节点的类型:
# 1. 工具节点:执行具体操作
def search_node(state):
results = search_api(state["query"])
return {"search_results": results}
# 2. LLM 节点:调用语言模型
def analyze_node(state):
llm_response = llm.invoke(state["search_results"])
return {"messages": [llm_response]}
# 3. 决策节点:不调用外部服务,只做判断
def check_quality(state):
if len(state["search_results"]) >= 10:
return {"quality": "sufficient"}
else:
return {"quality": "insufficient"}
# 4. 人机交互节点:暂停执行,等待输入
def human_review(state):
# 这里会暂停,等待人工反馈
return {"status": "waiting_human_input"}
节点设计的最佳实践:
# ✓ 好的节点设计:单一职责
def search_financial_data(state):
"""只负责搜索财务数据"""
return {"financial_data": ...}
def search_news(state):
"""只负责搜索新闻"""
return {"news_data": ...}
def analyze_data(state):
"""只负责分析"""
return {"analysis": ...}
# ✗ 差的节点设计:职责混乱
def do_everything(state):
"""又搜索又分析,什么都干"""
financial_data = search_financial(...)
news_data = search_news(...)
analysis = analyze(financial_data, news_data)
return {"everything": ...}
# 问题:难以测试、难以复用、难以调试
4. Edge:路径的选择
普通边:固定路径
# A 完成后,总是执行 B
graph.add_edge("search", "analyze")
条件边:根据 State 决定路径
def should_continue(state: AgentState):
"""判断是否继续搜索"""
# 已经搜了 5 轮,够了
if state["iteration"] >= 5:
return "max_iterations"
# 用户满意了,结束
if state["user_satisfied"]:
return "finish"
# 数据不够,继续搜索
if len(state["search_results"]) < 10:
return "need_more_data"
# 数据够了,去分析
return "ready_to_analyze"
# 添加条件边
graph.add_conditional_edges(
source="analyze", # 从 analyze 节点出发
path=should_continue, # 用这个函数判断
path_map={ # 判断结果 → 目标节点
"need_more_data": "search", # 回到搜索
"ready_to_analyze": "report", # 去生成报告
"finish": END, # 结束
"max_iterations": END # 结束
}
)
可视化理解:
analyze 节点
↓
[should_continue 判断]
↓
┌───────────┼───────────┐
↓ ↓ ↓
need_more ready_to max_iterations
↓ ↓ ↓
search report END
↓
analyze ← 循环回来
Part 3:核心机制深度解析
机制 1:循环的实现——ReAct 模式
让我们看一个经典的 AI Agent 模式:ReAct(Reasoning + Acting)。
ReAct 的工作流程:
循环执行,直到任务完成:
① Thought(思考)
LLM 分析当前情况,决定下一步
例如:"我需要先搜索公司的财报数据"
② Action(行动)
执行工具调用
例如:search("特斯拉 2024 财报")
③ Observation(观察)
查看工具返回的结果
例如:找到 3 份财报
④ 判断
任务完成了吗?
- 否 → 回到 ①(继续思考下一步)
- 是 → 生成最终答案
特点:
- 思考 → 行动 → 观察,循环往复
- 每一轮都在接近目标
- 类似人类解决问题的方式
用 LangGraph 实现:
from langgraph.graph import StateGraph, END
class ReActState(TypedDict):
messages: Annotated[list, add_messages]
iteration: int
task_complete: bool
# 1. 思考节点
def think_node(state: ReActState):
"""LLM 分析情况,决定下一步行动"""
messages = state["messages"]
# 构造提示词
prompt = f"""
已有信息:{messages}
思考:下一步应该做什么?
可以选择:
- search(query): 搜索信息
- calculate(expression): 计算
- finish: 任务完成
请输出你的决策。
"""
llm_response = llm.invoke(prompt)
return {
"messages": [{"role": "assistant", "content": llm_response}]
}
# 2. 行动节点
def act_node(state: ReActState):
"""执行工具调用"""
last_message = state["messages"][-1]["content"]
# 解析 LLM 的决策(简化示例)
if "search" in last_message:
query = extract_query(last_message)
result = search_api(query)
observation = f"搜索结果:{result}"
elif "calculate" in last_message:
expr = extract_expression(last_message)
result = eval(expr) # 简化处理,生产环境要更安全
observation = f"计算结果:{result}"
elif "finish" in last_message:
return {"task_complete": True}
else:
observation = "无法理解该行动"
return {
"messages": [{"role": "tool", "content": observation}],
"iteration": state["iteration"] + 1
}
# 3. 决策函数:是否继续循环
def should_continue(state: ReActState):
"""判断是否继续 ReAct 循环"""
# 任务完成
if state["task_complete"]:
return "finish"
# 达到最大迭代次数
if state["iteration"] >= 10:
return "max_iterations"
# 继续循环
return "continue"
# 构建图
graph = StateGraph(ReActState)
# 添加节点
graph.add_node("think", think_node)
graph.add_node("act", act_node)
# 设置入口
graph.set_entry_point("think")
# think → act(固定)
graph.add_edge("think", "act")
# act → ?(根据判断)
graph.add_conditional_edges(
"act",
should_continue,
{
"continue": "think", # 回到思考(循环!)
"finish": END,
"max_iterations": END
}
)
# 编译
react_agent = graph.compile()
# 使用
result = react_agent.invoke({
"messages": [{"role": "user", "content": "分析特斯拉的竞争力"}],
"iteration": 0,
"task_complete": False
})
执行示例:
用户:"分析特斯拉 2024 年的竞争力"
第 1 轮:
[think] LLM 思考:"需要先获取财务数据"
[act] 执行:search("特斯拉 2024 财报")
[observe] 结果:找到 Q1、Q2 财报
[judge] 继续 → 回到 think
第 2 轮:
[think] LLM 思考:"数据不完整,需要 Q3、Q4"
[act] 执行:search("特斯拉 Q3 Q4 财报")
[observe] 结果:找到了
[judge] 继续 → 回到 think
第 3 轮:
[think] LLM 思考:"数据齐全,开始分析"
[act] 执行:calculate("增长率、利润率...")
[observe] 结果:计算完成
[judge] 继续 → 回到 think
第 4 轮:
[think] LLM 思考:"分析完成,可以输出"
[act] 执行:finish
[judge] task_complete = True → END
最终输出:完整的竞争力分析报告
循环的安全保护:
def should_continue(state):
"""加上所有必要的安全检查"""
# 1. 最大迭代次数
if state["iteration"] >= 10:
return "max_iterations"
# 2. 超时检测
if time.time() - state["start_time"] > 300: # 5 分钟
return "timeout"
# 3. 成本控制
if state["llm_calls"] >= 20: # 最多 20 次 LLM 调用
return "cost_limit"
# 4. 异常检测
if state["consecutive_failures"] >= 3: # 连续失败 3 次
return "error"
# 5. 任务完成
if state["task_complete"]:
return "finish"
# 继续
return "continue"
机制 2:条件分支——智能路由
现实中的 Agent 需要根据情况选择不同的路径。
场景:智能客服的意图路由
# State 定义
class CustomerServiceState(TypedDict):
user_message: str
intent: str # 意图:查询订单、退款、咨询等
order_info: dict
issue_resolved: bool
# 1. 意图识别节点
def intent_classifier(state):
"""识别用户意图"""
message = state["user_message"]
# LLM 分类(简化示例)
prompt = f"""
用户说:"{message}"
这属于哪种意图?
A. 查询订单
B. 申请退款
C. 产品咨询
D. 投诉
E. 其他
只回答字母。
"""
intent_code = llm.invoke(prompt)
intent_map = {
"A": "查询订单",
"B": "退款",
"C": "咨询",
"D": "投诉",
"E": "其他"
}
return {"intent": intent_map.get(intent_code, "其他")}
# 2. 路由函数
def route_by_intent(state):
"""根据意图路由到不同节点"""
intent = state["intent"]
# 返回目标节点名称
if intent == "查询订单":
return "order_lookup"
elif intent == "退款":
return "refund_process"
elif intent == "咨询":
return "knowledge_base"
elif intent == "投诉":
return "complaint_handler"
else:
return "human_handoff" # 转人工
# 3. 不同路径的节点
def order_lookup_node(state):
"""查询订单"""
order_info = query_database(state["user_message"])
return {"order_info": order_info, "issue_resolved": True}
def refund_process_node(state):
"""退款流程"""
# 退款逻辑...
return {"issue_resolved": True}
def knowledge_base_node(state):
"""知识库检索"""
answer = search_kb(state["user_message"])
return {"response": answer, "issue_resolved": True}
def complaint_handler_node(state):
"""投诉处理"""
# 记录投诉,升级...
return {"issue_resolved": True}
def human_handoff_node(state):
"""转人工"""
return {"status": "transferred_to_human"}
# 构建图
graph = StateGraph(CustomerServiceState)
# 添加所有节点
graph.add_node("intent_classifier", intent_classifier)
graph.add_node("order_lookup", order_lookup_node)
graph.add_node("refund_process", refund_process_node)
graph.add_node("knowledge_base", knowledge_base_node)
graph.add_node("complaint_handler", complaint_handler_node)
graph.add_node("human_handoff", human_handoff_node)
# 入口:先识别意图
graph.set_entry_point("intent_classifier")
# 条件路由:根据意图选择路径
graph.add_conditional_edges(
"intent_classifier",
route_by_intent,
{
"order_lookup": "order_lookup",
"refund_process": "refund_process",
"knowledge_base": "knowledge_base",
"complaint_handler": "complaint_handler",
"human_handoff": "human_handoff"
}
)
# 所有路径最后都结束
for node in ["order_lookup", "refund_process", "knowledge_base",
"complaint_handler", "human_handoff"]:
graph.add_edge(node, END)
agent = graph.compile()
可视化:
intent_classifier
↓
[根据 intent 判断路径]
↓
┌────┬────┬─────┼─────┬────┐
↓ ↓ ↓ ↓ ↓ ↓
查询 退款 咨询 投诉 转人工
订单
↓ ↓ ↓ ↓ ↓
└────┴────┴─────┴─────┘
↓
END
运行示例:
# 场景 1:查询订单
result = agent.invoke({
"user_message": "我的订单 OH12345 怎么还没到?"
})
# 路径:intent_classifier → order_lookup → END
# 场景 2:退款
result = agent.invoke({
"user_message": "我要退货,这个产品质量太差了"
})
# 路径:intent_classifier → refund_process → END
# 场景 3:奇怪的问题
result = agent.invoke({
"user_message": "你们老板是谁?"
})
# 路径:intent_classifier → human_handoff → END
机制 3:并行执行——提升效率
有些任务可以同时进行,无需等待。
场景:综合研究任务
class ResearchState(TypedDict):
topic: str
financial_data: dict # 财务数据
news_data: list # 新闻数据
industry_data: dict # 行业数据
stock_data: dict # 股价数据
all_completed: bool
# 四个独立的搜索节点
def search_financial(state):
"""搜索财务数据(可能需要 5 秒)"""
results = search_api(f"{state['topic']} 财报")
return {"financial_data": results}
def search_news(state):
"""搜索新闻(可能需要 3 秒)"""
results = search_api(f"{state['topic']} 新闻")
return {"news_data": results}
def search_industry(state):
"""搜索行业报告(可能需要 4 秒)"""
results = search_api(f"{state['topic']} 行业报告")
return {"industry_data": results}
def search_stock(state):
"""搜索股价数据(可能需要 2 秒)"""
results = stock_api(state['topic'])
return {"stock_data": results}
# 综合分析节点(需要等待所有搜索完成)
def comprehensive_analysis(state):
"""综合分析所有数据"""
# 检查是否所有数据都有了
if not all([
state.get("financial_data"),
state.get("news_data"),
state.get("industry_data"),
state.get("stock_data")
]):
return {"error": "数据不完整"}
# 综合分析
analysis = llm.invoke(f"""
财务数据:{state['financial_data']}
新闻数据:{state['news_data']}
行业数据:{state['industry_data']}
股价数据:{state['stock_data']}
请综合分析 {state['topic']} 的情况。
""")
return {"analysis": analysis, "all_completed": True}
# 构建图
graph = StateGraph(ResearchState)
# 添加节点
graph.add_node("search_financial", search_financial)
graph.add_node("search_news", search_news)
graph.add_node("search_industry", search_industry)
graph.add_node("search_stock", search_stock)
graph.add_node("comprehensive_analysis", comprehensive_analysis)
# 入口:直接开始四个搜索(并行)
graph.set_entry_point("search_financial") # 其实任何一个都可以
# 关键:四个搜索节点都指向分析节点
graph.add_edge("search_financial", "comprehensive_analysis")
graph.add_edge("search_news", "comprehensive_analysis")
graph.add_edge("search_industry", "comprehensive_analysis")
graph.add_edge("search_stock", "comprehensive_analysis")
# 分析完成后结束
graph.add_edge("comprehensive_analysis", END)
# 编译
research_agent = graph.compile()
执行效果:
串行执行(传统 Chain):
search_financial (5秒)
→ search_news (3秒)
→ search_industry (4秒)
→ search_stock (2秒)
→ analysis (3秒)
总时间:5 + 3 + 4 + 2 + 3 = 17 秒
并行执行(LangGraph):
┌─ search_financial (5秒) ─┐
├─ search_news (3秒) ───────┤
├─ search_industry (4秒) ───┼─→ analysis (3秒)
└─ search_stock (2秒) ──────┘
总时间:max(5, 3, 4, 2) + 3 = 8 秒
节省:17 - 8 = 9 秒(53% 提升!)
注意:并行的前提
# ✓ 可以并行的任务:
# - 互不依赖
# - 各自访问独立的 API
# - 不修改相同的 State 字段
def search_a(state):
return {"data_a": ...} # 只修改 data_a
def search_b(state):
return {"data_b": ...} # 只修改 data_b
# ✗ 不能并行的任务:
# - 有依赖关系(B 需要 A 的结果)
# - 修改同一字段(会冲突)
def step1(state):
return {"counter": state["counter"] + 1} # 修改 counter
def step2(state):
return {"counter": state["counter"] + 2} # 也修改 counter
# 如果并行,counter 的最终值不确定!
机制 4:人机交互(Human-in-the-Loop)
有些决策需要人工确认,LangGraph 原生支持暂停和恢复。
场景:报告生成的审核流程
from langgraph.checkpoint import MemorySaver
class ReportState(TypedDict):
topic: str
draft: str
human_feedback: str
approved: bool
# 1. 生成草稿
def generate_draft(state):
"""LLM 生成报告草稿"""
draft = llm.invoke(f"写一份关于 {state['topic']} 的报告")
return {"draft": draft}
# 2. 人工审核节点(关键!)
def human_review(state):
"""这个节点会暂停执行,等待人工输入"""
# 返回特殊标记,表示需要中断
return {"status": "awaiting_human_review"}
# 3. 处理反馈
def process_feedback(state):
"""根据人工反馈决定下一步"""
feedback = state.get("human_feedback", "")
if "approve" in feedback.lower():
return {"approved": True}
elif "reject" in feedback.lower():
# 需要重写
return {"approved": False}
else:
# 修改建议
revised = llm.invoke(f"""
原草稿:{state['draft']}
修改建议:{feedback}
请根据建议修改草稿。
""")
return {"draft": revised, "approved": False}
# 4. 路由函数
def check_approval(state):
if state.get("approved"):
return "approved"
else:
return "revise"
# 构建图
# 关键:使用 checkpointer 支持暂停和恢复
checkpointer = MemorySaver()
graph = StateGraph(ReportState)
graph.add_node("generate_draft", generate_draft)
graph.add_node("human_review", human_review) # 中断点
graph.add_node("process_feedback", process_feedback)
graph.set_entry_point("generate_draft")
# 草稿 → 人工审核
graph.add_edge("generate_draft", "human_review")
# 人工审核 → 处理反馈
graph.add_edge("human_review", "process_feedback")
# 处理反馈后,根据结果决定
graph.add_conditional_edges(
"process_feedback",
check_approval,
{
"approved": END,
"revise": "generate_draft" # 回到重新生成
}
)
# 编译,启用检查点
report_agent = graph.compile(checkpointer=checkpointer)
使用方式:
# 第一次执行:生成草稿,然后暂停
config = {"configurable": {"thread_id": "report_123"}}
state1 = report_agent.invoke({
"topic": "AI 发展趋势"
}, config)
print(state1["draft"])
# 输出:
# "AI 发展趋势报告
# 1. 大模型持续进步...
# 2. 多模态融合...
# 3. ..."
print(state1["status"])
# 输出:"awaiting_human_review" ← 暂停了!
# 用户看到草稿,决定修改
# 几小时后...用户回来了
# 第二次执行:提供人工反馈,恢复执行
state2 = report_agent.invoke({
"human_feedback": "第 2 点需要更详细,加上具体例子"
}, config) # 相同的 thread_id!
# Agent 从暂停的地方恢复
# 根据反馈修改草稿
# 再次暂停等待审核
print(state2["draft"])
# 输出:修改后的草稿
# 第三次执行:批准
state3 = report_agent.invoke({
"human_feedback": "approve"
}, config)
print(state3["approved"])
# 输出:True
# 执行完成!
关键点:
# 1. thread_id 的作用
# 相同 thread_id = 同一个会话
# 不同 thread_id = 独立的会话
# 会话 1
agent.invoke(input1, config={"thread_id": "session_1"})
# 会话 2(独立的)
agent.invoke(input2, config={"thread_id": "session_2"})
# 2. checkpointer 的作用
# 保存每一步的 State
# 支持暂停和恢复
# 可以回溯到历史状态
# 3. 多轮交互
# 每次 invoke 都是"继续执行"
# 直到遇到 END 或新的中断点
Part 4:实战案例——研究助手 Agent
现在我们用 LangGraph 实现一个完整的研究助手。
需求:
用户输入:"帮我研究量子计算的最新进展"
Agent 需要:
1. 理解查询,拆解子问题
2. 搜索相关资料(可能多轮)
3. 评估信息是否充分,不够就继续搜
4. 综合分析,生成报告
5. 等待用户反馈,如果要深入某方面,重新搜索
完整实现:
from langgraph.graph import StateGraph, END
from langgraph.checkpoint import MemorySaver
from typing import TypedDict, Annotated, List
from langgraph.graph import add_messages
# 1. State 定义
class ResearchState(TypedDict):
# 用户原始查询
original_query: str
# 拆解的子问题列表
sub_questions: List[str]
# 搜索历史(所有搜索过的内容)
search_history: List[dict]
# 当前收集的文档
documents: List[str]
# 迭代次数
iteration: int
# 信息是否充分
info_sufficient: bool
# 生成的报告草稿
report_draft: str
# 用户反馈
user_feedback: str
# 任务完成
task_complete: bool
# 对话历史
messages: Annotated[List, add_messages]
# 2. 节点定义
def query_understanding(state):
"""理解查询,拆解子问题"""
query = state["original_query"]
prompt = f"""
用户查询:"{query}"
请将这个查询拆解成 3-5 个子问题,每个子问题对应需要搜索的方向。
例如,"量子计算的最新进展"可以拆解为:
1. 量子计算的基本原理和定义
2. 2023-2024 年的重大突破
3. 主要研究机构和团队
4. 商业应用前景
5. 面临的挑战
请输出子问题列表(JSON 格式)。
"""
response = llm.invoke(prompt)
sub_questions = parse_json(response) # 解析 JSON
return {
"sub_questions": sub_questions,
"messages": [{"role": "assistant", "content": f"已拆解为 {len(sub_questions)} 个子问题"}]
}
def parallel_search(state):
"""对每个子问题进行搜索"""
sub_questions = state["sub_questions"]
iteration = state["iteration"]
all_documents = []
search_records = []
for question in sub_questions:
# 搜索(简化示例,实际会调用搜索 API)
results = search_api(question)
all_documents.extend(results)
search_records.append({
"iteration": iteration,
"question": question,
"num_results": len(results)
})
return {
"documents": state.get("documents", []) + all_documents, # 累积
"search_history": state.get("search_history", []) + search_records, # 累积
"iteration": iteration + 1,
"messages": [{"role": "tool", "content": f"搜索完成,找到 {len(all_documents)} 份文档"}]
}
def evaluate_sufficiency(state):
"""评估信息是否充分"""
documents = state["documents"]
sub_questions = state["sub_questions"]
prompt = f"""
子问题:{sub_questions}
已收集文档数量:{len(documents)}
文档概览:{documents[:3]} # 只看前 3 份
判断:信息是否足够回答所有子问题?
请回答:
- sufficient(充分)
- need_more_data(需要更多数据)
如果需要更多数据,请说明缺少哪方面的信息。
"""
response = llm.invoke(prompt)
if "sufficient" in response.lower():
return {
"info_sufficient": True,
"messages": [{"role": "assistant", "content": "信息充分,开始分析"}]
}
else:
# 提取缺失的信息
missing_info = extract_missing_info(response)
return {
"info_sufficient": False,
"sub_questions": [missing_info], # 更新子问题为缺失的部分
"messages": [{"role": "assistant", "content": f"信息不足,需要补充:{missing_info}"}]
}
def comprehensive_analysis(state):
"""综合分析,生成报告"""
documents = state["documents"]
original_query = state["original_query"]
prompt = f"""
原始查询:{original_query}
已收集资料(共 {len(documents)} 份):
{format_documents(documents)}
请基于这些资料,撰写一份结构化的研究报告。
报告结构:
1. 概述
2. 详细分析(分点阐述)
3. 总结
长度:1500-2000 字。
"""
report = llm.invoke(prompt)
return {
"report_draft": report,
"messages": [{"role": "assistant", "content": "报告生成完成"}]
}
def human_feedback_node(state):
"""等待用户反馈"""
return {"status": "awaiting_feedback"}
def process_user_feedback(state):
"""处理用户反馈"""
feedback = state.get("user_feedback", "")
if not feedback or "满意" in feedback:
return {"task_complete": True}
# 提取用户想要深入的方向
prompt = f"""
用户反馈:"{feedback}"
用户想要深入了解哪个方面?请提取关键词作为新的搜索方向。
"""
new_direction = llm.invoke(prompt)
return {
"sub_questions": [new_direction], # 新的搜索方向
"task_complete": False,
"messages": [{"role": "user", "content": feedback}]
}
# 3. 路由函数
def route_after_search(state):
"""搜索后的路由"""
# 达到最大迭代次数
if state["iteration"] >= 5:
return "max_iterations_force_analyze"
# 评估信息充分性
return "evaluate"
def route_after_evaluate(state):
"""评估后的路由"""
if state["info_sufficient"]:
return "analyze" # 信息够了,去分析
else:
return "search" # 信息不够,继续搜索
def route_after_feedback(state):
"""反馈后的路由"""
if state.get("task_complete"):
return "finish"
else:
return "search" # 用户要深入,重新搜索
# 4. 构建图
checkpointer = MemorySaver()
graph = StateGraph(ResearchState)
# 添加所有节点
graph.add_node("understand", query_understanding)
graph.add_node("search", parallel_search)
graph.add_node("evaluate", evaluate_sufficiency)
graph.add_node("analyze", comprehensive_analysis)
graph.add_node("feedback", human_feedback_node)
graph.add_node("process_feedback", process_user_feedback)
# 入口
graph.set_entry_point("understand")
# 理解 → 搜索
graph.add_edge("understand", "search")
# 搜索 → 评估
graph.add_conditional_edges(
"search",
route_after_search,
{
"evaluate": "evaluate",
"max_iterations_force_analyze": "analyze" # 强制进入分析
}
)
# 评估 → 分析 or 继续搜索
graph.add_conditional_edges(
"evaluate",
route_after_evaluate,
{
"analyze": "analyze",
"search": "search" # 循环!
}
)
# 分析 → 人工反馈
graph.add_edge("analyze", "feedback")
# 反馈 → 处理
graph.add_edge("feedback", "process_feedback")
# 处理反馈 → 结束 or 继续搜索
graph.add_conditional_edges(
"process_feedback",
route_after_feedback,
{
"finish": END,
"search": "search" # 又循环回去!
}
)
# 编译
research_agent = graph.compile(checkpointer=checkpointer)
# 5. 使用
# 第一轮:开始研究
config = {"configurable": {"thread_id": "research_001"}}
state1 = research_agent.invoke({
"original_query": "量子计算的最新进展",
"iteration": 0,
"documents": [],
"search_history": [],
"messages": []
}, config)
# Agent 自动执行:
# understand → search → evaluate → search(循环)→ evaluate → analyze → feedback
# 在 feedback 节点暂停
print(state1["report_draft"])
# 输出:生成的报告
# 第二轮:用户看了报告,提出反馈
state2 = research_agent.invoke({
"user_feedback": "能否深入讲讲商业应用?特别是金融领域"
}, config)
# Agent 继续执行:
# process_feedback → search(新方向)→ evaluate → analyze → feedback
# 再次暂停
print(state2["report_draft"])
# 输出:更新后的报告(增加了金融应用部分)
# 第三轮:用户满意
state3 = research_agent.invoke({
"user_feedback": "满意,谢谢"
}, config)
# Agent 执行:process_feedback → END
# 完成!
流程可视化:
understand
↓
┌──────────────→ search ←──────────────┐
│ ↓ │
│ evaluate │
│ ↓ │
│ ┌────────┴────────┐ │
│ ↓ ↓ │
│ info_sufficient? info_lacking │
│ ↓ ↓ │
│ analyze │ │
│ ↓ └──────────┘
│ feedback
│ ↓
│ process_feedback
│ ↓
│ ┌─────┴─────┐
│ ↓ ↓
│ finish continue
│ ↓ │
│ END │
└───────────────┘
关键特性:
- 多轮搜索循环:信息不够就继续搜,最多 5 轮
- 动态调整:根据评估结果决定是继续搜索还是分析
- 人机交互:生成报告后暂停,等待用户反馈
- 迭代改进:用户要求深入,Agent 重新搜索和分析
- 状态保持:所有搜索历史、文档、迭代次数都在 State 中
Part 5:最佳实践与设计原则
原则 1:节点的单一职责
❌ 错误示例:一个节点做太多事
def mega_node(state):
"""又搜索又分析又生成,什么都干"""
# 搜索
search_results = search_api(state["query"])
# 分析
analysis = llm.invoke(search_results)
# 格式化
formatted = format_output(analysis)
# 保存到数据库
save_to_db(formatted)
return {"result": formatted}
# 问题:
# - 难以测试(一个节点太多逻辑)
# - 难以复用(搜索逻辑和分析逻辑耦合)
# - 难以调试(出错了不知道哪一步有问题)
# - 难以优化(无法单独优化搜索性能)
✅ 正确示例:每个节点只做一件事
def search_node(state):
"""只负责搜索"""
results = search_api(state["query"])
return {"search_results": results}
def analyze_node(state):
"""只负责分析"""
analysis = llm.invoke(state["search_results"])
return {"analysis": analysis}
def format_node(state):
"""只负责格式化"""
formatted = format_output(state["analysis"])
return {"formatted_output": formatted}
def save_node(state):
"""只负责保存"""
save_to_db(state["formatted_output"])
return {"saved": True}
# 优点:
# ✓ 每个节点可以独立测试
# ✓ 逻辑清晰,易于理解
# ✓ 可以灵活组合(跳过格式化?只改 graph,不改节点)
# ✓ 容易找到性能瓶颈(哪个节点慢?)
原则 2:State 的最小化
❌ 错误示例:State 包含所有东西
class BlogState(TypedDict):
# 必要的
original_query: str
analysis: str
# 不必要的(调试信息)
debug_log: List[str]
function_call_times: dict
memory_usage: float
# 不必要的(可以计算得出)
word_count: int # 可以从 analysis 计算
char_count: int # 可以从 analysis 计算
# 不必要的(临时变量)
temp_buffer: str
intermediate_step_1: str
intermediate_step_2: str
# 问题:
# - State 太大,序列化慢
# - 检查点占用空间大
# - 难以理解(哪些字段是关键的?)
✅ 正确示例:只存必要信息
class ResearchState(TypedDict):
# 只存真正需要在节点间传递的信息
# 用户输入
query: str
# 搜索结果(必须保存,后续节点要用)
documents: List[str]
# 分析结果
analysis: str
# 控制流状态
iteration: int
task_complete: bool
# 临时变量在节点内部处理,不放 State
def analyze_node(state):
documents = state["documents"]
# 这些是临时变量,不需要放 State
word_count = sum(len(doc.split()) for doc in documents)
temp_summary = summarize(documents)
# 只返回最终结果
analysis = llm.invoke(temp_summary)
return {"analysis": analysis}
原则 3:防御性编程
必须的安全保护:
def should_continue(state):
"""完善的循环控制"""
# 1. 最大迭代次数(防止死循环)
if state["iteration"] >= 10:
logger.warning("达到最大迭代次数")
return "max_iterations"
# 2. 超时控制
elapsed = time.time() - state["start_time"]
if elapsed > 300: # 5 分钟
logger.warning(f"执行超时:{elapsed}秒")
return "timeout"
# 3. 成本控制
if state["llm_token_used"] > 100000: # 10 万 tokens
logger.warning("Token 用量超限")
return "cost_limit"
# 4. 错误检测
if state["consecutive_failures"] >= 3:
logger.error("连续失败 3 次")
return "error_handler"
# 5. 任务完成
if state["task_complete"]:
return "finish"
# 继续执行
return "continue"
异常处理节点:
def error_handler_node(state):
"""统一的错误处理"""
error_type = state.get("error_type")
error_message = state.get("error_message")
logger.error(f"错误类型:{error_type},信息:{error_message}")
# 降级策略
if error_type == "search_api_failure":
# API 失败,尝试备用搜索
return {"use_fallback_search": True}
elif error_type == "llm_timeout":
# LLM 超时,使用更小的模型
return {"use_smaller_model": True}
elif error_type == "data_insufficient":
# 数据不足,返回部分结果
return {"partial_result": state.get("partial_data")}
else:
# 未知错误,转人工
return {"transfer_to_human": True}
# 在图中添加错误处理路径
graph.add_node("error_handler", error_handler_node)
# 从任何节点都可能跳转到错误处理
graph.add_conditional_edges(
"search",
lambda state: "error" if state.get("error") else "continue",
{
"continue": "analyze",
"error": "error_handler"
}
)
Part 6:LangGraph vs 其他框架
与 LangChain 的关系
LangChain = 基础设施
LangGraph = 工作流引擎
关系:
- LangGraph 基于 LangChain 构建
- LangGraph 的节点可以使用 LangChain 的组件
- LangGraph 是 LangChain 生态的一部分
类比:
LangChain = 零件库(prompts、models、tools)
LangGraph = 装配线(把零件组装成复杂机器)
何时用哪个:
LangChain(简单任务):
"翻译这段话"
"总结这篇文章"
"回答这个问题"
→ 用 Chain 就够了
LangGraph(复杂任务):
"研究这个课题,如果信息不足就继续搜"
"分析数据,发现异常就深入调查"
"多轮对话,根据用户反馈调整"
→ 需要 Graph 的灵活性
混合使用:
graph.add_node(
"summarize",
LangChainNode(summarize_chain) # 在 Graph 中用 Chain
)
与 AutoGPT 的对比
AutoGPT(完全自主):
────────────────────────────────
理念:
"给 LLM 一个目标,让它自己规划和执行"
优点:
- 极度自主(用户只需要说目标)
- 能处理开放性任务
缺点:
- 不可控(可能偏离目标)
- 成本不可预测(可能无限循环)
- 难以调试(不知道它在想什么)
- 不适合生产环境
适合:
研究和探索(AGI 的尝试)
LangGraph(人类规划):
────────────────────────────────
理念:
"人类设计工作流,LLM 执行节点"
优点:
- 可控(流程是人定义的)
- 成本可预测(最多执行多少步)
- 易于调试(每个节点都清晰)
- 生产就绪(稳定可靠)
缺点:
- 需要人工设计图结构
- 自主性较低(按流程执行)
适合:
生产环境的 Agent 应用
本质区别:
────────────────────────────────
AutoGPT:
AI 决定"做什么"和"怎么做"
LangGraph:
人类决定"做什么"(工作流设计)
AI 决定"怎么做"(节点内的决策)
类比:
AutoGPT = 给孩子一个任务,让他自己想办法
LangGraph = 给员工一个流程,让他按流程执行
哪个更好?
看场景:
探索未知领域 → AutoGPT(更大胆)
完成具体任务 → LangGraph(更靠谱)
Part 7:总结与展望
LangGraph 的核心价值
回到文章开头的那个困境:当你的 Agent 需要思考、尝试、回退、再重来时,简单的 Chain 已经不够用了。
LangGraph 解决的核心问题:
从"一次性执行"到"迭代探索"
Chain:A → B → C,执行完就结束
Graph:A → B → 判断 → C or 回到 A,持续探索
从"固定流程"到"动态路径"
Chain:路径固定,无法改变
Graph:根据情况选择路径,灵活应对
从"黑盒执行"到"透明可控"
Chain:执行过程不可见
Graph:每个节点、每次判断都清晰可见
从"一次性对话"到"持续交互"
Chain:执行后就没了
Graph:支持暂停、恢复、人机协作
三个关键认知
1. Graph 不是银弹
LangGraph 增加了灵活性,但也增加了复杂度
权衡:
简单任务("翻译这段话")
→ 用 Chain,别过度设计
中等复杂任务("多轮搜索分析")
→ 用 LangGraph,物有所值
超复杂任务("完全自主的 AGI")
→ LangGraph 也不够,需要更高层的架构
记住:
能用 Chain 解决的,不要用 Graph
需要 Graph 才能解决的,果断用 Graph
2. Agent = 人机协作,不是人类替代
LangGraph 的设计理念:
人类的角色:
- 定义任务的结构(有哪些步骤)
- 设计执行流程(什么顺序、什么条件)
- 提供工具(LLM 可以调用什么)
- 监控和干预(关键决策点人工确认)
AI 的角色:
- 理解自然语言
- 做出判断(选择哪条路径)
- 生成内容
- 与工具交互
这不是"让 AI 替代人",而是"让 AI 辅助人"
3. 从 Chain 到 Graph 是思维方式的转变
Chain 思维(流水线思维):
"按顺序做 A、B、C"
线性、确定、简单
Graph 思维(工作流思维):
"先做 A,然后根据结果选择:
- 如果满足条件 X → 做 B
- 如果满足条件 Y → 做 C
- 如果条件 Z → 回到 A 重试
持续执行,直到达成目标"
非线性、动态、灵活
需要:
- 更好的规划能力(设计图结构)
- 更多的测试(覆盖所有路径)
- 更强的调试能力(追踪状态变化)
未来展望
方向 1:AI 辅助的图设计
当前:人工设计图结构
未来:AI 辅助设计
用户:"我要一个能分析财报的 Agent"
AI 设计师:"建议流程是:
1. 搜索财报 → 2. 预处理 → 3. 分析 → 4. 生成报告
其中第 3 步如果发现异常,需要回到第 1 步补充数据"
生成 LangGraph 代码
人工审核、微调
部署上线
方向 2:自我优化
当前:静态的图结构
未来:从执行历史中学习
"过去 100 次执行,第 5 步通常需要 3 轮循环才够
→ 自动调整:从第 1 步就多搜一点"
"用户经常在报告生成后要求深入某方面
→ 自动优化:生成报告时先预判可能的追问,提前准备"
强化学习 + 图结构优化
方向 3:多模态 Agent
当前:主要处理文本
未来:融合视觉、语音、文本
"分析这家公司"
→ 节点 1:搜索财报(文本)
→ 节点 2:分析产品图片(视觉)
→ 节点 3:观看 CEO 访谈视频(视频+语音)
→ 节点 4:综合分析