LangGraph 知识点详细总结 --- 进阶篇
本篇为进阶篇,覆盖:持久化、人在回路、流式输出、记忆、时间旅行、子图、多Agent三大模式、DeepAgents、框架对比、API速查。
基础内容见 LangGraph知识点总结_基础篇.md
目录
- [13. 持久化(Persistence)](#13. 持久化(Persistence))
- [14. 人在回路(Human-in-the-Loop)](#14. 人在回路(Human-in-the-Loop))
- [15. 流式输出(Streaming)](#15. 流式输出(Streaming))
- [16. 记忆系统(Memory)](#16. 记忆系统(Memory))
- [17. 时间旅行(Time Travel)](#17. 时间旅行(Time Travel))
- [18. 子图(Subgraphs)](#18. 子图(Subgraphs))
- [19. 多Agent架构 --- Agent-as-Tool](#19. 多Agent架构 — Agent-as-Tool)
- [20. 多Agent架构 --- Handoff](#20. 多Agent架构 — Handoff)
- [21. 多Agent架构 --- Supervisor](#21. 多Agent架构 — Supervisor)
- [22. 三大多Agent模式对比](#22. 三大多Agent模式对比)
- [23. DeepAgents --- 开箱即用的 Agent 框架](#23. DeepAgents — 开箱即用的 Agent 框架)
- [24. LangGraph vs LangChain vs Dify 全面对比](#24. LangGraph vs LangChain vs Dify 全面对比)
- [25. API 速查表](#25. API 速查表)
13. 持久化(Persistence)
13.1 为什么需要持久化
LangGraph 内置持久化层,将图状态保存为检查点(Checkpoint)。持久化是以下功能的基础:
| 功能 | 说明 |
|---|---|
| 人在回路 | 暂停执行,等待人类确认后恢复 |
| 对话记忆 | 同一会话的多轮对话保持上下文 |
| 时间旅行 | 回溯到之前的状态,重新执行 |
| 容错恢复 | 节点失败后从上一个成功的检查点恢复 |
13.2 核心概念
线程(Thread) :每个对话的唯一标识,通过 thread_id 区分:
python
config = {"configurable": {"thread_id": "thread-1"}}
# thread_id 是检查点的主键
# 同一 thread_id = 同一对话
# 不同 thread_id = 不同对话
检查点(Checkpoint):每个超步后保存的状态快照:
图执行 START → node_a → node_b → END 后,会产生 4 个检查点:
检查点 0: 空状态,下一个节点是 START
检查点 1: 用户输入,下一个节点是 node_a
检查点 2: node_a 的输出,下一个节点是 node_b
检查点 3: node_b 的输出,无下一个节点(完成)
超步(Super-step):检查点在每个超步边界保存:
超步 0: 输入阶段
超步 1: node_a 执行
超步 2: node_b 执行
每个超步结束后 → 保存检查点
13.3 使用 Checkpointer
python
from langgraph.checkpoint.memory import MemorySaver
# 内存检查点(开发/测试用)
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
# 执行时必须指定 thread_id
result = graph.invoke(
{"messages": [HumanMessage(content="你好")]},
config={"configurable": {"thread_id": "thread-1"}}
)
# 同一 thread_id 的后续调用会保留之前的上下文
result2 = graph.invoke(
{"messages": [HumanMessage(content="我刚才说了什么?")]},
config={"configurable": {"thread_id": "thread-1"}}
)
# Agent 能记住之前的对话!
伪代码理解 Checkpointer:
# 没有 Checkpointer:
graph.invoke(input1) → 结果1,不保存任何状态
graph.invoke(input2) → 结果2,完全独立,不记得 input1
# 有 Checkpointer:
graph.invoke(input1, thread_id="chat-1")
→ 保存检查点:{thread: "chat-1", step: 1, state: ...}
graph.invoke(input2, thread_id="chat-1")
→ 读取检查点,恢复上下文 → Agent 知道之前的对话
13.4 获取和更新状态
python
config = {"configurable": {"thread_id": "thread-1"}}
# 获取最新状态
state_snapshot = graph.get_state(config)
# 获取完整历史
history = list(graph.get_state_history(config))
# 手动更新状态(人在回路场景)
graph.update_state(config, {"approved": True})
StateSnapshot 字段详解:
| 字段 | 类型 | 说明 |
|---|---|---|
values |
dict |
当前检查点的状态值 |
next |
tuple[str, ...] |
下一步要执行的节点,空 () 表示图已完成 |
config |
dict |
包含 thread_id、checkpoint_ns、checkpoint_id |
metadata |
dict |
执行元数据:source(来源)、writes(节点输出)、step(超步计数) |
created_at |
str |
ISO 8601 时间戳 |
parent_config |
`dict | None` |
tasks |
tuple[PregelTask, ...] |
当前步骤的任务列表 |
14. 人在回路(Human-in-the-Loop)
14.1 核心机制
使用 interrupt() 暂停执行,使用 Command(resume=...) 恢复执行。
伪代码理解:
# 人在回路 = 流程中的"审批站"
# 执行到审批站时暂停 → 等待人类决策 → 根据决策继续
def approval_node(state):
# 暂停!等待人类审批
approved = interrupt("是否批准此操作?")
# 人类回复后,approved = 人类的回复值
if approved:
return Command(goto="proceed")
else:
return Command(goto="cancel")
14.2 使用 interrupt 暂停
python
from langgraph.types import interrupt
def approval_node(state: State):
# 暂停执行,等待人类输入
# payload 会返回给调用者
approved = interrupt({
"question": "是否批准此操作?",
"details": state["action_details"]
})
# 恢复后,approved = Command(resume=...) 传入的值
return {"approved": approved}
interrupt 触发后的流程:
1. 图执行在 interrupt() 调用处暂停
2. 状态通过 checkpointer 保存
3. interrupt 的 payload 返回给调用者
4. 图无限期等待,直到恢复
5. 恢复时,Command(resume=...) 的值成为 interrupt() 的返回值
6. 节点从头重新执行(interrupt 之前的代码会重新运行)
14.3 使用 Command 恢复
python
from langgraph.types import Command
config = {"configurable": {"thread_id": "thread-1"}}
# 第一次调用 → 触发 interrupt,暂停
result = graph.invoke({"input": "data"}, config=config)
# 恢复执行,传入人类的决策
graph.invoke(Command(resume=True), config=config) # 批准
# 或
graph.invoke(Command(resume=False), config=config) # 拒绝
14.4 常见模式
审批模式:
python
def approval_node(state: State) -> Command:
is_approved = interrupt("是否批准此操作?")
if is_approved:
return Command(goto="proceed")
else:
return Command(goto="cancel")
审核编辑模式:
python
def review_node(state: State):
# 让人类审核 LLM 的输出
edited_content = interrupt({
"message": "请审核以下内容",
"content": state["draft"]
})
return {"final_content": edited_content}
处理多个 interrupt(并行分支中):
python
# 当并行分支同时触发 interrupt 时
result = graph.invoke({"vals": []}, config)
# 恢复时,用字典映射每个 interrupt 的 ID 和回复
resume_map = {
interrupt.id: f"answer for {interrupt.value}"
for interrupt in result.interrupts
}
graph.invoke(Command(resume=resume_map), config)
14.5 interrupt 规则
| 规则 | 说明 |
|---|---|
| 不要用 try/except 包裹 interrupt | 会导致中断机制失效 |
| 不要在节点内重排 interrupt 调用 | 恢复时节点从头执行,顺序必须一致 |
| 不要返回复杂值 | interrupt 的值必须是 JSON 可序列化的 |
| interrupt 前的副作用必须幂等 | 恢复时节点重新执行,副作用会重复 |
15. 流式输出(Streaming)
15.1 三种流式数据类别
| 类别 | 说明 | stream_mode |
|---|---|---|
| 工作流进度 | 每个节点执行后的状态更新 | updates, values |
| LLM Token | 模型生成的 token 流 | messages |
| 自定义更新 | 用户定义的进度信号 | custom |
15.2 流式模式详解
| 模式 | 类型 | 说明 |
|---|---|---|
values |
ValuesStreamPart |
每步后的完整状态快照 |
updates |
UpdatesStreamPart |
每步后的状态增量更新 |
messages |
MessagesStreamPart |
LLM token 流 + 元数据 |
custom |
CustomStreamPart |
自定义数据(通过 get_stream_writer) |
checkpoints |
CheckpointStreamPart |
检查点事件 |
tasks |
TasksStreamPart |
任务开始/完成事件 |
debug |
DebugStreamPart |
所有可用信息(checkpoints + tasks + 额外元数据) |
15.3 基本用法
流式输出状态更新:
python
# updates 模式:只输出变化的字段
for chunk in graph.stream(
{"topic": "ice cream"},
stream_mode="updates",
version="v2",
):
if chunk["type"] == "updates":
for node_name, state in chunk["data"].items():
print(f"节点 {node_name} 更新: {state}")
# values 模式:输出完整状态
for chunk in graph.stream(
{"topic": "ice cream"},
stream_mode="values",
version="v2",
):
if chunk["type"] == "values":
print(f"当前状态: {chunk['data']}")
流式输出 LLM Token:
python
for chunk in graph.stream(
{"messages": [HumanMessage(content="写一首诗")]},
stream_mode="messages",
version="v2",
):
if chunk["type"] == "messages":
msg, metadata = chunk["data"]
if isinstance(msg, AIMessageChunk) and msg.content:
print(msg.content, end="", flush=True)
自定义流式数据:
python
from langgraph.config import get_stream_writer
def my_node(state: State):
writer = get_stream_writer()
writer({"status": "正在处理..."}) # 发出自定义信号
result = do_work(state)
return {"result": result}
for chunk in graph.stream(
input,
stream_mode=["updates", "custom"],
version="v2",
):
if chunk["type"] == "custom":
print(f"进度: {chunk['data']}")
15.4 多模式同时使用
python
for part in graph.stream(
input,
stream_mode=["values", "updates", "messages", "custom"],
version="v2",
):
if part["type"] == "values":
print(f"State: {part['data']}")
elif part["type"] == "updates":
for node_name, state in part["data"].items():
print(f"Node `{node_name}` updated: {state}")
elif part["type"] == "messages":
msg, metadata = part["data"]
print(msg.content, end="", flush=True)
elif part["type"] == "custom":
print(f"Progress: {part['data']['progress']}%")
16. 记忆系统(Memory)
16.1 两种记忆类型
| 类型 | 范围 | 实现方式 | 比喻 |
|---|---|---|---|
| 短期记忆 | 单个会话(thread) | State + Checkpointer | 便签纸:记住当前对话 |
| 长期记忆 | 跨会话、跨线程 | Store | 笔记本:记住用户偏好 |
伪代码理解:
# 短期记忆 = 便签纸
# 只在当前对话中有效,对话结束就没了(除非用 checkpointer 保存)
thread_id = "chat-001"
graph.invoke(input, config={"thread_id": thread_id}) # 同一 thread 共享记忆
# 长期记忆 = 笔记本
# 跨对话、跨线程持久保存,随时可以查阅
store.put(("user", "alice"), "preferences", {"language": "zh"})
store.get(("user", "alice"), "preferences") # 任何线程都能读到
16.2 短期记忆
通过 State + Checkpointer 实现:
python
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
# 第一轮对话
graph.invoke(
{"messages": [HumanMessage(content="我叫小明")]},
config={"configurable": {"thread_id": "chat-1"}}
)
# 第二轮对话(同一 thread_id,Agent 记得之前的内容)
graph.invoke(
{"messages": [HumanMessage(content="我叫什么名字?")]},
config={"configurable": {"thread_id": "chat-1"}}
)
# Agent 回答:"你叫小明"
16.3 长期记忆
通过 Store 实现,跨会话、跨线程共享:
python
from langgraph.store.memory import InMemoryStore
# 创建 Store(生产环境用数据库后端)
store = InMemoryStore(
index={"embed": embed_function, "dims": 128} # 支持语义搜索
)
# 写入记忆
user_id = "alice"
namespace = (user_id, "preferences")
store.put(namespace, "lang", {"language": "zh", "style": "简短"})
# 读取记忆
item = store.get(namespace, "lang")
print(item.value) # {"language": "zh", "style": "简短"}
# 语义搜索
results = store.search(namespace, query="语言偏好")
16.4 记忆的三种类型
| 类型 | 存储内容 | 人类类比 | Agent 示例 |
|---|---|---|---|
| 语义记忆 | 事实和知识 | 学校学到的知识 | 用户偏好、事实信息 |
| 情景记忆 | 过去的经历 | 旅行回忆 | 过去的 Agent 行动记录 |
| 程序记忆 | 规则和技能 | 骑自行车的技能 | Agent 的系统提示词 |
语义记忆示例(用户画像):
python
# 保存用户画像
store.put(("user", "alice"), "profile", {
"name": "Alice",
"language": "zh",
"preferences": ["简短回答", "技术向"]
})
# 在节点中读取
def call_model(state, store: BaseStore):
profile = store.get(("user", "alice"), "profile")
# 根据用户偏好调整回答风格...
程序记忆示例(自我改进提示词):
python
def update_instructions(state, store: BaseStore):
# 读取当前指令
current = store.search(("instructions",))[0]
# 根据对话反馈改进指令
new_instructions = llm.invoke(f"改进指令: {current}\n反馈: {state['feedback']}")
store.put(("instructions",), "agent_a", {"instructions": new_instructions})
17. 时间旅行(Time Travel)
17.1 什么是时间旅行
回溯到之前的状态,从该点重新执行或探索不同路径。
伪代码理解:
# 时间旅行 = 游戏存档
# 每个超步后自动存档(checkpoint)
# 可以回到任意存档点,从那里重新开始
检查点 0: 初始状态
检查点 1: node_a 执行后
检查点 2: node_b 执行后 ← 回到这个点,尝试不同路径
检查点 3: node_c 执行后
17.2 回放(Replay)
python
config = {"configurable": {"thread_id": "thread-1"}}
# 获取历史
history = list(graph.get_state_history(config))
# 回放到某个检查点
old_checkpoint = history[2]
graph.invoke(None, config={
"configurable": {
"thread_id": "thread-1",
"checkpoint_id": old_checkpoint.config["configurable"]["checkpoint_id"]
}
})
17.3 分叉(Fork)
从某个检查点开始,走不同的路径:
python
# 在某个历史点上更新状态,然后继续执行
graph.update_state(
config_with_checkpoint_id,
{"decision": "different_choice"}, # 修改状态
as_node="decision_node" # 假装是某个节点写的
)
# 从修改后的状态继续执行
graph.invoke(None, config=config_with_checkpoint_id)
18. 子图(Subgraphs)
18.1 什么是子图
将复杂图拆分为多个子图,每个子图可以独立开发和测试,然后组合使用。
伪代码理解:
# 子图 = 部门
# 大公司(主图)包含多个部门(子图)
# 每个部门有自己的内部流程
主图:
START → 路由 → 技术部门(子图) → END
→ 创意部门(子图) → END
技术部门(子图):
接收任务 → 分析 → 编码 → 测试 → 返回结果
18.2 使用子图
python
# 子图作为节点
parent_graph.add_node("sub_agent", sub_graph)
# 子图有自己独立的状态
# 父图和子图通过状态转换通信
18.3 检查点命名空间
每个检查点有 checkpoint_ns 字段标识所属图:
| 值 | 含义 |
|---|---|
"" (空字符串) |
父图的检查点 |
"node_name:uuid" |
子图的检查点 |
| `"outer:uuid | inner:uuid"` |
19. 多Agent架构 --- Agent-as-Tool
19.1 核心概念
Agent-as-Tool:主 Agent 把子 Agent 当作可调用的工具。控制权始终在主 Agent 手里,子 Agent 执行完返回结果后,主 Agent 继续决策。
生活化比喻:
Agent-as-Tool = 前台打电话给某部门,等对方回复后继续
前台始终掌控通话,没有把电话转出去
公司客服总台:
员工来电 → 前台接待员(主 Agent)判断问题归属
→ IT 问题:前台打电话问 IT 专员(子 Agent),等回复后继续
→ HR 问题:前台打电话问 HR 专员(子 Agent),等回复后继续
→ 财务问题:前台打电话问财务专员(子 Agent),等回复后继续
前台汇总回答 → END
19.2 架构图
主 Agent (StateGraph)
├── main_llm: 判断需要哪个子 Agent
└── main_tool_node: 调用子 Agent 工具
│
├── call_it_support() ──→ it_graph.invoke(...) ──→ 返回回答
├── call_hr_support() ──→ hr_graph.invoke(...) ──→ 返回回答
└── call_finance() ──→ finance_graph.invoke(...) ──→ 返回回答
│
▼
回到 main_llm → 汇总回答 → END
19.3 关键实现步骤
1. 构建子 Agent 图 --- 每个子 Agent 是一个完整的 StateGraph:
python
# 子 Agent 是完整的 ReAct Agent
it_graph = (
StateGraph(ITState)
.add_node("it_llm", it_llm)
.add_node("it_tool", it_tool_node)
.add_edge(START, "it_llm")
.add_conditional_edges("it_llm", it_router, ["it_tool", END])
.add_edge("it_tool", "it_llm")
.compile()
)
2. 子 Agent 打包为 @tool --- 用装饰器把子图包装成普通工具:
python
@tool
def call_it_support(query: str) -> str:
"""将 IT 相关问题转接给 IT 支持专员(密码、系统状态等)"""
result = it_graph.invoke({"messages": [HumanMessage(content=query)]})
return extract_final_answer(result) # 提取最终 AI 回答
3. 主 Agent 绑定子 Agent 工具 --- 主 Agent 把子 Agent 当作普通工具调用:
python
all_tools = [call_it_support, call_hr_support, call_finance]
main_model = model.bind_tools(all_tools)
# 主 Agent 的标准 ReAct 循环
main_graph = (
StateGraph(MainState)
.add_node("main_llm", main_llm)
.add_node("main_tool", main_tool_node)
.add_edge(START, "main_llm")
.add_conditional_edges("main_llm", main_router, ["main_tool", END])
.add_edge("main_tool", "main_llm") # 子 Agent 返回后,主 Agent 继续决策
.compile()
)
19.4 使用 build_react_agent 简化子 Agent 创建
python
from utils.graph_helpers import build_react_agent
# 一行创建子 Agent(替代手写 StateGraph)
it_sub_agent = build_react_agent(
model.bind_tools(it_tools),
it_tools,
system_prompt="你是 IT 支持专员,擅长解决密码、系统等技术问题。"
)
# 子 Agent 同样包装为 @tool
@tool
def call_it_support(query: str) -> str:
"""将 IT 相关问题转接给 IT 支持专员"""
result = it_sub_agent.invoke({"messages": [HumanMessage(content=query)]})
return extract_final_answer(result)
19.5 核心特征总结
| 特征 | 说明 |
|---|---|
| ✅ 子 Agent 是完整的 StateGraph | 有自己的 LLM + 工具 + 循环 |
| ✅ 子 Agent 被 @tool 包装 | 对主 Agent 来说就是一个普通工具 |
| ✅ 控制权始终在主 Agent | 子 Agent 执行完必须返回,主 Agent 汇总 |
| ✅ 消息流:主→子→主 | 同步调用,子 Agent 不感知主 Agent 的存在 |
20. 多Agent架构 --- Handoff
20.1 核心概念
Handoff(控制权交接):控制权从一个 Agent 完全转移到下一个 Agent,就像接力赛交棒,前一个 Agent 交出控制权后就不再参与。
生活化比喻:
Handoff = 接力赛交棒,接棒的人跑你就不跑了
前台说"这个不归我管",把用户直接带到下一个科室
医院分诊系统:
患者到院 → 分诊护士评估症状
→ 心脏相关 → 转给心内科(护士不再参与)
→ 心内科看后需拍片 → 转给放射科(心内科不再参与)
→ 放射科出报告 → END
20.2 核心 API:Command(goto=...)
python
from langgraph.types import Command
# Command 同时完成两件事:
# 1. goto:指定下一个要执行的节点名(或 END)
# 2. update:更新状态(类似节点返回的 dict)
def triage_nurse(state: HandoffState):
symptom = state["symptom"]
if "心脏" in symptom:
return Command(goto="cardiology", update={
"path": ["分诊"],
"diagnosis": "疑似心脏问题",
})
else:
return Command(goto=END, update={
"path": ["分诊"],
"diagnosis": "常见症状,直接处理",
})
20.3 架构图
START → 分诊护士 →(症状判断)
├── 心脏相关 → 心内科 →(需拍片?)
│ ├── 需要放射科 → 放射科 → END
│ └── 不需要 → END
├── 神经相关 → 神经内科 → END
└── 其他 → 全科医生 → END
关键:不需要 add_conditional_edges!
每个节点用 Command(goto=...) 自己决定去哪
20.4 图构建方式
python
# Handoff 模式:不需要条件边!只需连接起点
graph = (
StateGraph(HandoffState)
.add_node("triage", triage_nurse)
.add_node("cardiology", cardiology)
.add_node("neurology", neurology)
.add_node("radiology", radiology)
.add_node("general", general)
.add_edge(START, "triage") # 只需连起点!
.compile()
)
# 后续所有跳转都由 Command(goto=...) 在运行时决定
与条件分支对比:
python
# 传统条件分支:需要在图构建时预设路由映射
.add_conditional_edges("triage", route, {
"cardiology": "cardiology",
"neurology": "neurology",
})
# Handoff 模式:运行时动态决定,无需预设映射
.add_edge(START, "triage") # 只需连起点
20.5 LLM 版 Handoff
用结构化输出约束 LLM 的决策:
python
from pydantic import BaseModel, Field
from typing import Literal
class TriageDecision(BaseModel):
department: Literal["cardiology", "neurology", "general", "end"] = Field(
description="应该转到的科室"
)
reason: str = Field(description="分诊理由")
triage_model = model.with_structured_output(TriageDecision)
def triage_nurse(state: HandoffState):
decision = triage_model.invoke(f"患者症状:{state['symptom']}")
# LLM 只能返回四个值之一,确保输出可控
return Command(goto=decision.department, update={
"path": ["分诊"],
"diagnosis": f"分诊理由: {decision.reason}",
})
20.6 核心特征总结
| 特征 | 说明 |
|---|---|
| ✅ 用 Command(goto=...) 代替 add_conditional_edges | 运行时动态决定去向 |
| ✅ 控制权一旦交出就不回来 | 每个节点独立决策,前一个节点不参与后续 |
| ✅ 不需要在图构建时预设所有路由映射 | 灵活性更强 |
| ✅ 适合"接力式"流程 | A → B → C → END(线性或分支) |
21. 多Agent架构 --- Supervisor
21.1 核心概念
Supervisor(监督者):一个主管 Agent 管理多个工人 Agent 的工作流。工人完成后必须回到主管汇报,主管审查后再分配下一个任务,形成循环审查的工作流。
生活化比喻:
Supervisor = 装修队长
队长负责:找工人、看进度、验收、决定下一步
工人干完活必须回来找队长汇报
队长审查后:满意 → 派下一个工人,不满意 → 让同一工人重做
软件开发团队:
项目经理(Supervisor) → 分配研究员调研 → 研究员回来汇报
→ 审查后分配程序员编码 → 程序员回来汇报
→ 审查后分配评审员验收 → 评审员回来汇报
→ 项目经理认为完成 → END
21.2 架构图
┌──────────────────────────────────┐
│ supervisor(主管) │
│ 唯一决策者:分配 + 审查 + 决定结束 │
└────┬─────────┬──────────┬────────┘
│ │ │
分配任务 │ │ │ 汇报结果
▼ ▼ ▼
┌────────┐┌────────┐┌────────┐
│worker A││worker B││worker C│
│研究员 ││程序员 ││评审员 │
└───┬────┘└───┬────┘└───┬────┘
│ │ │
└─────────┴─────────┘
│
Command(goto="supervisor")
工人不能自行结束,必须回到主管
21.3 关键实现步骤
1. 状态设计 --- 迭代计数器防止无限循环:
python
class SupervisorState(TypedDict):
messages: Annotated[list, add_messages]
task: str # 项目需求
next: str # 下一个工人(由 supervisor 决定)
work_product: str # 当前工人的产出
all_products: list # 所有产出汇总
iteration: int # 迭代计数器(防无限循环)
2. Supervisor 节点 --- 用结构化输出决定分配:
python
class SupervisorDecision(BaseModel):
next_worker: Literal["researcher", "coder", "reviewer", "FINISH"] = Field(
description="下一个要分配的工人,或 FINISH 表示完成"
)
reasoning: str = Field(description="为什么分配给这个工人")
supervisor_model = model.with_structured_output(SupervisorDecision)
def supervisor(state: SupervisorState):
# 安全保护:超过迭代上限强制结束
if state["iteration"] >= 5:
return Command(goto=END, update={"next": "FINISH"})
# LLM 智能决策下一个工人
decision = supervisor_model.invoke(
f"任务:{state['task']}。已有进展:{state['all_products']}。"
)
if decision.next_worker == "FINISH":
return Command(goto=END, update={"next": "FINISH"})
return Command(goto=decision.next_worker, update={
"next": decision.next_worker,
})
3. Worker 节点 --- 干完活必须回到 supervisor:
python
def researcher(state: SupervisorState):
# 执行调研工作...
product = "调研报告:..."
return Command(goto="supervisor", update={ # ★ 必须回到 supervisor
"work_product": product,
"all_products": state["all_products"] + [product],
"iteration": state["iteration"] + 1,
})
4. 图构建 --- 同样只需连接起点:
python
graph = (
StateGraph(SupervisorState)
.add_node("supervisor", supervisor)
.add_node("researcher", researcher)
.add_node("coder", coder)
.add_node("reviewer", reviewer)
.add_edge(START, "supervisor") # 只需连起点
.compile()
)
21.4 无 LLM 版 vs LLM 版
| 维度 | 无 LLM 版 | LLM 版 |
|---|---|---|
| 分配策略 | 固定顺序(iteration % 3) |
LLM 根据任务状态智能决定 |
| 结束判断 | 迭代次数到了强制结束 | LLM 判断可交付则提前结束 |
| 工人产出 | 硬编码模板文本 | LLM 扮演角色生成真实内容 |
| 审查能力 | 无 | reviewer 用 LLM 审查并给出建议 |
21.5 核心特征总结
| 特征 | 说明 |
|---|---|
| ✅ Supervisor 是唯一做决策的 Agent | 分配任务、审查结果、决定何时结束 |
| ✅ 所有 Worker 完成后必须回到 Supervisor | Command(goto="supervisor") |
| ✅ 可多次循环审查 | 不满意 → 再分配 → 再审查 |
| ✅ 用 iteration 防止无限循环 | 达到上限强制 END |
22. 三大多Agent模式对比
22.1 一图对比
┌──────────────────────────────────────────────────────────────────────┐
│ │
│ Agent-as-Tool Handoff Supervisor │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 主 Agent │ │ 节点 A │ │ 主管Agent │ │
│ │ ↓ 调用 │ │ ↓ 交出 │ │ ↓ 分配 │ │
│ │ 子Agent │ │ 节点 B │ │ 工人Agent │ │
│ │ ↓ 返回 │ │ ↓ 交出 │ │ ↑ 汇报 │ │
│ │ 主Agent │ │ 节点 C │ │ 主管审查 │ │
│ │ ↓ 继续 │ │ ↓ 结束 │ │ ↓ 再分配 │ │
│ │ END │ │ END │ │ ...循环 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 控制:主Agent掌控 控制:交出不再回来 控制:主管循环管理 │
│ 比喻:打电话问同事 比喻:接力赛交棒 比喻:包工头派活 │
│ API: @tool + bind_tools API: Command(goto) API: Command(goto) │
│ │
└──────────────────────────────────────────────────────────────────────┘
22.2 详细对比表
| 维度 | Agent-as-Tool | Handoff | Supervisor |
|---|---|---|---|
| 控制权 | 始终在主 Agent | 交出后不再回来 | 主管循环管理 |
| 子Agent关系 | 主 Agent 的"工具" | 独立接力的"继任者" | 主管的"下属" |
| 子Agent能否自行结束 | 否,必须返回主 Agent | 是,可以决定 END | 否,必须回到主管 |
| 循环能力 | 无(调用一次返回) | 否(线性接力) | 是(审查→再分配) |
| 路由方式 | bind_tools + tool_calls | Command(goto=...) | Command(goto=...) |
| 图构建 | 标准 ReAct 循环 | 只需 add_edge(START, ...) | 只需 add_edge(START, ...) |
| 适合场景 | 客服总台、专业咨询 | 分诊、任务转交 | 项目管理、迭代改进 |
| 迭代保护 | 不需要 | 不需要 | 必须有 iteration 上限 |
| 比喻 | 打电话问同事 | 接力赛交棒 | 包工头派活 |
22.3 选型决策
你的多 Agent 需要什么?
1. 主 Agent 需要掌控全局,子 Agent 只是"咨询顾问"?
→ ✅ Agent-as-Tool
(客服总台、专业咨询、工具式调用)
2. 任务需要"接力"完成,每个 Agent 独立决策后交出控制权?
→ ✅ Handoff
(医院分诊、任务转交、线性流程)
3. 需要一个"主管"统一管理,循环审查直到满意?
→ ✅ Supervisor
(项目管理、迭代开发、质量驱动的工作流)
4. 需要结合多种模式?
→ 可以!三种模式可以在同一个图中组合使用
(例如:Supervisor 管理多个 Agent-as-Tool 的子 Agent)
23. DeepAgents --- 开箱即用的 Agent 框架
23.1 什么是 DeepAgents
DeepAgents 是 LangChain 团队在 LangGraph 之上封装的"batteries-included"Agent 框架。它用一行代码创建拥有完整能力的 Agent。
层级关系:
手动 StateGraph ← 最底层,完全控制
↑
create_react_agent() ← 封装了标准 ReAct 循环
↑
create_deep_agent() ← 再封装:内置文件系统、任务规划、子代理、Skills 等
一句话:create_deep_agent() 返回的仍然是一个标准 LangGraph StateGraph,
只是它自动帮你装了所有常用工具,不用自己一个个加了。
生活化比喻:
手动 StateGraph = 自己买零件组装电脑
create_react_agent() = 买品牌整机(标准配置)
create_deep_agent() = 买品牌整机 + 预装所有常用软件
23.2 六大核心内置能力
| 能力 | 说明 | 核心工具/API |
|---|---|---|
| 1. 文件系统工具 | Agent 自动拥有文件读写能力 | ls / read_file / write_file / edit_file / glob / grep |
| 2. 任务规划 | 把复杂任务拆解为 TODO 列表 | write_todos |
| 3. 子代理 | 主 Agent 可委派任务给独立子 Agent | SubAgent(name, description, model, system_prompt) |
| 4. Skills(技能文件) | 通过 SKILL.md 定义可复用行为 | skills=["./skills/"] |
| 5. 上下文工程 | 管理长期会话的上下文 | memory_paths |
| 6. 模型灵活性 | 支持任何 LangChain 支持的模型 | openai:gpt-5.5、anthropic:claude-sonnet-4 等 |
23.3 架构一图览
create_deep_agent(model, tools, ...)
│
├── 内置工具
│ ├── write_todos (任务规划)
│ ├── ls (目录浏览)
│ ├── read_file (文件读取)
│ ├── write_file (文件写入)
│ ├── edit_file (文件编辑)
│ ├── glob (文件搜索)
│ └── grep (内容搜索)
│
├── 用户自定义工具
│ └── 通过 tools=[...] 传入
│
├── 子代理
│ └── 通过 subagents=[Subagent(...)] 传入
│
├── Skills
│ └── 通过 skills=["./skills/"] 传入 SKILL.md 目录
│
└── 文件系统后端
└── 通过 backend=FilesystemBackend(root_dir="./workspace") 配置
所有组件组合后 → 返回标准 LangGraph StateGraph
→ 可以 invoke() / stream() / 接入 LangSmith
23.4 快速入门
最简 DeepAgent:
python
from deepagents import create_deep_agent
from langchain_core.tools import tool
@tool
def calculate(expression: str) -> str:
"""执行数学表达式计算"""
return str(eval(expression))
# ★ 一行创建 DeepAgent!
agent = create_deep_agent(
model=model,
tools=[calculate],
system_prompt="你是一个智能助手。",
)
result = agent.invoke({"messages": [HumanMessage(content="123乘456=?")]})
带文件系统的 DeepAgent:
python
from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend
import tempfile
# 配置文件系统后端(安全沙箱)
workspace = tempfile.mkdtemp(prefix="deepagent_workspace_")
backend = FilesystemBackend(root_dir=workspace, virtual_mode=False)
agent = create_deep_agent(
model=model,
backend=backend, # Agent 自动拥有 ls/read_file/write_file/edit_file/glob/grep
system_prompt="你是写作助手,可以读写文件。",
)
# Agent 可以自主创建文件!
result = agent.invoke({
"messages": [HumanMessage(content="请创建 hello.txt,写一句问候语")]
})
23.5 子代理(SubAgents)
子代理是上下文隔离的独立 Agent:
python
from deepagents import create_deep_agent, SubAgent
# 定义子代理
data_analyst = SubAgent(
name="data_analyst",
description="分析数据并生成统计报告",
model=model,
system_prompt="你是数据分析师,分析统计特征并返回报告。",
)
# 注册子代理
agent = create_deep_agent(
model=model,
subagents=[data_analyst],
system_prompt="你是项目经理。遇到数据分析任务请委派给数据分析师。",
)
# 主 Agent 遇到数据分析任务时,自动委派给子代理
result = agent.invoke({
"messages": [HumanMessage(content="分析这组数据:[12, 45, 23, 67, 34]")]
})
伪代码理解子代理:
# 子代理 = 专业外包
# 主 Agent = 项目经理
# 项目经理遇到专业任务时,委派给外包(子代理)
# 外包完成后交回结果,不污染主对话
主 Agent: "这组数据 [12,45,23,67] 需要分析"
→ 判断:这是数据分析任务
→ 委派给 data_analyst 子代理
→ 子代理独立执行(有自己的上下文、工具、LLM)
→ 返回分析报告
→ 主 Agent 继续对话
23.6 Skills(技能文件)
Skills 是通过 SKILL.md 文件定义的可复用行为模板:
python
# 1. 创建 SKILL.md 文件
skill_content = """---
name: 代码审查
description: 审查代码并指出潜在问题、改进建议
triggers: 当用户请求审查代码、检查代码质量时
---
# 代码审查技能
你是一个资深代码审查员。审查代码时请:
1. 指出潜在 bug 和边界情况
2. 建议代码风格和改进方案
3. 评估时间/空间复杂度
"""
# 2. 加载技能
agent = create_deep_agent(
model=model,
backend=backend,
skills=["./skills/"], # Agent 自动扫描 SKILL.md 文件
system_prompt="根据用户需求自动使用合适的技能。",
)
# 3. 触发技能
result = agent.invoke({
"messages": [HumanMessage(content="请审查这段代码:def avg(n): return sum(n)/len(n)")]
})
# Agent 自动发现并加载"代码审查"技能
Skills 的优势:
| 优势 | 说明 |
|---|---|
| 文件化 | 技能定义在文件中,版本控制友好 |
| 可共享 | 多个 Agent 可共享同一技能 |
| 即改即用 | 不用重新训练,改文件即改技能 |
| 自动发现 | Agent 运行时自动扫描、按需加载 |
23.7 三种构建方式对比
| 方式 | 代码量 | 内置能力 | 自定义度 | 适合场景 |
|---|---|---|---|---|
| 手动 StateGraph | ~50 行 | 无 | ★★★★★ | 需要完全控制每个节点和边 |
| create_react_agent() | 1 行 | 工具调用 | ★★★ | 标准 ReAct Agent,不需要文件操作 |
| create_deep_agent() | 1 行 | 全部内置 | ★★★ | 需要文件系统/子代理/Skills 的完整 Agent |
23.8 何时用/不用 DeepAgents
✅ 何时用 DeepAgents:
- 需要 Agent 读写文件
- 需要子代理委派任务
- 需要 Skills 插件系统
- 需要任务规划(TODO 列表)
- 快速搭建生产级 Agent
❌ 何时不用:
- 只需要简单工具调用(用 create_react_agent() 就够了)
- 需要精细控制图结构(手动 StateGraph)
- 教学理解底层原理(先学手动构建)
24. LangGraph vs LangChain vs Dify 全面对比
24.1 三者定位总览
┌─────────────────────────────────────────────────────────────────────────┐
│ │
│ 低代码/可视化 ◄──────────────────────────────────────────► 纯代码 │
│ │
│ Dify LangGraph LangChain │
│ ┌──────┐ ┌──────────┐ ┌──────────┐ │
│ │ 平台 │ │ 编排引擎 │ │ 组件框架 │ │
│ │ │ │ │ │ │ │
│ │可视化 │ │图结构编排 │ │链式组合 │ │
│ │拖拽式 │ │状态管理 │ │模型/工具 │ │
│ │全栈 │ │循环/分支 │ │数据连接 │ │
│ └──────┘ └──────────┘ └──────────┘ │
│ │
│ 面向:产品经理/运营 面向:工程师 面向:工程师 │
│ 门槛:低 门槛:中 门槛:高 │
│ 灵活度:★★ 灵活度:★★★★★ 灵活度:★★★★ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
24.2 核心区别
| 维度 | Dify | LangGraph | LangChain |
|---|---|---|---|
| 本质 | AI 应用开发平台(BaaS + LLMOps) | 工作流编排引擎 | LLM 应用组件框架 |
| 开发方式 | 可视化拖拽 + 低代码 | 纯代码(Python) | 纯代码(Python/JS) |
| 核心理念 | "让 AI 应用开发像搭积木" | "节点干活,边决定下一步" | "组合(Chaining)" |
| 工作流模型 | DAG(有向无环图) | 状态图(支持循环) | 线性链(Chain) |
| 是否支持循环 | 有限支持 | 原生支持 | 不支持 |
| 状态管理 | 节点间上下文变量传递 | 显式 State + Reducer | 隐式 Memory |
| 开源协议 | Apache 2.0(部分功能企业版) | MIT | MIT |
| 目标用户 | 产品经理、运营、低代码开发者 | 后端工程师、AI 工程师 | 全栈工程师、数据工程师 |
24.3 各自优缺点
Dify
| 优点 | 缺点 |
|---|---|
| 极速原型:几小时搭建可用 Demo | 灵活度受限:复杂循环/动态分支难以可视化表达 |
| 低门槛:非技术人员也能参与开发 | 黑盒风险:可视化背后逻辑不透明,调试困难 |
| 内置 RAG:文档上传→分块→检索一站式 | 扩展性瓶颈:超出平台能力时需深度定制 |
| 多模型兼容:100+ LLM 统一接口切换 | 版本管理弱:工作流 JSON 不如代码方便 diff/review |
| 全栈能力:Prompt 管理、日志、监控、API 一体化 | 循环支持有限:DAG 模型天然不支持复杂循环 |
| 国产化友好:深度适配通义千问、讯飞星火等 | 代码审查难:非代码资产难以纳入 CI/CD |
LangGraph
| 优点 | 缺点 |
|---|---|
| 极致灵活:任意复杂的图结构 | 学习曲线陡:需理解 State/Reducer/Send 等概念 |
| 原生循环:ReAct Agent、迭代优化天然支持 | 无 UI:纯代码,非技术人员无法参与 |
| 持久化:Checkpoint + 人在回路 + 时间旅行 | 需自建基础设施:监控、部署、API 需自行搭建 |
| 流式输出:7 种流式模式,精细控制 | 生态依赖:依赖 LangChain 的模型/工具接口 |
| 生产级:容错恢复、状态管理、可扩展 | 样板代码多:简单 Agent 也需 20+ 行 |
LangChain
| 优点 | 缺点 |
|---|---|
| 生态最丰富:300+ 集成,社区最大 | 线性限制:Chain 是线性的,不支持循环 |
| 组件化:Model/Prompt/Tool/Parser 即插即用 | 抽象过重:多层抽象导致调试困难 |
| 快速上手:几行代码完成 LLM 调用 | 状态管理弱:Memory 机制不够灵活 |
| 多语言:Python + JavaScript/TypeScript | 版本不稳定:API 频繁变动,迁移成本高 |
24.4 典型场景详解
场景一:企业知识库问答(RAG)
| 工具 | 适合度 | 说明 |
|---|---|---|
| Dify | ★★★★★ | 内置文档处理→分块→向量化→检索→生成全流程,非技术人员 1 小时搞定 |
| LangChain | ★★★★ | 丰富的文档加载器和检索器,但需自行组装 |
| LangGraph | ★★★ | 需手动集成向量库,但可实现更复杂的检索策略 |
场景二:多步推理 Agent(ReAct)
| 工具 | 适合度 | 说明 |
|---|---|---|
| LangGraph | ★★★★★ | 原生支持循环,create_react_agent() 一行搞定 |
| LangChain | ★★★ | 有 AgentExecutor,但黑盒、不可控、难调试 |
| Dify | ★★ | Agent 功能有限,复杂工具链难以编排 |
场景三:审批流程(人在回路)
| 工具 | 适合度 | 说明 |
|---|---|---|
| LangGraph | ★★★★★ | interrupt() + Command(resume=...) 原生支持 |
| Dify | ★★★ | 人工节点支持,但灵活度不如代码 |
| LangChain | ★ | 无原生支持,需自行实现 |
场景四:内部工具快速上线
| 工具 | 适合度 | 说明 |
|---|---|---|
| Dify | ★★★★★ | 拖拽→配置→发布 API→嵌入业务系统,全流程 1 天 |
| LangChain | ★★★ | 需自行搭建 API 服务和前端 |
| LangGraph | ★★ | 需自行搭建 API 服务、监控、运维 |
场景五:复杂多 Agent 协作
| 工具 | 适合度 | 说明 |
|---|---|---|
| LangGraph | ★★★★★ | 子图 + Send + 状态管理,天生为多 Agent 设计 |
| LangChain | ★★ | 需手动编排多 Agent 交互 |
| Dify | ★★ | 单 Agent 场景足够,多 Agent 协作受限 |
24.5 组合使用策略
策略一:Dify 做前端 + LangGraph 做后端
Dify(可视化界面、RAG、用户管理)
→ API 调用 → LangGraph(复杂 Agent 逻辑、人在回路)
策略二:LangChain 做组件 + LangGraph 做编排
LangChain(Model、Tool、Parser、Retriever)
→ LangGraph(StateGraph 编排、循环、持久化)
策略三:Dify 快速验证 → LangGraph 生产重构
第一步:用 Dify 几小时验证想法
第二步:验证通过后,用 LangGraph 重写为生产级代码
24.6 选型决策速查表
| 你的需求 | 推荐 | 理由 |
|---|---|---|
| 快速验证 AI 想法,非技术人员参与 | Dify | 可视化、低门槛、内置 RAG |
| 简单的 LLM 调用链(翻译、摘要) | LangChain | 最简单,几行代码搞定 |
| 需要 Agent 循环(ReAct) | LangGraph | 原生循环支持 |
| 需要人在回路/审批 | LangGraph | interrupt() 原生支持 |
| 企业知识库问答 | Dify | 内置 RAG 全流程 |
| 复杂多 Agent 协作 | LangGraph | 子图 + Send + 状态管理 |
| 需要持久化/容错恢复 | LangGraph | Checkpointer 原生支持 |
| 内部工具快速上线 | Dify | 一键发布 API |
| 需要精细控制 Agent 行为 | LangGraph | 完全可控,无黑盒 |
| 已有 LangChain 项目,需加循环 | LangGraph | 无缝集成 LangChain 组件 |
25. API 速查表
核心 API
python
from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.graph.message import add_messages
from langgraph.types import Send, Command, interrupt
from langgraph.prebuilt import create_react_agent
from langgraph.config import get_stream_writer
from langgraph.checkpoint.memory import MemorySaver
from langgraph.store.memory import InMemoryStore
图构建
| API | 说明 |
|---|---|
StateGraph(state_schema) |
创建图 |
.add_node(name, func) |
添加节点 |
.add_edge(from, to) |
添加普通边 |
.add_conditional_edges(source, router, mapping) |
添加条件边 |
.compile(checkpointer=..., interrupt_before=..., interrupt_after=...) |
编译图 |
.invoke(input, config) |
执行图 |
.stream(input, stream_mode=..., version="v2") |
流式执行 |
状态定义
| API | 说明 |
|---|---|
TypedDict |
定义状态 Schema(最常用) |
Annotated[type, reducer] |
带 reducer 的状态字段 |
add_messages |
消息列表的智能 reducer |
operator.add |
列表追加 reducer |
MessagesState |
内置消息状态 |
Overwrite |
绕过 reducer 直接覆盖 |
工具相关
| API | 说明 |
|---|---|
@tool |
装饰器,定义工具 |
model.bind_tools(tools) |
绑定工具到模型 |
ToolMessage(content, tool_call_id) |
工具执行结果消息 |
model.with_structured_output(Schema) |
结构化输出 |
持久化与记忆
| API | 说明 |
|---|---|
MemorySaver() |
内存 Checkpointer |
graph.get_state(config) |
获取当前状态 |
graph.get_state_history(config) |
获取状态历史 |
graph.update_state(config, values) |
手动更新状态 |
InMemoryStore() |
内存 Store(长期记忆) |
store.put(namespace, key, value) |
写入长期记忆 |
store.get(namespace, key) |
读取长期记忆 |
store.search(namespace, query=...) |
搜索长期记忆 |
人在回路与 Command
| API | 说明 |
|---|---|
interrupt(value) |
暂停等待人类输入 |
Command(resume=value) |
恢复执行 |
Command(goto=node_name) |
控制流程跳转(Handoff/Supervisor) |
Command(update=state_dict) |
更新状态并跳转 |
Command(goto=..., update=...) |
同时跳转和更新 |
流式输出
| API | 说明 |
|---|---|
graph.stream(input, stream_mode="updates") |
流式状态更新 |
graph.stream(input, stream_mode="messages") |
流式 LLM Token |
graph.stream(input, stream_mode="custom") |
流式自定义数据 |
get_stream_writer() |
在节点内发送自定义流式数据 |
多Agent与DeepAgents
| API | 说明 |
|---|---|
Send(node_name, state) |
并行扇出 |
Command(goto=...) |
控制权跳转(Handoff/Supervisor 核心) |
create_deep_agent(model, tools, ...) |
创建 DeepAgent |
SubAgent(name, description, model, ...) |
定义子代理 |
FilesystemBackend(root_dir=...) |
文件系统后端 |
skills=["./skills/"] |
加载 Skills 目录 |
进阶篇完 --- 基础内容(核心概念、状态、节点、边、构建图、ReAct Agent、工作流、工具、结构化输出、消息、Send API)见 LangGraph知识点总结_基础篇.md