跳到正文

TECH NOTE

2473 字 12 分钟 系列 7 / 22

07 Router 路由:先分诊,再并行开查,最后汇总

ON THIS PAGE / 目录13 节

一句话讲透:进来先”分诊”一次——判断这个问题属于哪个(或哪几个)领域,然后把请求并行扔给对应的专家 Agent,最后把几份结果合并成一个完整回答。它不聊天、不记仇(无状态),就是一个”预处理步骤”。


它到底是什么#

原文定义:

在 router 架构里,一个路由步骤先给输入分类,再把它定向到专门的 Agent。当你有若干个互不相同的 verticals(垂直领域)——各自需要独立 Agent 的独立知识域——时,这个模式很有用。

graph LR
A([查询]) --> B[路由 Router]
B --> C[Agent A]
B --> D[Agent B]
B --> E[Agent C]
C --> F[汇总 Synthesize]
D --> F
E --> F
F --> G([合并后的回答])

这张图在说什么:① 入口只有一次分类(Router 节点);② 分类结果可以指向一个或多个 Agent(图中同时指向 A、B、C,即并行);③ 所有专家的结果都汇到 Synthesize(汇总)节点;④ 用户拿到的是一份合并后的回答,而不是三份碎片。


三个关键特征#

特征原文说人话
路由器分解查询Router decomposes the query把”一个大问题”拆成”分别该谁回答”
调用零个或多个专家 Agent,且并行Zero or more specialized agents are invoked in parallel”零个”很关键——有时候判定”不用查”,直接答
结果被汇总成连贯回答Results are synthesized into a coherent response必须有一步”合并”,否则用户收到三份互不相干的答案

什么时候用它#

原文三条:

  • 你有互不相同的垂直领域(各自需要独立 Agent 的知识域);
  • 需要并行查询多个来源;
  • 想把这些结果汇总成一个合并回答。

🧭 补充解读(非原文):Router 最舒服的场景是**“知识源是分仓的”——产品文档在 GitHub、需求在 Notion、讨论在 Slack(这正是官方教程的例子)。这类场景里”该查哪几个仓”是个分类问题**,而不是”多轮对话”问题。


最小可用实现:两种路由方式#

原文给了两种机制:

  • 用 Command → 路由到单个专家 Agent;
  • 用 Send → 并行扇出到多个 Agent。

方式一:单个 Agent(用 Command)#

from langgraph.types import Command
def classify_query(query: str) -> str:
"""用 LLM 给 query 分类,决定该交给哪个 Agent。"""
# 分类逻辑
...
def route_query(state: State) -> Command:
"""根据分类结果路由到合适的 Agent。"""
active_agent = classify_query(state["query"])
# 路由到被选中的那个 Agent
return Command(goto=active_agent)

这段代码在说什么:① classify_query 是”分诊”;② Command(goto=...) 是 LangGraph 里的”直接跳转到某个节点”;③ 一次只去一个地方——这是”或”的关系,不是”且”。

方式二:多个 Agent 并行(用 Send)#

from typing import TypedDict
from langgraph.types import Send
class ClassificationResult(TypedDict):
query: str
agent: str
def classify_query(query: str) -> list[ClassificationResult]:
"""用 LLM 分类,决定要调起哪些 Agent(可能多个)。"""
# 分类逻辑
...
def route_query(state: State):
"""根据分类结果把请求扇出给相关 Agent。"""
classifications = classify_query(state["query"])
# 并行扇出到被选中的各个 Agent
return [
Send(c["agent"], {"query": c["query"]})
for c in classifications
]

这段代码在说什么:① 分类结果是一个列表(可能命中多个领域);② 每个命中项生成一个 Send(...),Send 的作用是”往某个节点发一份独立的输入”,LangGraph 会并行执行这些分支;③ 返回列表 = 扇出(fan-out),这是 Router 相对 Handoffs 的最大优势。

🧭 补充解读(非原文):Send 还解决了一个工程难题——分支数量在编译期不确定。你可以理解为”图上的边是运行时才生成的”。并行分支的结果怎么合回一个列表?靠 reducer(归并函数),具体做法在 12 篇的实战里有完整演示。

📎 官方实战教程:Build a multi-source knowledge base with routing——并行查 GitHub、Notion、Slack 三个来源,再汇总成连贯回答;涵盖状态定义、专用 Agent、Send 并行执行与结果汇总(本笔记的精讲版见 12)。


有状态 vs 无状态#

原文把 Router 分成两种用法:

无状态:每来一个请求独立处理#

原文:每个请求都被独立路由——调用之间没有记忆。多轮会话请走有状态路由。

Router vs Subagents(原文的 Tip,务必读)#

两种模式都能把工作派发给多个 Agent,但路由决策的产生方式不同:

  • Router:一个专门的路由步骤(通常是一次 LLM 调用或基于规则的逻辑)给输入分类并分发。路由器本身通常不维护对话历史、也不做多轮编排——它是一个预处理步骤。
  • Subagents:一个主管 Agent 在持续对话中动态决定要调哪些子智能体。主管维护上下文,可以跨回合调用多个子智能体,编排复杂多步工作流。

什么时候用 Router:你有清晰的输入类别,并且想要确定性的或轻量的分类。 什么时候用 Supervisor:你需要灵活的、感知对话的编排——由 LLM 根据不断演进的上下文决定下一步做什么。

🧭 补充解读(非原文):一句话分辨——Router 是”进门分诊”,Subagents 是”全程陪聊的管家”。判断标准是”这件事需要跨轮推进吗”:需要 → Subagents;一次问清 → Router。

有状态:要跨轮记住上下文#

原文:多轮对话场景下,你需要在多次调用之间维护上下文。

做法一:工具包装(Tool wrapper)#

原文说这是最简单的做法:把无状态路由器包成一个工具,让一个”会聊天”的 Agent 去调它。对话 Agent 负责记忆与上下文,路由器保持无状态——这样就避开了”在多个并行 Agent 之间管理对话历史”的复杂度。

@tool
def search_docs(query: str) -> str:
"""Search across multiple documentation sources."""
result = workflow.invoke({"query": query})
return result["final_answer"]
# 会聊天的 Agent 把路由器当成一个工具来用
conversational_agent = create_agent(
model,
tools=[search_docs],
prompt="You are a helpful assistant. Use search_docs to answer questions.",
)

这段代码在说什么:① 路由器还是那个无状态的路由器(workflow.invoke);② 记忆全在外层 conversational_agent 上;③ 这其实是 04 Subagents 的”一个子智能体”特例——用一个会记事的 Agent 给无状态路由器补上记忆。

做法二:完整持久化(Full persistence)#

如果你需要路由器自己维护状态,就用持久化来保存消息历史。路由到某个 Agent 时,从状态里取出之前的消息,有选择地放进该 Agent 的上下文——这正是上下文工程的一个杠杆。

⚠️ 警告(原文):有状态路由器需要自定义历史管理。 如果路由器在多个回合之间在不同 Agent 之间切换,当这些 Agent 的语气或提示词不同时,用户会觉得对话不连贯。并行调用的情况下,你需要在路由器层维护历史(输入 + 汇总后的输出),并在路由逻辑里用上这份历史。不妨考虑改用 handoffs 模式 或 subagents 模式——它们在多轮对话上有更清晰的语义。

🧭 补充解读(非原文):这条警告的潜台词是——别拿 Router 硬撑多轮对话。Router 的强项是”一次问清、多源并行、合并输出”;一旦你要”记住用户上轮说了什么、这轮该换个专家接着聊”,语义就滑向 Handoffs/Subagents 了。


一张图看清三种”派活”方式的区别#

【Router】一次分诊,并行执行,一次汇总 —— 无记忆
用户问题 ─▶ 分类(1 次模型调用)
├─▶ 知识源 A ─┐
├─▶ 知识源 B ─┼─▶ 汇总 ─▶ 合并回答
└─▶ 知识源 C ─┘
【Subagents】主管全程在,逐个/并行派活,结果都回主管 —— 主管有记忆
用户 ◀──▶ 主管 ──▶ 子智能体 A(隔离上下文,无记忆)
├──▶ 子智能体 B
└──▶ 子智能体 C ← 每次调用都从零开始
【Handoffs】一个 Agent 换着身份陪用户走完全程 —— 串行、有状态
用户 ◀──▶ [步骤1 的 Agent] ──状态──▶ [步骤2 的 Agent] ──状态──▶ [步骤3 的 Agent]

小结#

  1. 一句话:Router = 先分类 → 并行分发(0..N 个专家)→ 汇总,典型场景是”分仓知识源”。
  2. 两种路由机制:Command(goto=...) 路由到单个;Send(...) 列表实现动态并行扇出。
  3. 三个特征:分解查询、并行调用零或多个专家、结果汇总成连贯回答。
  4. 无状态是默认形态:要做多轮,要么包成工具给会聊天的 Agent 用(简单),要么做完整持久化(复杂,且官方建议改用 Handoffs/Subagents)。
  5. Router vs Subagents:Router 是预处理式的一次分类(类别清晰、要确定性/轻量);Subagents 是跨回合的动态编排(上下文演进、要灵活)。
  6. 代价:多一次路由调用(03 篇里重复请求每轮都多这一次);换来的是并行与明确的分类边界。

一句话选型#

  • 选它:输入类别清晰、有多个分仓知识源、要并行查、要合并成一个回答;或者你只想要一次”确定性的轻量分类”。
  • 别选它:需要多轮对话记忆(用 05 Handoffs 或 04 Subagents);任务需要”A 完了接着 B”的多跳串联(Router 不支持多跳);只是想让单 Agent 按需加载知识(用 06 Skills)。

上一篇:06 Skills 技能 | 下一篇:08 Custom workflow 自定义工作流 | 实战:12 用 Router 搭多源知识库

07 Router 路由:先分诊,再并行开查,最后汇总
https://aicooleo.cn/blog/posts/multiagent-07-router/
作者
aicooleo
发布于
2026-08-28
许可协议
CC BY-NC-SA 4.0
多 Agent 精讲 · 第 7 / 22 篇 返回合集