路由模式:智能分发 + 垂直 Agent

路由模式:智能分发 + 垂直 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",它还承担:

  1. 请求分析:解析用户输入,提取意图、实体、上下文
  2. 路由决策:根据分析结果匹配最合适的 Agent
  3. 参数转发:将原始请求转换为目标 Agent 所需的输入格式
  4. 结果汇聚:可选------将 Agent 返回结果做后处理(格式化、翻译、合并)
  5. 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 路由:

  1. 先用规则路由(关键词 + 正则),积累分类样本
  2. 样本量足够后训练一个轻量文本分类模型
  3. 仅处理分类边界模糊的剩余请求用 LLM Judge 兜底

每一步都验证"这一步真的需要吗"------路由层的核心目标是降低成本、提升准确率,不是"用上最新技术"。

相关推荐
XLYcmy2 小时前
核内调度问题的分层优化:缓存管理与性能均衡策略 模型评价 模型缺点与改进方向
缓存·ai·启发式算法·图神经网络·遗传算法·数模·模拟退火
wumingxiaoyao5 小时前
从 0 开始学 AI:第 4 课,CPU、GPU、显存和算力基础
人工智能·ai·cpu·gpu·显存
Dragon Wu5 小时前
ComfyUI ReActor需要下载检测模型的解决方案
人工智能·ai
雨辰AI5 小时前
【零基础实战】大模型入门面试 100 问:基础概念 + 环境实操(一问一答版,直接背诵)
人工智能·ai·面试·职场和发展·ai编程
Sam09275 小时前
【AI 算法精讲 16】BPE 分词:从字节对到子词
人工智能·python·算法·ai
站在墙头上6 小时前
Windows下安装 龙虾-Openclaw
windows·ai·openclaw
赵榕6 小时前
SDD vs Vibe Coding:AI 编程时代的"自由派"与"规范派",如何兼得?
ai·vibe coding·sdd
Sam09277 小时前
【AI 算法精讲 15】余弦相似度:向量检索与归一化内积
人工智能·python·算法·ai
TPBoreas7 小时前
AI 架构图绘制 + Mermaid 实战:从痛点到 Diagram as Code
人工智能·ai