第 11 章 路由 Routing
本章要解决的问题
一个入口要处理多种类型的请求------让模型决定走哪条处理链路,判断错了怎么办?
章节大纲
- 11.1 按输入特点分发到不同处理分支
- 11.2 路由决策:规则 vs 模型分类
- 11.3 多路由 Agent 的实现
- 11.4 主流框架对照与变体
- 🛠 解决方案:路由误判的兜底与回退设计
11.1 模式原理:按输入特点分发到不同处理分支
11.1.1 一句话定义
路由(Routing)是在系统入口处,根据输入的特点(意图、语言、复杂度、敏感度、用户身份......)把请求分发到不同的处理分支,让每个分支用最适合自己的提示词、模型甚至整条流水线来处理。
如果说提示链解决的是"一个任务怎么拆",路由解决的是"多个任务怎么分流"。它们是第 IV 部分里最常被一起使用的两个模式------第 10 章 10.5.1 的条件链,本质就是路由 + 提示链的组合。
11.1.2 为什么需要路由:一个万能提示词撑不住的场景
看一个典型场景:一个客服 Agent 入口,要同时处理:
text
1. "我买的手机壳什么时候发货?" → 查物流(工具调用 + 短链)
2. "你们家的政策太烂了,我要投诉!" → 投诉工单(情绪处理链)
3. "你好" → 闲聊寒暄(简单回复,别烧钱)
4. "帮我算一下退货运费是多少?" → 计算(工具调用)
5. "退款到账了吗?" → 查订单状态(工具 + 长上下文)
如果用一个万能提示词硬接这五类请求,会出两个问题:
- 每类请求的最优提示词不同:查物流需要"订单号抽取 + 工具调用",投诉需要"情绪安抚 + 升级策略"。混在一个提示词里,提示词会膨胀到无法维护,且互相干扰。
- 成本浪费 :闲聊请求不需要挂载 5 个工具定义 + 长系统提示词,但万能提示词对每个请求都背着全部负担------token 成本被最低价值请求拉高。
路由的价值正是:在入口花很小的代价(一次分类),让后续每个分支都用最小、最合适的上下文处理,质量、成本、延迟三者同时优化。
11.1.3 路由的分层:从粗到细
| 路由层次 | 判断依据 | 例子 |
|---|---|---|
| 入口路由 | 意图/任务类型 | 查物流 vs 投诉 vs 闲聊 |
| 语言路由 | 语言 | 中文走中文链、英文走英文链 |
| 模型路由 | 复杂度/预算 | 简单→小模型(便宜快)、复杂→大模型 |
| 敏感路由 | 数据敏感度 | 含 PII → 脱敏链 + 私有模型 |
| 用户路由 | 身份/等级 | VIP → 优先队列 + 专属策略 |
| 异常路由 | 失败兜底 | 主链路失败 → 降级分支 |

图 1:路由分层
本书聚焦前三层(意图/语言/模型),敏感路由详见第 22 章安全,异常路由见本章 11.4 兜底设计。
11.2 路由决策:规则 vs 模型分类
路由的核心是决策器:怎么判断"这个请求该走哪条路"?两种主流做法,各有适用边界。

图 2:规则 vs 模型路由
11.2.1 规则路由(Rule-based Routing)
用关键词、正则、枚举匹配直接决定去向,零 LLM 调用、零延迟、零成本:
python
def rule_router(text: str) -> str:
t = text.lower()
if any(k in t for k in ["发货", "物流", "快递", "到哪了"]):
return "logistics"
if any(k in t for k in ["投诉", "举报", "太差", "再也不"]):
return "complaint"
if any(k in t for k in ["你好", "hi", "hello", "在吗"]):
return "chitchat"
return "fallback"
优点 :快、准、可解释、免费。 缺点:只能覆盖"说得出口"的模式------用户换个说法("我的包裹飞哪去了")就漏了;表达天然多样,规则很难覆盖所有情况。
适用:意图集合固定且用词收敛的场景(比如"查物流/查订单"这种动词明确的服务入口)。
11.2.2 模型路由(Model-based Routing)
让 LLM 做一次"分类 + 抽取",一次调用完成意图判断,甚至直接输出要走的路径:
python
ROUTE_PROMPT = """你是请求分类器。将用户请求分到以下类别之一,只输出 JSON:
{"route": "logistics|complaint|chitchat|order|billing", "reason": "一句话理由"}
用户请求:{query}"""
def model_router(query: str) -> str:
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user",
"content": ROUTE_PROMPT.format(query=query)}],
temperature=0.1, # 分类任务用低温度(第 24 章表二)
response_format={"type": "json_object"},
)
try:
return json.loads(resp.choices[0].message.content)["route"]
except (json.JSONDecodeError, KeyError):
return "chitchat" # 解析失败时降级到安全路由
优点 :理解自然语言、泛化强、能处理未见过的表达。 缺点:多一次调用(延迟 + 成本)、可能误判(见 11.4 兜底)。
适用:意图多样、表达自由、规则覆盖不住的中长尾场景。
11.2.3 混合路由:规则兜底 + 模型兜底,两层漏斗
工程上最稳的做法是两层漏斗:

图 3:两层漏斗路由
css
输入 → [第一层:规则快速命中] ─命中→ 走分支
│
└─未命中→ [第二层:模型分类] ─→ 走分支
│
└─不确定→ 走 fallback 分支
规则能覆盖的高频表达(80% 流量)零成本秒判;模型只处理规则的漏网之鱼。规则层拦掉大部分流量,模型层只吃长尾,成本与泛化兼得。
顺带一提:这个"规则优先、模型兜底"的思路在第 22 章安全(关键词拦截 + 模型审核双层)和第 17 章评估自检(规则校验 + 模型自检)中会反复出现,是全书高频复用的工程心法。
11.3 完整示例:多分支客服路由
我们实现一个真实的多分支路由系统:入口路由 + 模型路由 + 兜底,接到第 10 章的提示链分支。

图 4:客服多分支路由
python
import json
from openai import OpenAI
client = OpenAI(base_url="https://api.deepseek.com", api_key="<你的Key>")
BRANCHES = {
# 每个分支 = (名称, 处理函数)。简单分支直接返回文案,复杂分支复用提示链
"logistics": lambda q: run_logistics_chain(q), # 第10章风格链
"complaint": lambda q: run_complaint_chain(q), # 情绪处理链
"order": lambda q: run_order_chain(q),
"billing": lambda q: "退款相关问题请提供订单号,我们将在 1 个工作日内处理。",
"chitchat": lambda q: "在的~有什么可以帮您?也可以直接告诉我订单号查询进度哦。",
}
FALLBACK = "这个问题我暂时无法判断,已转人工客服,请稍候。"
# 第一层:规则快速命中(覆盖高频)
def rule_router(q: str) -> str | None:
t = q.lower()
if any(k in t for k in ["发货", "物流", "快递", "到哪", "配送"]):
return "logistics"
if any(k in t for k in ["投诉", "举报", "差评", "再也不"]):
return "complaint"
if any(k in t for k in ["退款", "运费", "费用", "钱"]):
return "billing"
return None
# 第二层:模型分类(覆盖长尾)
def model_router(q: str) -> dict:
resp = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": f"""你是客服请求分类器。
类别:logistics(查物流/配送) | complaint(投诉/不满) | order(查订单/下单)
| billing(退款/费用) | chitchat(闲聊)
只输出 JSON:{{"route": "类别名", "confidence": 0到1}}
请求:{q}"""}],
temperature=0.1,
response_format={"type": "json_object"},
)
try:
return json.loads(resp.choices[0].message.content)
except json.JSONDecodeError:
return {"route": "chitchat", "confidence": 0.0} # 解析失败时降级
def route_and_handle(q: str) -> str:
# 第一层
r = rule_router(q)
# 第二层(带置信度,低置信度不硬猜)
if r is None:
m = model_router(q)
if m["confidence"] >= 0.6:
r = m["route"]
# 兜底
handler = BRANCHES.get(r, lambda _: FALLBACK)
return handler(q)
# 测试
for q in ["我的手机壳什么时候发货?", "你们太坑了我要投诉!", "在吗",
"那个退款还没到账", "今天天气怎么样"]:
print(f"[{q}] → {route_and_handle(q)[:40]}")
这个示例有几个工程要点:
- 置信度门槛:模型分类结果 confidence < 0.6 时宁可不猜,走兜底------路由误判的成本往往高于"转人工"。
- 分支可插拔:BRANCHES 是字典,新增业务分支 = 新增一个 key,无需改路由逻辑。
- 规则层先跑:高频请求零 LLM 开销,只有长尾才花一次模型分类的 token。
11.4 主流框架对照与变体
11.4.1 框架对照
| 实现方式 | 特点 | 适用 |
|---|---|---|
| 自研(本章主线) | 两层漏斗 + 字典分支,完全可控,几行代码 | 中小规模,分支 ≤10 个 |
| LangGraph 条件边 | add_conditional_edges 根据 state 跳转 |
路由结果还要接复杂图 |
| LangChain RouterChain | 框架内置路由链,开箱即用 | 已用 LangChain 生态 |
| 语义路由器(如 semantic-router 库) | 用 embedding 相似度匹配预定义意图 | 需要"近似匹配"而非精确分类 |
LangGraph 的路由写法(条件边):
python
g.add_conditional_edges(
"router",
lambda state: state["route"], # 返回的 key 决定走向
{"logistics": "logistics_node",
"complaint": "complaint_node",
"chitchat": "chitchat_node",
"fallback": END},
)
11.4.2 变体一:模型路由(Model Routing)
按"任务复杂度 × 预算"把请求分到不同模型(小模型 vs 大模型),是路由模式在成本侧的重要应用(呼应第 23 章成本优化):
python
def model_router(complexity: str) -> str:
# simple → 便宜快的小模型;complex → 更强的模型
return "qwen-turbo" if complexity == "simple" else "deepseek-chat"
典型收益:简单请求(翻译、格式整理)走小模型,成本可降 60%+,延迟减半。判断复杂度本身也是一次小模型调用,别用大模型判断------判断模型的成本应该低于它省下的钱。
11.4.3 变体二:意图感知路由(Intent-aware Routing)
给模型路由的提示词里加上少量示例(few-shot),显著提升边界意图的准确率(呼应第 4 章 4.2 节):
python
few_shot = """
示例1 请求:"快递显示签收但我没收到" → logistics
示例2 请求:"我想退货" → order
示例3 请求:"客服是机器人吗?" → chitchat
"""
11.4.4 变体三:级联路由(Cascade Routing)
路由不再是一次性,而是逐级细化:先分大类(服务类/非服务类),服务类再细分(物流/订单/退款)。级联的好处是每级分类的候选集小,准确率高,且可以不同层级用不同策略(第一级规则、第二级模型)。
🛠 解决方案:路由误判的兜底与回退设计
常见问题
- "路由分错了,把投诉分成了闲聊":这是最高成本的误判。对策:① 模型路由加置信度门槛,低置信度转人工;② 敏感分支(投诉)设置"疑似即升级"规则------被分到闲聊但含负面词,强制走投诉链。
- "模型路由太贵了,每个请求都花一次调用":先上规则层拦高频(两层漏斗),模型只吃长尾;或改用 embedding 相似度路由,比 LLM 分类便宜一个量级。
- "新增一个分支要改一堆代码":用分支字典/注册表模式(11.3 的 BRANCHES),新增分支 = 加一个函数 + 一个 key。
- "路由提示词经常答非所问" :类别定义写"类别 + 典型例子 + 边界说明",并给 few-shot 示例;输出用 JSON + 枚举约束(
"route": "logistics|complaint|...")。 - "简单请求也走大模型分支":加复杂度前置判断(模型路由变体),简单请求走小模型,别让大模型做杀鸡的牛刀。
解决方案速查表
| 现象 | 根因 | 解决方案 |
|---|---|---|
| 高频误判 | 仅用规则层 | 两层漏斗 + 模型兜底 |
| 长尾漏判 | 规则覆盖不全 | 模型路由 + few-shot 示例 |
| 敏感请求分错 | 分类粒度不够 | 疑似即升级 + 负面词强制拦截 |
| 路由成本高 | 每请求都调模型 | 规则先行 + embedding 路由 |
| 分支难扩展 | 硬编码 if-else | 分支字典注册表 |
实战提示
- 先埋点再优化:上线第一周记录"每个请求的实际路由 + 用户是否满意",用真实数据校准路由规则,别靠猜。
- 路由要可观测:路由决策本身要打日志(含 confidence),这是后续调优的第一手数据。
- 兜底必须存在:任何路由系统都必须有 fallback 分支,且 fallback 应该"保守"------宁可转人工,不可瞎猜。
- 评估路由准确率:单独抽 100 条请求人工标注正确路由,建立路由评测集(呼应第 20 章),每次改规则后回归。