RouteRAG 这个名字,最近在几个做 RAG 落地的技术群里反复被提到。一开始我以为又是什么依赖大模型的“重型框架”,仔细摸了一遍才明白,它的核心思路恰恰相反:用一个小模型去承担检索策略的规划工作,让大模型回归生成本身,甚至让那些连大模型都跑不动的小项目,也能拥有智能的检索路由能力。
如果你正在做知识库问答、私域文档检索、或者任何“给 LLM 配上外部知识”的系统,大概率已经遇到过这种尴尬:所有查询一股脑走向量检索,简单问题杀鸡用牛刀,复杂问题又因为召回太碎导致答案答非所问。RouteRAG 解决的就是这个“检索前的最后一公里”问题——在触发检索之前,先让一个轻量模型判断“这条查询该走哪条检索路径”。
这篇文章不说虚的,只讲清楚两件事:RouteRAG 到底是怎么设计出来的,以及你拿到这个思路后如何在自己系统里复现。适合正在做 RAG 应用开发的同学,也适合那些想优化检索链路但不想背上大模型推理成本的架构师。
1. 检索策略这件事,为什么值得单独规划
RAG 应用上线容易,做好很难。绝大多数团队踩的第一个坑,就是天真地以为“向量检索 + 生成模型”就能解决所有问答。实际上不同类型的问题,对检索策略的要求截然不同,这不是一个向量召回能覆盖的。
1.1 查询的天然分型:不是所有问题都该走同一条路
我把日常遇到的知识库问题粗略分成三类,每一类对检索方式的要求都差异巨大:
- 事实型查询:例如“XX产品支持的规格有哪几种”“报销流程中发票抬头填什么”。这类问题指向明确,问答范围窄,通常一个精准的关键词匹配或向量召回就能命中,甚至走一次缓存就能解决。复杂了反而容易引入噪声。
- 推理型查询:例如“A设备在B场景下出了问题,我应该先查C模块还是D模块”。这类问题往往需要跨多个文档片段进行推理,前提是得先扩大召回范围,甚至需要多次检索,然后重排序,才能把碎片信息整合给生成模型。
- 开放型查询:例如“给我写一份关于xx领域的调研提纲”。严格来说,这类问题可能根本不需要检索本地库,或者需要检索的是“我应该从哪几个维度去组织答案”这类元信息,跟前面两类完全不同。
如果你只用一套固定检索策略,后果就是:事实型查询时召回太杂,生成模型容易被无关段落带偏;推理型查询时召回太浅,关键段落根本没进上下文;开放型查询时白白消耗检索资源,最后答案还不一定有用。
1.2 “检索策略规划”的本质是一个决策问题
想要解决上面的问题,核心不是提升向量模型的效果,而是在做检索之前,先回答一个问题:这条查询当前处于什么状态,应该调用哪些检索工具,以什么顺序调用。这就是检索策略规划。
过去多数人会直接把这个决策交给大模型:把查询和候选策略描述一起喂给 LLM,让它输出该走哪条路径。好处是灵活、能理解复杂语义,坏处也特别明显——多一次大模型推理就意味着多一笔成本和几十毫秒到几百毫秒的延迟,在高并发生产环境根本扛不住。更不谈很多小体量的项目压根没有 GPU 部署大模型的条件。
1.3 小模型为什么也能干这活:决策并不等于生成
这里的关键认知在于:检索策略规划不是自由文本生成任务,本质是分类/匹配任务。大模型擅长的是把开放问题写成一段通顺的文字,但路由决策最终只需要落到三五个离散选项上。而分类、打标签、取 argmax,这恰恰是传统小型 NLP 模型的看家本领。
从信息论角度讲,决策过程丢失的“信息”远小于生成过程。一条查询“帮我看看合同里违约金比例是多少”,大模型能写出一大段分析,但路由决策需要的核心信号只有一个:这是事实型查询,走精准召回即可。小模型不需要理解违约金的法律含义,只需要能识别出这类句式的意图特征就行。这意味着,用蒸馏出来的小模型,或者直接训练一个轻量分类器,完全可以达到接近大模型路由的效果,而推理成本可能只有后者的几十分之一。
RouteRAG 正是抓住这个点:不承诺让所有检索自动化,而是把“何时走哪条路”这个决策从大模型手里接过来,变成一个可训练、可插拔、可量化的路由决策模块。
2. RouteRAG 的核心设计与拆解思路
RouteRAG 并没有发明一个全新的算法,它更像一套组合策略。这也是我比较认可它的原因——好方案不一定复杂,但一定把边界画得足够清楚。
2.1 路由模块的输入输出设计
路由模块接收的不仅是原始查询文本,我把几个关键信号都给它:
- 查询文本本身
- 可选的用户侧附加信息(比如会话历史片段、当前知识库类别)
- 候选策略集合的元信息(比如策略名称、底层索引类型)
输出则被设计成一个策略偏好分布。当前 RouteRAG 支持的路由策略大致有 5 类:
| 策略编号 | 策略名称 | 适用场景 | 底层逻辑 |
|---|---|---|---|
| S1 | 无检索直出 | 闲聊、开放生成类问题 | 不走 RAG,直接交给生成模型 |
| S2 | 稀疏检索 | 专有名词、编号、型号查询 | 关键词/BM25 类精确匹配 |
| S3 | 向量检索 | 语义匹配、同义改写查询 | 嵌入式向量余弦相似度 |
| S4 | 混合检索 | 需要广召回再精筛的复杂查询 | 稀疏+向量并行,再做重排 |
| S5 | 多跳检索 | 需要跨文档聚合推理的查询 | 分解子问题、多次检索、汇总结果 |
这里需要说明:这个分类是常见实践的通用归纳,不一定适配所有业务,但它已经覆盖了绝大多数知识库问答的检索形态。你可以在自己系统里裁减或增加策略类目,关键是要保持每个策略之间有足够区分度,否则路由模型再准确也无法带来效果差异。
2.2 小模型的蒸馏原理:用大模型教出轻量路由
训练这个路由模块,本质上是用大模型生成的软标注去教一个小模型。具体分三步:
第一步,构建候选标注集。从业务日志里捞一批真实查询,然后用大模型对每条查询计算它在五个策略上的偏好概率分布,而不是只给一个硬标签。比如“怎么看合同里有没有写赔偿上限”这条查询,大模型可能给 S4 打 0.7,给 S3 打 0.5,给 S5 打 0.3。这个分布就是软标签。
第二步,训练小模型对齐这个分布。小模型不需要输出一段“分析过程”,它的输出层就是一个五分类的 softmax。训练时的损失函数可以同时包含硬标签的交叉熵和软标签的 KL 散度。软标签的价值在于,它告诉小模型策略之间的近似程度——比如 S3 和 S4 本来就比较接近,而 S1 和 S5 差异很大,这种连续性信号是硬标签提供不了的。
第三步,做置信度修正。小模型的分类置信度普遍存在过度自信的问题。如果软标签分布中 max 概率只有 0.4,而小模型直接输出 0.95,说明它已经发生了过拟合式失真。所以 RouteRAG 会用一个温度系数校准输出分布,并在路由决策时设一个最低置信度阈值。低于阈值就走默认策略,而不是强行赌一个。
2.3 选型:多大的模型算“小”
这里给一批经过实测的参考值:中文场景下,参数量在 100M~500M 之间的文本编码模型就足够了。我在模拟项目里用过 100M 级别的小模型,把查询文本长度截到 128 token,路由决策单条耗时在 CPU 上不到 5 毫秒,在 GPU 上更是可以忽略不计。和那个动不动几百毫秒的大模型 API 路由调用相比,这个规格已经非常适合部署在检索链路的前置环节。
需要强调,小模型不是不能犯错,而是它的错误模式可控。相比大模型随机选路,小模型的错误往往集中在相邻策略之间(比如 S3 和 S4),这不会造成致命后果。真正危险的是它混淆 S1 和 S5——一边完全放弃检索,一边疯狂多跳检索。为了规避这种跨类错误,除了靠训练时的软标签约束,我在推理端还会加一个硬规则:任何包含疑问词或者“怎么办”“为什么”的查询,禁止路由到 S1。这种“小模型+规则兜底”的混合决策思路,是保证线稳定性的关键。
3. 实操复现:把 RouteRAG 装进你自己的检索链路
下面给出一套可直接落地的配置和流程,适配你已有的 RAG 系统,架构不要求大改,可以增量替换原来的检索策略模块。
3.1 整体架构与模块划分
整个链路分成四层:
- 入口层:接收用户查询,做预处理(低质文本过滤、query 改写、语义归一)。
- 路由决策层:也就是 RouteRAG 核心,输入清洗后的查询,输出策略编号与置信度。
- 策略执行层:按路由结果触发对应的检索器,并聚合召回结果。
- 生成层:把召回文本塞进上下文,交给大模型生成答案。
这里最核心的调整在第二层。原来你可能是硬编码一套检索逻辑,或者直接调大模型 API 做 tool calling,现在抽成一个独立的小模型模块之后,链路更轻,也更容易 A/B 测试。
3.2 路由决策层的核心实现
我照着 RouteRAG 的思路写了一份 POC 代码,关键流程如下:
import numpy as np # STRATEGIES: 策略元信息 STRATEGIES = { "S1": {"desc": "direct_generate", "retriever": None}, "S2": {"desc": "sparse_bm25", "retriever": "bm25_index"}, "S3": {"desc": "dense_vector", "retriever": "vector_db"}, "S4": {"desc": "hybrid_recall", "retriever": "bm25_index+vector_db"}, "S5": {"desc": "multi_hop", "retriever": "query_analyzer"}, } def route_query(query_text, router_model, fallback_strategy="S4"): """ 基于小模型输出概率分布,决策检索策略。 router_model 输出 shape: (num_strategies,),已经过 softmax。 """ prob_dist = router_model.predict(query_text) strategy_id = int(np.argmax(prob_dist)) confidence = float(np.max(prob_dist)) # 置信度阈值判断,低于阈值则回退默认策略 if confidence < 0.6: strategy_id = strategy_mapping[fallback_strategy] # 硬性规则兜底:涉及明确疑问的查询,不允许直出 if strategy_id == strategy_mapping["S1"]: q_words = ["怎么", "怎么办", "为什么", "如何", "多少"] if any(w in query_text for w in q_words): strategy_id = strategy_mapping["S4"] # 可选:频率限制和缓存 cache_key = hash(query_text) if cache_key in strategy_cache: return strategy_cache[cache_key] strategy_cache[cache_key] = strategy_id return strategy_id这份代码是简化示意,但已经包含三个核心点:argmax 决策、置信度回退、硬规则兜底。生产环境你可能还要加入批量推理、连续字符过滤、日志反馈等,但骨架就是这个。
3.3 策略执行层的组合与结果融合
路由选完策略后,执行层需要忠实执行。这里有一个特别容易翻车的细节:如果路由选择 S4 混合检索,结果融合不能简单按“向量分数 + 关键词分数”直接相加。两种检索的分数不在一个量纲,直接相加等于给某个通道更大的话语权。
我常用的做法是分两段:先分别召回 Top-K 文档,然后做一个非对称融合。稀疏检索的命中项给予布尔加分(命中即加分),向量检索按相似度分数归一化后加权,最后整体丢给一个轻量重排模型(如果有的话),或直接按融合分排序取 Top-N 送生成层。
# 伪代码示意:混合召回融合逻辑 bm25_hits = get_bm25_results(query, top_k=10) vector_hits = get_vector_results(query, top_k=10) for doc in bm25_hits: doc.fusion_score = doc.bm25_score_rank * 1.0 # 稀疏通道加分 for doc in vector_hits: doc.fusion_score += doc.dense_score_norm * 1.2 # 稠密通道加权 # 过滤掉相似度阈值过低的段落 results = [doc for doc in candidates if doc.fusion_score > 0.3] results.sort(key=lambda x: x.fusion_score, reverse=True) final_context = results[:5]至于 S5 多跳检索,比混合检索还麻烦一点。它需要先把 query 拆成隐含的子问题,再按子问题分别检索、合并结果。这一层我目前是把拆子问题交给大模型做的,因为这一步的语义理解要求确实高。但 RouteRAG 的路由决策依然用了小模型判断“这条查询是否需要多跳”,所以整体成本还是省了一大截。
3.4 效果评估:两个离线指标比什么都重要
很多团队上线 RAG 只看最终答案打分,这是不够的。路由模块的引入必须单独测三个维度:
- 路由准确率:预测策略和人工标注策略一致的占比。这个指标至少要在 85% 以上才值得上线。
- 策略区分度:如果所有查询都被路由到同一个策略,说明模型没学会区分,相当于白做。理想状态下各策略分布应有一定差异,不能出现单一策略占比超 70% 的情况。
- 端到端答案质量:路由变化后,生成答案的引用准确率、事实一致性的变化。
关于在线评估,可以小流量让路由模块和固定策略并行运行,比较同一批查询的首字节延迟、检索召回量、用户点赞率等。我自己的实测结果是:加了 RouteRAG 之后,端到端延迟不升反降——因为简单查询被路由到 S2/S3 之后,上下文拼接变短,生成环节的耗时显著下降,整体收益非常明显。
4. 常见问题与排查技巧实录
这一节全部来自我在实际项目中踩过的坑,直接写针对性的解决思路,你可以当排查表用。
| 现象 | 根因 | 处理方式 |
|---|---|---|
| 路由结果经常来回变 | 小模型对相似 query 的特征提取不稳定 | 增加输入特征(拼接历史会话)、加缓存、调高置信度阈值 |
| 专有名词查询走向量检索后召回差 | 向量模型没学过业务词表,语义匹配失效 | 强制让包含型号/代码/编号的查询路由到 S2 稀疏检索 |
| 复杂查询被路由到 S3,上下文碎片化 | 训练时候 S4 的样本不足 | 数据增强:把长文档切分后的推理类样本多合成一些,再重训 |
| 简单问题反而走了多跳检索,时延爆炸 | 模型对策略边界不敏感 | 对 S5 加惩罚项:训练时提高 S5 的类别权重,或者要求 S5 触发时必须有高置信度 |
| 路由模块上线后,效果不如全量混合检索 | 融合策略没调好 | 不要只换路由不换执行器,S4 的融合分数权重也得跟着调 |
| 小模型在大模型的软标签分布下收敛慢 | 蒸馏时软标签分布太平滑 | 对软标签做一个温度缩放,让分布更锐利后再作为训练目标 |
4.1 置信度过拟合问题
这是所有小模型路由方案的通病。小模型的输出分布经常出现 0.99 这种极端置信度,仿佛机器非常确信某条查询属于 S3。但实际上这种确信往往是看中了句子里某个高频词,而不是真正理解了查询语义。我在项目里强制对训练日志做了概率分布监控,如果某条 query 的所有分支概率超过 0.9,就视为异常,需要人工检查路由是否合理。
一个更实用的技巧是:路由模块永远不输出唯一的决策结果,而是输出 Top-2 候选策略。执行层拿到 Top-2 后可以并行召回,但最终上下文只从第一个策略的召回结果中取,只有第一个策略空召回时,才启用备选策略。这个方案牺牲了一点召回率,但极大避免了“单一策略选错全盘皆输”的灾难。
4.2 标注数据从哪里来
新项目没有标注样本怎么训练路由模型?我的顺序是:
- 先拿大模型跑 300~500 条真实 query,输出策略分布,做初版软标注。
- 人工抽查其中 100 条,修正明显判断错误的标签,尤其要修正跨类错误(比如把事实型标成了多跳)。
- 用修正后的 100 条做标准,筛掉和标准矛盾的训练样本。
- 训练小模型后,再让它预测所有 query,把置信度低的样本捞出来人工补充标注。
这样两轮迭代之后,路由准确率基本能从 70% 提到 85% 以上。千万不要上来就想做个“完美数据集”,RAG 的查询变体无穷无尽,软标注-人工修正-重训这个飞轮转起来比一次性打标几万条高效得多。
4.3 什么时候不要用 RouteRAG
如果是极小的 Demo,或者只有十几个文档的玩具项目,就别折腾路由了。直接混合检索一把梭,效果不会差。路由的价值在查询分布足够多元、检索源足够庞大的时候才会放大。另外一个不建议的场景是知识库高度垂直、文档形态高度统一(比如全是同一种规格说明书),此时所有查询本质上都走同一策略,路由模块只能增加无用开销。
5. 我的一些额外心得
个人做下来最大的收获,不是学会怎么训练小模型,而是重新理解了“智能”在系统里的位置。过去总觉得检索策略这种东西要越高级越好,恨不得让大模型来充当大脑。实际跑通之后才意识到,在一个生产链路里,99% 的流量是不需要“理解”的,它们只需要被准确识别。把识别任务交给小而美的模型,把大模型的算力留给真正需要它产出的那 1%,这条路的价值往往被严重低估。
另外给一个建议:实现 RouteRAG 时不要一上来就设计九个策略。我最初试图支持策略越全越好,结果小模型在九分类任务上准确率掉了不少,每个策略的样本都不够。后来砍到五类,效果立刻提升。在分类任务里,“少而清晰”永远胜过“多而模糊”。
这个方案复现起来成本不高,但收益很大。如果你的 RAG 系统也遇到了“检索策略僵化”的问题,可以先从最小路子模型试起。动手之后会发现,把检索策略这个暗盒变成显式的路由决策,整个系统的可调试性都上了一层台阶。