LangGraph Agent 开发指南(10~子图 Subgraphs)

一、什么是子图?

1.1 通俗解释

想象你在搭建一个大型乐高城堡:

复制代码
没有子图:
  你需要一块一块地搭建整个城堡
  每次都要从零开始,很难复用

有子图:
  你可以先搭建好"塔楼"、"城墙"、"大门"等组件
  然后像搭积木一样组合这些组件
  每个组件都可以独立使用或复用

子图就是把一个完整的图当作另一个图的"节点"来使用:

复制代码
父图(Parent Graph)
├── 节点A
├── 子图B(本身就是一个完整的图)
│   ├── 节点B1
│   ├── 节点B2
│   └── 节点B3
└── 节点C

执行顺序:A → B(B1→B2→B3) → C

1.2 子图的作用

作用 说明 例子
构建多代理系统 每个代理是一个子图 研究代理 + 写作代理 + 审核代理
代码复用 同一套逻辑多处使用 多个地方都需要"搜索+总结"流程
模块化开发 不同团队独立开发 团队A开发子图A,团队B开发子图B
封装复杂性 隐藏内部实现细节 外部只需知道输入输出

1.3 核心概念

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    子图架构示意                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  父图                                                       │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                                                     │   │
│  │   节点1 ──▶ 子图节点 ──▶ 节点3                      │   │
│  │              │                                      │   │
│  │              ▼                                      │   │
│  │         ┌─────────┐                                │   │
│  │         │  子图   │                                │   │
│  │         │ 节点A   │                                │   │
│  │         │   ↓     │                                │   │
│  │         │ 节点B   │                                │   │
│  │         │   ↓     │                                │   │
│  │         │ 节点C   │                                │   │
│  │         └─────────┘                                │   │
│  │                                                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、父图与子图的通信方式

2.1 两种通信方式

子图的关键问题是:父图和子图如何交换数据?

复制代码
方式一:共享状态模式
  父图和子图有相同的状态键(如 messages)
  数据自动传递,无需转换

方式二:不同状态模式
  父图和子图的状态完全不同
  需要手动转换数据

2.2 方式对比

对比项 共享状态模式 不同状态模式
状态定义 父子图有共同的状态键 父子图状态完全不同
数据传递 自动传递 手动转换
实现方式 直接添加子图为节点 在节点函数中调用子图
适用场景 多代理共享消息历史 每个子图需要私有状态
复杂度 简单 较复杂

三、共享状态模式

3.1 什么是共享状态?

复制代码
父图状态:{foo: str, bar: str}
子图状态:{foo: str, baz: str}

共享状态键:foo
私有状态键:bar(父图独有)、baz(子图独有)

3.2 使用方法

步骤:

  1. 定义子图并编译
  2. 将编译后的子图直接添加为父图的节点
python 复制代码
from typing_extensions import TypedDict
from langgraph.graph.state import StateGraph, START

# 定义共享状态
class State(TypedDict):
    foo: str

# 子图定义
def subgraph_node_1(state: State):
    return {"foo": "hi! " + state["foo"]}

subgraph_builder = StateGraph(State)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph = subgraph_builder.compile()

# 父图定义
builder = StateGraph(State)
builder.add_node("node_1", subgraph)  # 直接添加子图
builder.add_edge(START, "node_1")
graph = builder.compile()

3.3 完整示例

python 复制代码
from typing_extensions import TypedDict
from langgraph.graph.state import StateGraph, START

# 子图状态
class SubgraphState(TypedDict):
    foo: str    # 与父图共享
    bar: str    # 子图私有

def subgraph_node_1(state: SubgraphState):
    return {"bar": "bar"}

def subgraph_node_2(state: SubgraphState):
    # 使用子图私有的 bar,更新共享的 foo
    return {"foo": state["foo"] + state["bar"]}

subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_node(subgraph_node_2)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph_builder.add_edge("subgraph_node_1", "subgraph_node_2")
subgraph = subgraph_builder.compile()

# 父图状态
class ParentState(TypedDict):
    foo: str    # 与子图共享

def node_1(state: ParentState):
    return {"foo": "hi! " + state["foo"]}

builder = StateGraph(ParentState)
builder.add_node("node_1", node_1)
builder.add_node("node_2", subgraph)  # 添加子图
builder.add_edge(START, "node_1")
builder.add_edge("node_1", "node_2")
graph = builder.compile()

# 执行
for chunk in graph.stream({"foo": "foo"}):
    print(chunk)

输出:

复制代码
{'node_1': {'foo': 'hi! foo'}}
{'node_2': {'foo': 'hi! foobar'}}

3.4 执行流程图

复制代码
输入: {"foo": "foo"}
    │
    ▼
node_1: {"foo": "hi! foo"}
    │
    ▼
子图 node_2:
    │
    ├── subgraph_node_1: {"bar": "bar"}
    │
    └── subgraph_node_2: {"foo": "hi! foobar"}
    │
    ▼
输出: {"foo": "hi! foobar"}

四、不同状态模式

4.1 什么是不同状态?

复制代码
父图状态:{foo: str}
子图状态:{bar: str, baz: str}

没有共享状态键!
需要在调用时手动转换数据

4.2 使用方法

步骤:

  1. 定义子图并编译
  2. 创建一个节点函数,在其中调用子图
  3. 在节点函数中进行数据转换
python 复制代码
from typing_extensions import TypedDict
from langgraph.graph.state import StateGraph, START

# 子图状态(与父图完全不同)
class SubgraphState(TypedDict):
    bar: str

def subgraph_node_1(state: SubgraphState):
    return {"bar": "hi! " + state["bar"]}

subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph = subgraph_builder.compile()

# 父图状态
class State(TypedDict):
    foo: str

# 调用子图的节点函数
def call_subgraph(state: State):
    # 转换:父图状态 → 子图状态
    subgraph_input = {"bar": state["foo"]}

    # 调用子图
    subgraph_output = subgraph.invoke(subgraph_input)

    # 转换:子图状态 → 父图状态
    return {"foo": subgraph_output["bar"]}

builder = StateGraph(State)
builder.add_node("node_1", call_subgraph)
builder.add_edge(START, "node_1")
graph = builder.compile()

4.3 完整示例

python 复制代码
from typing_extensions import TypedDict
from langgraph.graph.state import StateGraph, START

# 子图状态
class SubgraphState(TypedDict):
    bar: str    # 子图私有
    baz: str    # 子图私有

def subgraph_node_1(state: SubgraphState):
    return {"baz": "baz"}

def subgraph_node_2(state: SubgraphState):
    return {"bar": state["bar"] + state["baz"]}

subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_node(subgraph_node_2)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph_builder.add_edge("subgraph_node_1", "subgraph_node_2")
subgraph = subgraph_builder.compile()

# 父图状态
class ParentState(TypedDict):
    foo: str    # 父图私有

def node_1(state: ParentState):
    return {"foo": "hi! " + state["foo"]}

def node_2(state: ParentState):
    # 转换输入
    subgraph_input = {"bar": state["foo"], "baz": ""}

    # 调用子图
    response = subgraph.invoke(subgraph_input)

    # 转换输出
    return {"foo": response["bar"]}

builder = StateGraph(ParentState)
builder.add_node("node_1", node_1)
builder.add_node("node_2", node_2)
builder.add_edge(START, "node_1")
builder.add_edge("node_1", "node_2")
graph = builder.compile()

# 执行(显示子图输出)
for chunk in graph.stream({"foo": "foo"}, subgraphs=True):
    print(chunk)

输出:

复制代码
((), {'node_1': {'foo': 'hi! foo'}})
(('node_2:xxx',), {'subgraph_node_1': {'baz': 'baz'}})
(('node_2:xxx',), {'subgraph_node_2': {'bar': 'hi! foobaz'}})
((), {'node_2': {'foo': 'hi! foobaz'}})

4.4 数据转换流程

复制代码
父图状态: {"foo": "hi! foo"}
    │
    ▼ 转换输入
子图输入: {"bar": "hi! foo", "baz": ""}
    │
    ▼ 执行子图
subgraph_node_1: {"baz": "baz"}
subgraph_node_2: {"bar": "hi! foobaz"}
    │
    ▼ 转换输出
父图状态: {"foo": "hi! foobaz"}

五、多级子图

5.1 什么是多级子图?

复制代码
父图
└── 子图
    └── 孙子图

三层嵌套!

5.2 完整示例

python 复制代码
from typing_extensions import TypedDict
from langgraph.graph.state import StateGraph, START, END

# 孙子图
class GrandChildState(TypedDict):
    my_grandchild_key: str

def grandchild_1(state: GrandChildState):
    return {"my_grandchild_key": state["my_grandchild_key"] + ", how are you"}

grandchild = StateGraph(GrandChildState)
grandchild.add_node("grandchild_1", grandchild_1)
grandchild.add_edge(START, "grandchild_1")
grandchild.add_edge("grandchild_1", END)
grandchild_graph = grandchild.compile()

# 子图
class ChildState(TypedDict):
    my_child_key: str

def call_grandchild_graph(state: ChildState):
    # 转换输入
    grandchild_input = {"my_grandchild_key": state["my_child_key"]}
    # 调用孙子图
    grandchild_output = grandchild_graph.invoke(grandchild_input)
    # 转换输出
    return {"my_child_key": grandchild_output["my_grandchild_key"] + " today?"}

child = StateGraph(ChildState)
child.add_node("child_1", call_grandchild_graph)
child.add_edge(START, "child_1")
child.add_edge("child_1", END)
child_graph = child.compile()

# 父图
class ParentState(TypedDict):
    my_key: str

def parent_1(state: ParentState):
    return {"my_key": "hi " + state["my_key"]}

def parent_2(state: ParentState):
    return {"my_key": state["my_key"] + " bye!"}

def call_child_graph(state: ParentState):
    # 转换输入
    child_input = {"my_child_key": state["my_key"]}
    # 调用子图
    child_output = child_graph.invoke(child_input)
    # 转换输出
    return {"my_key": child_output["my_child_key"]}

parent = StateGraph(ParentState)
parent.add_node("parent_1", parent_1)
parent.add_node("child", call_child_graph)
parent.add_node("parent_2", parent_2)
parent.add_edge(START, "parent_1")
parent.add_edge("parent_1", "child")
parent.add_edge("child", "parent_2")
parent.add_edge("parent_2", END)
parent_graph = parent.compile()

# 执行
for chunk in parent_graph.stream({"my_key": "Bob"}, subgraphs=True):
    print(chunk)

输出:

复制代码
((), {'parent_1': {'my_key': 'hi Bob'}})
(('child:xxx', 'child_1:yyy'), {'grandchild_1': {'my_grandchild_key': 'hi Bob, how are you'}})
(('child:xxx',), {'child_1': {'my_child_key': 'hi Bob, how are you today?'}})
((), {'child': {'my_key': 'hi Bob, how are you today?'}})
((), {'parent_2': {'my_key': 'hi Bob, how are you today? bye!'}})

六、添加持久化

6.1 父图持久化

只需在编译父图时提供 checkpointer,会自动传播到子图:

python 复制代码
from langgraph.graph import START, StateGraph
from langgraph.checkpoint.memory import InMemorySaver

# 子图
subgraph_builder = StateGraph(State)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph = subgraph_builder.compile()

# 父图
builder = StateGraph(State)
builder.add_node("node_1", subgraph)
builder.add_edge(START, "node_1")

# 只需在父图添加 checkpointer
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)

6.2 子图独立持久化

如果子图需要独立的记忆(如多代理系统中每个代理跟踪自己的消息历史):

python 复制代码
# 子图独立持久化
subgraph_builder = StateGraph(...)
subgraph = subgraph_builder.compile(checkpointer=True)

七、查看子图状态

7.1 获取子图状态

python 复制代码
# 获取父图状态
parent_state = graph.get_state(config)

# 获取子图状态(需要 subgraphs=True)
state_with_subgraphs = graph.get_state(config, subgraphs=True)
subgraph_state = state_with_subgraphs.tasks[0].state

7.2 注意事项

复制代码
重要:子图状态只能在中断时查看!

一旦恢复执行,子图状态将无法访问。

7.3 完整示例

python 复制代码
from langgraph.graph import START, StateGraph
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt, Command

# 子图(包含中断)
def subgraph_node_1(state: State):
    value = interrupt("请输入值:")
    return {"foo": state["foo"] + value}

subgraph_builder = StateGraph(State)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph = subgraph_builder.compile()

# 父图
builder = StateGraph(State)
builder.add_node("node_1", subgraph)
builder.add_edge(START, "node_1")

checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "1"}}

# 执行(会中断)
graph.invoke({"foo": ""}, config)

# 查看子图状态
subgraph_state = graph.get_state(config, subgraphs=True).tasks[0].state
print(subgraph_state)

# 恢复执行
graph.invoke(Command(resume="bar"), config)

八、流式传输子图输出

8.1 启用子图流式输出

python 复制代码
for chunk in graph.stream(
    {"foo": "foo"},
    subgraphs=True,        # 启用子图输出
    stream_mode="updates",
):
    print(chunk)

8.2 输出格式

python 复制代码
((), {'node_1': {'foo': 'hi! foo'}})                           # 父图节点
(('node_2:xxx',), {'subgraph_node_1': {'bar': 'bar'}})        # 子图节点
(('node_2:xxx',), {'subgraph_node_2': {'foo': 'hi! foobar'}}) # 子图节点
((), {'node_2': {'foo': 'hi! foobar'}})                        # 父图节点

格式说明:

  • () 空元组:父图节点
  • ('node_2:xxx',) 子图路径:子图节点

8.3 完整示例

python 复制代码
from typing_extensions import TypedDict
from langgraph.graph.state import StateGraph, START

# 子图
class SubgraphState(TypedDict):
    foo: str
    bar: str

def subgraph_node_1(state: SubgraphState):
    return {"bar": "bar"}

def subgraph_node_2(state: SubgraphState):
    return {"foo": state["foo"] + state["bar"]}

subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_node(subgraph_node_2)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph_builder.add_edge("subgraph_node_1", "subgraph_node_2")
subgraph = subgraph_builder.compile()

# 父图
class ParentState(TypedDict):
    foo: str

def node_1(state: ParentState):
    return {"foo": "hi! " + state["foo"]}

builder = StateGraph(ParentState)
builder.add_node("node_1", node_1)
builder.add_node("node_2", subgraph)
builder.add_edge(START, "node_1")
builder.add_edge("node_1", "node_2")
graph = builder.compile()

# 流式输出(包含子图)
for chunk in graph.stream(
    {"foo": "foo"},
    stream_mode="updates",
    subgraphs=True,
):
    print(chunk)

九、实际应用场景

9.1 多代理系统

python 复制代码
# 研究代理(子图)
research_agent = create_react_agent(model, tools=[search])

# 写作代理(子图)
writing_agent = create_react_agent(model, tools=[write_file])

# 审核代理(子图)
review_agent = create_react_agent(model, tools=[send_email])

# 主流程(父图)
builder = StateGraph(MessagesState)
builder.add_node("research", research_agent)
builder.add_node("writing", writing_agent)
builder.add_node("review", review_agent)
builder.add_edge(START, "research")
builder.add_edge("research", "writing")
builder.add_edge("writing", "review")

9.2 模块化工作流

python 复制代码
# 数据处理模块(子图)
data_processing = StateGraph(DataState)
data_processing.add_node("clean", clean_data)
data_processing.add_node("transform", transform_data)
data_processing_graph = data_processing.compile()

# 分析模块(子图)
analysis = StateGraph(AnalysisState)
analysis.add_node("analyze", analyze_data)
analysis.add_node("visualize", visualize_data)
analysis_graph = analysis.compile()

# 主流程(父图)
builder = StateGraph(MainState)
builder.add_node("process", data_processing_graph)
builder.add_node("analyze", analysis_graph)

十、常见问题

Q1: 什么时候用共享状态,什么时候用不同状态?

复制代码
共享状态:
- 多代理共享消息历史
- 状态结构相似
- 简单场景

不同状态:
- 每个子图需要私有状态
- 状态结构完全不同
- 需要数据转换

Q2: 子图可以有多少层?

复制代码
理论上没有限制,但建议不超过3层:

推荐:父图 → 子图 → 孙子图(最多3层)
不推荐:父图 → 子图 → 孙子图 → 曾孙图 → ...

Q3: 如何调试子图?

python 复制代码
# 方法1:流式输出
for chunk in graph.stream(input, subgraphs=True):
    print(chunk)

# 方法2:查看状态
state = graph.get_state(config, subgraphs=True)

# 方法3:可视化
from IPython.display import Image
Image(subgraph.get_graph().draw_mermaid_png())

Q4: 子图的 checkpointer 如何配置?

python 复制代码
# 方式1:父图统一管理(推荐)
graph = builder.compile(checkpointer=InMemorySaver())

# 方式2:子图独立管理
subgraph = subgraph_builder.compile(checkpointer=True)

十一、API 速查表

11.1 添加子图

方式 代码 说明
共享状态 builder.add_node("name", subgraph) 直接添加
不同状态 builder.add_node("name", call_subgraph) 在函数中调用

11.2 数据转换

操作 代码
转换输入 subgraph_input = {"bar": state["foo"]}
调用子图 output = subgraph.invoke(subgraph_input)
转换输出 return {"foo": output["bar"]}

11.3 查看状态

方法 代码
父图状态 graph.get_state(config)
子图状态 graph.get_state(config, subgraphs=True)

11.4 流式输出

参数 说明
subgraphs=True 包含子图输出
stream_mode="updates" 更新模式

十二、延伸阅读


总结

子图的核心要点:

  1. 定义子图:像普通图一样定义和编译
  2. 添加到父图:共享状态直接添加,不同状态需要转换函数
  3. 数据传递:共享状态自动传递,不同状态手动转换
  4. 持久化:在父图配置即可自动传播

子图的作用:

  • 构建多代理系统
  • 代码复用
  • 模块化开发
  • 封装复杂性

一句话总结:子图就是把一个完整的图当作节点使用,像搭积木一样构建复杂系统。

相关推荐
财经资讯数据_灵砚智能1 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(日间)2026年5月16日
大数据·人工智能·python·信息可视化·自然语言处理
Gerardisite1 小时前
企业微信怎么玩?用 API 打造智能私域助手
开发语言·python·机器人·企业微信
砚底藏山河1 小时前
股票数据API接口:(沪深A股)如何获取股票当天逐笔交易数据
java·windows·python·maven
xian_wwq1 小时前
【学习笔记】探讨大模型应用安全建设系列——顶层规划:如何推动公司级大模型安全建设-1
笔记·学习·安全·ai
Ulyanov1 小时前
PySide6 + QML 混合编程全景解析:从底层原理到企业级实战
python·pyside6·qml·雷达电子对抗
AQ14_1 小时前
给AI编程上一道“紧箍咒”:Superpowers的军法式重构
重构·ai编程
极品小學生1 小时前
Claude Code 从零上手与进阶实战指南
ai编程
令狐少侠20111 小时前
创建钉钉企业内应用,钉钉AI助手操作钉钉文档
ai·钉钉
小江的记录本1 小时前
【MySQL】MySQL日志体系:redo log/undo log/binlog 三者区别、两阶段提交、如何保证数据一致性
java·数据库·后端·python·sql·mysql·面试