路由模式:智能分发 + 垂直 Agent
路由模式的核心是智能分发------根据任务类型、内容或上下文将请求路由到不同的垂直 Agent。读完你能设计出适配多种场景的路由层,理解分类器精度与兜底策略的关键 trade-off。
一、概念速查
1.1 什么是路由模式
路由模式(Router Pattern)用一个中央路由层替代"一个 Agent 处理所有请求"。路由层分析入站任务,分发给最适合的垂直 Agent,类似微服务架构中的 API Gateway + 后端服务。
#mermaid-svg-EWnYbLIoEeyQ4s1C{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EWnYbLIoEeyQ4s1C .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EWnYbLIoEeyQ4s1C .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EWnYbLIoEeyQ4s1C .error-icon{fill:#552222;}#mermaid-svg-EWnYbLIoEeyQ4s1C .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EWnYbLIoEeyQ4s1C .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EWnYbLIoEeyQ4s1C .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EWnYbLIoEeyQ4s1C .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EWnYbLIoEeyQ4s1C .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EWnYbLIoEeyQ4s1C .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EWnYbLIoEeyQ4s1C .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EWnYbLIoEeyQ4s1C .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EWnYbLIoEeyQ4s1C .marker.cross{stroke:#333333;}#mermaid-svg-EWnYbLIoEeyQ4s1C svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EWnYbLIoEeyQ4s1C p{margin:0;}#mermaid-svg-EWnYbLIoEeyQ4s1C .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EWnYbLIoEeyQ4s1C .cluster-label text{fill:#333;}#mermaid-svg-EWnYbLIoEeyQ4s1C .cluster-label span{color:#333;}#mermaid-svg-EWnYbLIoEeyQ4s1C .cluster-label span p{background-color:transparent;}#mermaid-svg-EWnYbLIoEeyQ4s1C .label text,#mermaid-svg-EWnYbLIoEeyQ4s1C span{fill:#333;color:#333;}#mermaid-svg-EWnYbLIoEeyQ4s1C .node rect,#mermaid-svg-EWnYbLIoEeyQ4s1C .node circle,#mermaid-svg-EWnYbLIoEeyQ4s1C .node ellipse,#mermaid-svg-EWnYbLIoEeyQ4s1C .node polygon,#mermaid-svg-EWnYbLIoEeyQ4s1C .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EWnYbLIoEeyQ4s1C .rough-node .label text,#mermaid-svg-EWnYbLIoEeyQ4s1C .node .label text,#mermaid-svg-EWnYbLIoEeyQ4s1C .image-shape .label,#mermaid-svg-EWnYbLIoEeyQ4s1C .icon-shape .label{text-anchor:middle;}#mermaid-svg-EWnYbLIoEeyQ4s1C .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EWnYbLIoEeyQ4s1C .rough-node .label,#mermaid-svg-EWnYbLIoEeyQ4s1C .node .label,#mermaid-svg-EWnYbLIoEeyQ4s1C .image-shape .label,#mermaid-svg-EWnYbLIoEeyQ4s1C .icon-shape .label{text-align:center;}#mermaid-svg-EWnYbLIoEeyQ4s1C .node.clickable{cursor:pointer;}#mermaid-svg-EWnYbLIoEeyQ4s1C .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EWnYbLIoEeyQ4s1C .arrowheadPath{fill:#333333;}#mermaid-svg-EWnYbLIoEeyQ4s1C .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EWnYbLIoEeyQ4s1C .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EWnYbLIoEeyQ4s1C .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EWnYbLIoEeyQ4s1C .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EWnYbLIoEeyQ4s1C .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EWnYbLIoEeyQ4s1C .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EWnYbLIoEeyQ4s1C .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EWnYbLIoEeyQ4s1C .cluster text{fill:#333;}#mermaid-svg-EWnYbLIoEeyQ4s1C .cluster span{color:#333;}#mermaid-svg-EWnYbLIoEeyQ4s1C div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EWnYbLIoEeyQ4s1C .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EWnYbLIoEeyQ4s1C rect.text{fill:none;stroke-width:0;}#mermaid-svg-EWnYbLIoEeyQ4s1C .icon-shape,#mermaid-svg-EWnYbLIoEeyQ4s1C .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EWnYbLIoEeyQ4s1C .icon-shape p,#mermaid-svg-EWnYbLIoEeyQ4s1C .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EWnYbLIoEeyQ4s1C .icon-shape .label rect,#mermaid-svg-EWnYbLIoEeyQ4s1C .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EWnYbLIoEeyQ4s1C .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EWnYbLIoEeyQ4s1C .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EWnYbLIoEeyQ4s1C :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客服类
代码类
数据分析类
其他
用户请求
路由层
分类器 / LLM Judge
客服 Agent
代码 Agent
分析 Agent
通用 Agent
兜底
1.2 核心术语速查
| 术语 | 定义 |
|---|---|
| 路由层(Router) | 中央分发节点,接收请求并决定目标 Agent |
| 垂直 Agent | 专精某一领域的 Agent(客服、代码、分析) |
| 分类器 | 路由层的决策引擎,可用轻量模型、规则或 LLM |
| 兜底 Agent | 当路由无法匹配时接管的通用处理 Agent |
| 路由表 | 任务类型 → 目标 Agent 的映射规则 |
| Fallback 策略 | 路由失败或 Agent 超时时的降级处理逻辑 |
1.3 三种路由实现方式
| 方式 | 决策引擎 | 延迟 | 成本 | 适用场景 |
|---|---|---|---|---|
| 基于规则 | 关键词 / Regex / 意图匹配 | 极低 | 接近 0 | 分类边界清晰的场景(工单类型、指令前缀) |
| 基于轻量模型 | BERT 分类器 / Embedding + KNN | 低 | 低 | 分类明确的场景,类别固定 |
| 基于 LLM Judge | LLM Judge 分析后决策 | 中 | 高 | 分类模糊、需要语义理解的复杂场景 |
二、底层原理
2.1 路由层的核心职责
路由层不只是"选一个 Agent",它还承担:
- 请求分析:解析用户输入,提取意图、实体、上下文
- 路由决策:根据分析结果匹配最合适的 Agent
- 参数转发:将原始请求转换为目标 Agent 所需的输入格式
- 结果汇聚:可选------将 Agent 返回结果做后处理(格式化、翻译、合并)
- Fallback:路由失败或 Agent 异常时降级
2.2 路由决策的三种方法
方法一:基于规则的路由器
最轻量,适合分类边界清晰的场景。
python
# Python 3.11+ | from src.router import RuleRouter
from __future__ import annotations
import re
from dataclasses import dataclass, field
from typing import Callable
@dataclass
class RuleRouter:
"""基于规则的路由器------关键词 + 正则匹配"""
routes: dict[str, str] = field(default_factory=dict)
fallback: str = "general"
def add_rule(self, pattern: str, target: str) -> None:
self.routes[pattern] = target
def route(self, text: str) -> str:
for pattern, target in self.routes.items():
if re.search(pattern, text, re.IGNORECASE):
return target
return self.fallback
router = RuleRouter(fallback="general_agent")
router.add_rule(r"客服|退货|退款|投诉", "customer_service")
router.add_rule(r"代码|bug|debug|部署", "code_agent")
router.add_rule(r"报表|图表|统计|分析", "analytics_agent")
print(router.route("我想退货")) # customer_service
print(router.route("帮我查 bug")) # code_agent
print(router.route("今天天气")) # general_agent
方法二:基于 Embedding 的路由器
将请求文本和 Agent 描述向量化,用语义相似度匹配。
python
# Python 3.11+ | from src.router import EmbeddingRouter
from __future__ import annotations
from typing import Sequence
import numpy as np
from sentence_transformers import SentenceTransformer # pip install sentence-transformers
class EmbeddingRouter:
"""基于语义相似度的路由器------Embedding + Cosine 相似度"""
def __init__(self, model_name: str = "all-MiniLM-L6-v2") -> None:
self.model = SentenceTransformer(model_name)
self.agents: dict[str, str] = {} # agent_name -> description
self.agent_embeddings: dict[str, np.ndarray] = {}
def register_agent(self, name: str, description: str) -> None:
self.agents[name] = description
self.agent_embeddings[name] = self.model.encode(description)
def route(self, text: str, top_k: int = 1) -> Sequence[str]:
text_emb = self.model.encode(text)
scores = {
name: float(np.dot(text_emb, emb) / (np.linalg.norm(text_emb) * np.linalg.norm(emb)))
for name, emb in self.agent_embeddings.items()
}
sorted_agents = sorted(scores, key=scores.get, reverse=True) # type: ignore[arg-type]
return sorted_agents[:top_k]
router = EmbeddingRouter()
router.register_agent("customer_service", "处理退货退款投诉等客户服务问题")
router.register_agent("code_agent", "编写代码、调试、部署和技术支持")
router.register_agent("analytics_agent", "数据分析、报表生成和可视化")
print(router.route("我的订单怎么还没到")) # ['customer_service', ...]
方法三:基于 LLM Judge 的路由器
用 LLM 做语义理解,适合边界模糊的任务。
python
# Python 3.11+ | from src.router import LLMRouter
from __future__ import annotations
import json
from typing import Any
from openai import OpenAI # pip install openai
class LLMRouter:
"""基于 LLM Judge 的路由器------LLM 分析后决策"""
def __init__(self, client: OpenAI, agents: list[str]) -> None:
self.client = client
self.agents = agents
self._build_system_prompt()
def _build_system_prompt(self) -> None:
agent_list = "\n".join(f"- {a}" for a in self.agents)
self.system_prompt = f"""分析用户请求,选择最合适的 Agent 处理。
可选 Agent:
{agent_list}
仅返回 JSON,格式:
{{"agent": "agent_name", "reason": "简短理由"}}"""
def route(self, text: str) -> dict[str, Any]:
resp = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": text},
],
response_format={"type": "json_object"},
temperature=0,
)
return json.loads(resp.choices[0].message.content or "{}")
client = OpenAI()
router = LLMRouter(client, ["customer_service", "code_agent", "analytics_agent"])
print(router.route("帮我画一下 Q2 的销售额趋势"))
# {"agent": "analytics_agent", "reason": "用户需要数据分析与可视化"}
2.3 路由结束后做什么
路由决策只是第一步。路由层还需处理三种后续情形:
| 情形 | 处理方式 | 说明 |
|---|---|---|
| Agent 返回成功 | 原路返回给用户 | 可选加后处理(格式统一、翻译、脱敏) |
| Agent 返回错误 | 重试 → 切换 → 报错 | 同 Agent 重试 1-2 次,仍然失败则切到兜底 Agent |
| Agent 超时 | 降级到兜底或报错 | 设严格超时(如 30s),超时后走 Fallback |
2.4 路由模式 vs 单一 Agent
| 维度 | 单一 Agent | 路由模式 |
|---|---|---|
| 系统 Prompt | 巨大(所有能力塞进一个 Prompt) | 极简(仅路由分类 Prompt) |
| 上下文污染 | 高------文件搜索指令可能干扰客服推理 | 无------各 Agent 上下文隔离 |
| 工具集 | 全部挂载,LLM 选择易出错 | 仅挂载本领域工具,选择准确率高 |
| 扩展性 | 加新能力 = 重写 Prompt | 加新 Agent 注册即可 |
| 容错性 | 一个环节失败全流程挂 | 路由层独立,单个 Agent 故障不影响其他 |
三、架构设计原则
3.1 兜底优先原则
路由不可用时,必须有兜底。 任何分类器都有精度上限。当置信度低于阈值时,不强行分类------回退到通用 Agent 或请求用户澄清。
#mermaid-svg-XG1K14CfxiaCGFVb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XG1K14CfxiaCGFVb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XG1K14CfxiaCGFVb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XG1K14CfxiaCGFVb .error-icon{fill:#552222;}#mermaid-svg-XG1K14CfxiaCGFVb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XG1K14CfxiaCGFVb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XG1K14CfxiaCGFVb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XG1K14CfxiaCGFVb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XG1K14CfxiaCGFVb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XG1K14CfxiaCGFVb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XG1K14CfxiaCGFVb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XG1K14CfxiaCGFVb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XG1K14CfxiaCGFVb .marker.cross{stroke:#333333;}#mermaid-svg-XG1K14CfxiaCGFVb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XG1K14CfxiaCGFVb p{margin:0;}#mermaid-svg-XG1K14CfxiaCGFVb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XG1K14CfxiaCGFVb .cluster-label text{fill:#333;}#mermaid-svg-XG1K14CfxiaCGFVb .cluster-label span{color:#333;}#mermaid-svg-XG1K14CfxiaCGFVb .cluster-label span p{background-color:transparent;}#mermaid-svg-XG1K14CfxiaCGFVb .label text,#mermaid-svg-XG1K14CfxiaCGFVb span{fill:#333;color:#333;}#mermaid-svg-XG1K14CfxiaCGFVb .node rect,#mermaid-svg-XG1K14CfxiaCGFVb .node circle,#mermaid-svg-XG1K14CfxiaCGFVb .node ellipse,#mermaid-svg-XG1K14CfxiaCGFVb .node polygon,#mermaid-svg-XG1K14CfxiaCGFVb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XG1K14CfxiaCGFVb .rough-node .label text,#mermaid-svg-XG1K14CfxiaCGFVb .node .label text,#mermaid-svg-XG1K14CfxiaCGFVb .image-shape .label,#mermaid-svg-XG1K14CfxiaCGFVb .icon-shape .label{text-anchor:middle;}#mermaid-svg-XG1K14CfxiaCGFVb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XG1K14CfxiaCGFVb .rough-node .label,#mermaid-svg-XG1K14CfxiaCGFVb .node .label,#mermaid-svg-XG1K14CfxiaCGFVb .image-shape .label,#mermaid-svg-XG1K14CfxiaCGFVb .icon-shape .label{text-align:center;}#mermaid-svg-XG1K14CfxiaCGFVb .node.clickable{cursor:pointer;}#mermaid-svg-XG1K14CfxiaCGFVb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XG1K14CfxiaCGFVb .arrowheadPath{fill:#333333;}#mermaid-svg-XG1K14CfxiaCGFVb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XG1K14CfxiaCGFVb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XG1K14CfxiaCGFVb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XG1K14CfxiaCGFVb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XG1K14CfxiaCGFVb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XG1K14CfxiaCGFVb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XG1K14CfxiaCGFVb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XG1K14CfxiaCGFVb .cluster text{fill:#333;}#mermaid-svg-XG1K14CfxiaCGFVb .cluster span{color:#333;}#mermaid-svg-XG1K14CfxiaCGFVb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XG1K14CfxiaCGFVb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XG1K14CfxiaCGFVb rect.text{fill:none;stroke-width:0;}#mermaid-svg-XG1K14CfxiaCGFVb .icon-shape,#mermaid-svg-XG1K14CfxiaCGFVb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XG1K14CfxiaCGFVb .icon-shape p,#mermaid-svg-XG1K14CfxiaCGFVb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XG1K14CfxiaCGFVb .icon-shape .label rect,#mermaid-svg-XG1K14CfxiaCGFVb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XG1K14CfxiaCGFVb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XG1K14CfxiaCGFVb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XG1K14CfxiaCGFVb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 置信度高
置信度低
用户请求
LLM Judge
置信度评分
路由到垂直 Agent
兜底 Agent
或请求用户澄清
返回结果
3.2 路由层无状态原则
路由层不记忆会话历史,每次请求独立决策。理由:
- 单一职责:路由只做分发,不做推理
- 可扩展:无状态意味着可以水平扩展路由节点
- 可隔离:会话状态属于垂直 Agent 的职责范围
3.3 分类器精度 ≠ 路由质量原则
100% 精度的分类器不代表好的路由体验。要考虑:
- 误召回的成本不对称:把客服问题路由给代码 Agent(代码 Agent 执行不了)比路由给兜底 Agent(兜底能处理大部分)严重得多
- 路由延迟:多 100ms 分类延迟 + 垂直 Agent 的推理延迟,用户感知明显
- Agent 负载:高并发下路由应做简单限流,避免将请求发给已过载的 Agent
3.4 可观测性原则
路由决策必须可追溯:
- 记录每次路由的输入、决策结果、置信度、延迟
- 记录 Fallback 触发的原因和次数
- 用路由日志持续优化分类规则(误分类案例分析)
3.5 渐进式复杂度原则
不要一开始就上 LLM Judge 路由:
- 先用规则路由(关键词 + 正则),积累分类样本
- 样本量足够后训练一个轻量文本分类模型
- 仅处理分类边界模糊的剩余请求用 LLM Judge 兜底
每一步都验证"这一步真的需要吗"------路由层的核心目标是降低成本、提升准确率,不是"用上最新技术"。