进阶能力
第 2 章让你能搭出 Agent,这一章解决"复杂、并行、有长期记忆、能部署"——也是 JD 里"高可用/高扩展 Agent 工程"的具体抓手。
3.1 子图 Subgraph:封装与复用
当图变大,你需要把一块逻辑封装成"子图",像搭乐高一样组合。回忆第 1 章:编译后的图是个 Runnable,所以一个图可以直接当节点塞进另一个图。
# 子图:一个独立的小图(比如一个专精的检索 Agent)
sub_builder = StateGraph(SubState)
sub_builder.add_node(...)
subgraph = sub_builder.compile()
# 父图:直接把编译好的子图当成一个节点
parent = StateGraph(ParentState)
parent.add_node("retriever_agent", subgraph) # ← 子图作为节点
parent.add_node("writer", writer_node)
关键:父子图的 State 怎么对接
| 情况 | 做法 |
|---|---|
| 父子共享部分 State key(同名通道) | 直接把子图当节点加,LangGraph 自动按同名 key 传递 |
| 父子 State 结构不同 | 包一层普通节点,在里面手动 subgraph.invoke(转换后的输入),再把输出映射回父 State |
多智能体系统里,每个 sub-agent 就是一个子图。子图让你能独立开发、独立测试、独立复用每个 Agent,再由上层编排——这是工程上管理复杂度的核心手段。
3.2 Send:并行 map-reduce(动态扇出)
第 1 章讲了 Send 的原理,这里给一个完整可跑的 map-reduce:把一个主题列表,并行生成多个结果,再归并、选最优。
import operator
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.types import Send
class State(TypedDict):
topics: list
drafts: Annotated[list, operator.add] # 累积通道:收集并行结果
best: str
def write_one(state: dict): # map:处理单个主题(局部 state)
return {"drafts": [f"《{state['topic']}》的草稿…"]}
def dispatch(state: State): # 运行时扇出:有几个主题就并行几路
return [Send("write_one", {"topic": t}) for t in state["topics"]]
def reduce_best(state: State): # reduce:所有草稿到齐后选最优
return {"best": max(state["drafts"], key=len)}
b = StateGraph(State)
b.add_node("write_one", write_one)
b.add_node("reduce_best", reduce_best)
b.add_conditional_edges(START, dispatch, ["write_one"])
b.add_edge("write_one", "reduce_best") # 所有并行分支汇到 reduce
b.add_edge("reduce_best", END)
graph = b.compile()
graph.invoke({"topics": ["AI", "露营", "做饭"]})
3 个 write_one 在同一超步并行执行,各自往 drafts 累积通道写;超步结束的"同步屏障"保证 3 份草稿被 operator.add 安全合并;下一超步 reduce_best 才被激活——这就是第 1 章 BSP 模型的实战体现。
3.3 Command:动态路由 + 跨图跳转
原理见第 1 章。这里给多智能体里最常用的一招:子 Agent 干完,用 Command 直接把控制权交回父图的 supervisor。
from langgraph.types import Command
from typing import Literal
def research_agent(state) -> Command[Literal["supervisor"]]:
result = do_research(state)
return Command(
update={"messages": [result]}, # 写回结果
goto="supervisor", # 交回主管,由它决定下一步
graph=Command.PARENT, # 跳到【父图】的 supervisor(跨图)
)
对比记忆:条件边适合"纯看状态的简单分支";Command适合"决策与状态更新耦合、或要跨图跳"的场景。第 4 章多智能体案例会大量用到。
3.4 跨会话长期记忆:Store
第 2 章的 Checkpointer 是会话内记忆(同一 thread)。但"记住这个用户上个月说他喜欢露营"是跨会话的长期记忆——这要用 Store。
| Checkpointer | Store | |
|---|---|---|
| 作用域 | 单个 thread(一次对话) | 跨 thread、跨用户(长期) |
| 存什么 | 每步的完整 State 快照 | 你主动写入的"记忆条目"(键值/带命名空间) |
| 典型用途 | 多轮上下文、续跑、HITL | 用户画像、长期偏好、知识沉淀 |
from langgraph.store.memory import InMemoryStore
store = InMemoryStore() # 生产用 PostgresStore,可带向量检索
graph = builder.compile(checkpointer=memory, store=store)
# 节点里通过注入的 store 读写长期记忆(按 namespace 隔离用户)
def node(state, *, store):
ns = ("user-42", "prefs")
store.put(ns, "hobby", {"value": "露营"}) # 写
items = store.search(ns) # 读/检索
return {...}
被问"Agent 的记忆怎么做"时,清晰区分短期(checkpointer/thread 内)和长期(store/跨会话),再提一句长期记忆可结合向量检索做语义召回——这正是研究报告里高频考的 Memory 机制。
3.5 生产级持久化
开发用 MemorySaver(进程内,重启即丢)。上生产要换成落盘/数据库的 checkpointer。
# SQLite(单机/轻量)
from langgraph.checkpoint.sqlite import SqliteSaver
with SqliteSaver.from_conn_string("checkpoints.sqlite") as cp:
graph = builder.compile(checkpointer=cp)
# Postgres(生产/多实例共享)
from langgraph.checkpoint.postgres import PostgresSaver
with PostgresSaver.from_conn_string("postgresql://...") as cp:
cp.setup() # 首次建表
graph = builder.compile(checkpointer=cp)
异步服务用对应的 AsyncSqliteSaver / AsyncPostgresSaver + ainvoke/astream。多实例部署时,Postgres 版让任意实例都能恢复同一会话——这是水平扩展的关键。
3.6 流式细节:不止打字机
第 2 章提了 stream,这里讲生产里真正要用的两个深入点。
① 组合 stream_mode
# 同时要"每步状态"和"逐 token",传一个列表
for mode, chunk in graph.stream(inputs, config,
stream_mode=["updates", "messages"]):
if mode == "messages":
token, meta = chunk
print(token.content, end="") # 前端打字机
elif mode == "updates":
print(chunk) # 哪个节点改了啥,用于状态条
② astream_events:细到每个 LLM/工具调用
需要"工具开始执行""LLM 第一个 token"这类细粒度事件(做复杂前端、埋点)时,用 astream_events,它吐出整棵 Runnable 调用树的事件流。
3.7 部署上线
编译后的图就是个 Runnable,部署有三条路,由轻到重:
| 方式 | 适用 |
|---|---|
自己包 API:FastAPI 里调 graph.ainvoke/astream | 最灵活,融入已有后端;自管持久化与并发 |
langgraph-cli 本地开发:langgraph dev 起本地服务 + 自带可视化调试台(LangGraph Studio) | 开发期调试 Agent,看图执行、改状态、重跑 |
| LangGraph Platform / Server:官方托管运行时,内置持久化、任务队列、HITL、cron | 不想自己造轮子的生产部署 |
pip install "langgraph-cli[inmem]"
# 项目根放一个 langgraph.json 声明图入口,然后:
langgraph dev # 本地起服务 + Studio 可视化调试
"怎么部署 LangGraph?"——讲清三层:本地 langgraph dev 调试 → 自包 FastAPI(配 Postgres checkpointer 支持多实例)→ 或上 LangGraph Platform。再点一句"持久化用 Postgres 版 checkpointer 才能水平扩展",就到位了。