LangGraph:子图动态路由与 Agent 工作流设计

1. 子图如何动态跳回父图?

前面学习子图时,我们主要关注:

text 复制代码
父图调用子图
子图执行
结果返回父图

但 LangGraph 还支持另一种更动态的方式:

子图可以直接把执行流路由到父图中的某个节点。

核心就是:

python 复制代码
Command(
    goto="node_b",
    graph=Command.PARENT
)

例如子图节点:

python 复制代码
def sub_node(state):
    return Command(
        update={
            "data": state["data"] + " → 子图"
        },
        goto="node_b",
        graph=Command.PARENT
    )

这里:

text 复制代码
update
=
更新状态

goto
=
指定接下来执行父图中的哪个节点

graph=Command.PARENT
=
告诉 LangGraph:
goto 指向的不是当前子图,而是父图

于是执行路径可以变成:

text 复制代码
父图 START
    ↓
进入子图
    ↓
sub_node
    ↓
Command.PARENT
    ↓
父图 node_b
    ↓
END

这和普通子图调用最大的区别是:

text 复制代码
普通子图
=
执行完以后按照父图原本的边继续

动态路由
=
子图自己决定回到父图哪个节点

有一个细节需要注意。

子图本身并不知道父图有哪些节点,因此返回类型不能简单写成:

python 复制代码
Command[Literal["node_b"]]

否则编译子图时可能认为 node_b 是一个不存在的子图节点。

另外,如果希望渲染出来的图能够正确显示这条动态路径,可以在添加子图节点时声明:

python 复制代码
builder.add_node(
    "sub_graph",
    sub_graph,
    destinations=("node_b",)
)

destinations 主要影响拓扑渲染,不会改变真正的动态路由行为。

2. Routing 不应该让 LLM 直接返回节点名

Routing 的基本结构我们已经学习过:

text 复制代码
输入
 ↓
路由判断
 ↓
选择不同业务流程

但是工程中更推荐把它拆成三层:

text 复制代码
LLM 节点
负责语义判断

↓

结构化结果

↓

路由函数
把判断结果映射成节点

↓

业务节点
真正执行任务

例如先定义结构化输出:

python 复制代码
class Route(BaseModel):
    step: Literal[
        "poem",
        "story",
        "joke"
    ]

然后让 LLM 只负责回答:

text 复制代码
story
joke
poem

而不是直接返回:

text 复制代码
model_call_1
model_call_2
model_call_3

之后再由普通 Python 函数映射:

python 复制代码
def route_decision(state):

    if state["decision"] == "story":
        return "model_call_1"

    if state["decision"] == "joke":
        return "model_call_2"

    if state["decision"] == "poem":
        return "model_call_3"

    return END

这样做的核心价值是:

text 复制代码
LLM
只理解业务语义

图结构
由程序控制

如果直接让模型返回节点名:

text 复制代码
LLM 输出
和
LangGraph 图结构

就会耦合得很紧。

后面一旦改节点名称,Prompt 也可能需要一起改,而且类型检查、异常兜底都会更麻烦。

所以 Routing 最好记成:

text 复制代码
语义判断和图路由分离

3. Parallelization 和 Orchestrator-worker 到底差在哪?

这两个模式看起来都像:

text 复制代码
一个任务
 ↓
拆成多个并行任务
 ↓
汇总

但最大的区别是:

text 复制代码
Parallelization
任务数量在编译图时已经确定

Orchestrator-worker
任务数量在运行时才确定

例如固定并行:

text 复制代码
START

├── 查天气
├── 查新闻
└── 查汇率

↓

汇总

无论用户问什么,都是三个固定节点。

这就是:

text 复制代码
Parallelization

而 Orchestrator-worker 可能是:

text 复制代码
用户:
帮我写一份关于 LangGraph 的研究报告

↓

Orchestrator 分析任务

↓

动态生成:
章节1
章节2
章节3
章节4
章节5

↓

动态创建 Worker

↓

并行生成

↓

Synthesizer 汇总

下次换一个问题,可能只需要两个 Worker,也可能需要八个。

所以它的本质就是我们前面学习过的:

text 复制代码
Map-Reduce

对应关系:

text 复制代码
Orchestrator
=
生成 Map 任务

Send
=
动态分发

Worker
=
执行 Map

Reducer
=
合并中间结果

Synthesizer
=
执行 Reduce

因此:

text 复制代码
任务数量固定
→ Parallelization

任务数量动态
→ Orchestrator-worker

4. Evaluator-optimizer 为什么一定要限制循环?

Evaluator-optimizer 的流程是:

text 复制代码
Generator
 ↓
Evaluator
 ↓
合格?
 ├── 是 → END
 └── 否
      ↓
   带反馈重新生成
      ↓
   Evaluator

例如:

text 复制代码
生成代码
↓
执行测试
↓
测试失败
↓
反馈错误
↓
重新生成代码

或者:

text 复制代码
生成文案
↓
合规检查
↓
不合格
↓
修改
↓
再次检查

这里的问题很明显:

如果一直不合格怎么办?

流程就可能一直循环。

因此这种模式必须有退出控制。

资料中把方法分为两类:

text 复制代码
主动退出
=
在达到某个条件前主动结束

被动退出
=
触发递归限制后异常终止

这和前面学过的循环防护正好对应。

所以 Evaluator-optimizer 的完整设计不应该只有:

text 复制代码
Generator + Evaluator

还应该包含:

text 复制代码
退出条件

可以把它理解为:

text 复制代码
生成能力
+
质量标准
+
反馈机制
+
循环边界

缺少最后一个,工作流就不完整。

5. Workflow 和 Agent 应该怎么组合?

资料最后把 Agent 和前面的设计模式做了一个很重要的区分。

前面的:

text 复制代码
Prompt Chaining
Parallelization
Routing
Orchestrator-worker
Evaluator-optimizer

本质上都属于:

text 复制代码
Workflow

也就是:

开发者提前决定图大体上怎么走。

而 Agent 不一样:

text 复制代码
LLM 根据当前 messages
以及工具执行结果
动态决定下一步

典型结构:

text 复制代码
用户输入

↓

LLM

↓

是否调用工具?

├── 否 → END
│
└── 是
     ↓
   ToolNode
     ↓
   ToolMessage
     ↓
     LLM

这就是 ReAct 循环。

但这并不意味着:

text 复制代码
Agent 比 Workflow 高级
所以所有流程都应该交给 Agent

这些模式解决的是不同问题。

例如可以组合成:

text 复制代码
用户请求
   ↓
Routing
   ↓
判断任务类型
   ↓
┌───────────────┐
│               │
固定业务流程     开放式复杂任务
│               │
Workflow         Agent
│               │
└───────┬───────┘
        ↓
     ToolNode
        ↓
Evaluator
        ↓
      END

如果任务流程本身非常明确:

text 复制代码
A
↓
B
↓
C

就没必要让 LLM 每一步重新思考。

而当:

text 复制代码
需要哪些工具未知
调用顺序未知
任务步骤无法提前确定

Agent 才更合适。

所以可以记成:

text 复制代码
确定性强的部分
→ Workflow

需要自主决策的部分
→ Agent
相关推荐
晴天161 小时前
Gradio 与 Streamlit:Python 快速构建 Web 应用的双子星-Day28
开发语言·前端·python
苏灿烤鱼1 小时前
把求职写成工作流,公开 fork 为什么会把简历写进仓库?
python·typescript·claude
whcyhhh1 小时前
头歌实践教学平台:数据科学与大数据技术导论(十)
大数据·开发语言·python
华研前沿标杆游学5 小时前
华为松山湖高阶研学|企业数字化转型游学攻略
python
Data_Journal9 小时前
用于网页抓取的 Node-unblocker
大数据·开发语言·数据库·python·scrapy
豆角焖肉9 小时前
SSM 聚合项目搭建:多 Maven 模块项目
开发语言·python·pycharm
青 春 记 忆10 小时前
零基础入门python20:Flask配置、扩展和蓝图拆分
python·flask·后端开发
m0_3807438710 小时前
为 OpenAI 兼容接口配置教程
开发语言·python·node.js
leisoo809712 小时前
ig50数据落盘ClickHousevsTimescaleDBvsDuckDB实测对比 IG50免费开源股票数据API接口
开发语言·jvm·数据库·python·json