1. 项目概述:从面试题到工程化思考
“你的 RAG 有几种检索路径?怎么决定走哪条?” 这问题一抛出来,很多做 RAG 的朋友可能心里会咯噔一下。我们平时聊 RAG,张口闭口就是向量检索、Embedding 模型、Chunk 策略,但真到了要设计一个健壮、智能的检索系统时,才发现“检索”本身就是一个需要精心设计的复杂模块。这不仅仅是京东二面的一个追问,更是所有 RAG 系统从“能用”走向“好用”必须跨越的一道坎。
简单来说,这个问题直指 RAG 系统的核心工程挑战:检索路径的多样性与路由决策的智能化。一个成熟的 RAG 系统,其检索后端绝不应该只有向量数据库这一条路。面对用户千变万化的 Query,我们如何像一位经验丰富的调度员,快速判断该派哪支“检索小队”上场,甚至如何让多支小队协同作战,最终汇总出最精准的答案?这就是 Query 路由框架要解决的问题。
我结合自己踩过的坑和项目实战经验,梳理出了一个“四层路由框架”。这个框架不是纸上谈兵,而是从实际需求中抽象出来的,旨在系统性地解决“走哪条路”和“怎么走”的问题。它涵盖了从最基础的检索器选择,到复杂的多路召回与融合,再到最终的智能路由决策。接下来,我们就一层层拆解,看看如何为你的 RAG 系统装上“最强大脑”。
2. 核心需求解析:为什么单一检索路径不够用?
在深入框架之前,我们必须先理解,为什么在 RAG 中执着于单一检索路径(比如只依赖向量相似度)是危险的。这源于用户 Query 和知识库文档本身固有的复杂性。
2.1 用户 Query 的多样性挑战
用户的提问方式千差万别,对检索的诉求也完全不同:
- 精确匹配型:例如“《劳动合同法》第三十九条的具体内容是什么?”。这种 Query 包含明确的实体(法律名称)和精确的定位信息(条款号)。此时,基于关键词精确匹配的检索器(如 BM25、Elasticsearch 的 term query)效率最高、结果最准。向量检索可能因为语义泛化而引入不相关结果。
- 语义泛化型:例如“公司可以随便开除员工吗?”。这个问题背后对应的可能就是《劳动合同法》中关于解除劳动合同的诸多条款。用户没有使用任何法律术语,而是用口语化的方式表达。这时,向量检索的优势就体现出来了,它能捕捉到“开除员工”与“解除劳动合同”之间的语义相似性。
- 混合意图型:更多时候,Query 是混合的。比如“对比一下 BMW 3系和 Tesla Model 3 在续航和操控上的表现”。这里既有需要精确匹配的实体(“BMW 3系”, “Tesla Model 3”),也有需要语义理解的抽象概念(“续航表现”, “操控表现”)。单一检索路径难以同时满足。
2.2 知识库内容的异构性
我们的知识库也并非铁板一块:
- 结构化数据:可能是数据库中的表格,包含明确的字段(如产品参数表:型号、价格、续航里程)。对于“特斯拉 Model 3 的续航是多少公里?”这类问题,用 SQL 或精确查询比向量检索更直接。
- 非结构化文本:大量的文档、报告、文章。这是向量检索的主战场。
- 半结构化数据:如 JSON、XML 或 Markdown,其中部分内容有明确标签(如标题、作者、日期)。检索时可能需要结合关键词(在标题中查找)和语义(在内容中理解)。
2.3 单一检索器的固有缺陷
- 向量检索的“语义漂移”:过于依赖语义相似度,可能召回在语义上相关但事实不匹配的文档。例如,Query 是“Python 列表的 append 方法”,向量模型可能因为“添加元素”、“数据结构”等语义关联,召回了关于“字典 update 方法”或“集合 add 方法”的文档。
- 关键词检索的“词汇鸿沟”:无法解决同义词、近义词问题。例如,知识库文档写的是“新能源汽车”,用户 Query 是“电动车”,BM25 可能完全匹配不上。
- 效率与效果的权衡:在大规模知识库中,精确的向量相似度计算(如余弦相似度)可能很耗时。而一些轻量级检索器(如 BM25)速度更快,可作为第一层粗筛。
因此,一个健壮的 RAG 系统必须配备多种检索路径(检索器),并建立一个智能的路由机制,根据当前 Query 的特征,动态选择最合适的一条或多条路径执行检索。这就是我们构建四层框架的根本动机。
3. 四层路由框架详解
我将这个框架分为四个层次,自底向上,从数据准备到智能决策,层层递进。
3.1 第一层:检索器池构建
这是整个框架的基石。在这一层,我们需要预先准备好多种不同的“检索武器”。常见的检索器包括:
- 向量检索器:核心武器。使用如
text-embedding-ada-002、BGE、M3E等模型将文档和 Query 转换为向量,通过向量数据库(如 Milvus, Pinecone, Weaviate)或本地库(FAISS, Chroma)进行相似度搜索。适合语义匹配。 - 关键词检索器:经典武器。以 BM25 算法为代表,可以通过 Elasticsearch、MeiliSearch 或
rank_bm25等库实现。它对 Term 的频率和文档长度进行加权,擅长精确匹配和词汇召回。 - 混合检索器:这不是一个独立的检索器,而是一种策略。通常同时使用向量和关键词检索,然后对结果进行融合(如加权平均、RRF)。LangChain 和 LlamaIndex 都提供了
EnsembleRetriever之类的工具。 - 图检索器:如果知识库中存在丰富的实体和关系(例如人物、地点、事件),可以构建知识图谱。对于涉及多跳推理的 Query(如“A 公司的 CEO 毕业于哪所大学?”),图检索能沿着关系路径查找,这是向量检索难以做到的。
- SQL/查询检索器:针对结构化数据。将自然语言 Query 通过 Text-to-SQL 模型转换为数据库查询语句,直接获取精准答案。
实操心得:不要试图自己从头实现 BM25 或向量检索核心算法。优先使用成熟、高效的库或服务。例如,对于 BM25,在生产环境中强烈推荐使用 Elasticsearch,它不仅提供了 BM25 实现,还有强大的分词、过滤、聚合能力。对于向量检索,Milvus 或 Pinecone 这类专业向量数据库在性能、可扩展性上远胜于用纯 Python 库管理大规模向量。
3.2 第二层:Query 理解与特征提取
在决定走哪条路之前,必须先“读懂”Query。这一层的目标是将原始的 Query 文本转化为一系列可量化的特征,供路由决策层使用。
- 基础文本特征:
- 长度:长 Query 可能意图更复杂,需要更精细的检索。
- 关键词密度:包含多少名词、实体词?高密度可能偏向关键词检索。
- 句法复杂度:是否包含疑问词、比较级、并列结构?
- 语义/意图特征:
- 意图分类:使用一个轻量级分类模型(或基于 Embedding 的聚类)判断 Query 属于哪一类意图(如“事实问答”、“对比分析”、“摘要生成”、“代码查询”)。不同意图对检索精度和召回的要求不同。
- 领域识别:Query 属于技术、法律、医疗还是通用领域?某些领域可能有特定的检索器偏好(如法律条文重精确匹配)。
- 实体识别:使用 NER 模型识别出 Query 中的人名、地名、组织名、时间、法律条款等实体。实体数量多且明确,是触发关键词或图检索的强信号。
- 检索历史特征(可选但强大):
- 可以记录用户历史 Query 的成功检索路径。如果当前 Query 与历史某个成功 Query 相似,可以优先尝试相同的路径。
# 一个简化的特征提取示例(伪代码) def extract_query_features(query: str): features = {} features['length'] = len(query.split()) features['num_entities'] = len(ner_model(query)) # 假设有NER模型 features['contains_comparison'] = '对比' in query or 'vs' in query.lower() # 使用一个轻量级句子编码器获取语义向量,用于后续相似度计算 features['embedding'] = sentence_encoder(query) features['intent'] = intent_classifier(query) # 输出意图标签 return features3.3 第三层:路由决策引擎
这是框架的“大脑”。它接收第二层提取的特征,并决定最终调用哪个或哪几个检索器,以及如何调用(例如,给不同检索器分配不同的权重)。决策策略可以从简单到复杂:
规则路由:最简单直接。基于特征设定 if-else 规则。
if features['num_entities'] > 2 and features['intent'] == 'factual': # 实体多,事实型问题,优先用关键词检索 retriever = bm25_retriever elif features['contains_comparison']: # 对比型问题,需要广泛召回,用混合检索 retriever = hybrid_retriever else: # 默认走语义检索 retriever = vector_retriever优点:简单、透明、易调试。缺点:规则难以覆盖所有复杂情况,维护成本随场景增多而剧增。
模型路由:更智能。将路由决策建模为一个分类或排序问题。
- 分类模型:训练一个分类器(如 SVM、XGBoost、浅层神经网络),输入是 Query 特征,输出是应使用的检索器类型(或组合)。
- 排序模型:训练一个模型(如 Learning to Rank),对同一个 Query,预测不同检索器返回结果的相关性分数,选择预期分数最高的检索器。
- 训练数据:需要积累一批 Query 及其对应的“最佳检索路径”标签。可以通过人工标注,或者通过一个更昂贵的“全能检索系统”(同时调用所有检索器,用 LLM 评估结果质量)来自动生成训练数据。
强化学习路由(高级):将路由决策视为一个序列决策问题,通过与 LLM 答案质量反馈(Reward)进行交互来优化路由策略。这属于更前沿的 Agentic RAG 范畴,实现复杂,但长期收益可能更高。
注意事项:在项目初期,强烈建议从规则路由开始。快速上线,通过日志收集真实用户的 Query 和检索结果,分析路由决策的成功与失败案例。积累到一定量的数据后,再考虑升级到模型路由。切忌一开始就追求复杂的模型,容易陷入数据不足、调试困难的泥潭。
3.4 第四层:结果融合与重排序
决策引擎选定了检索路径(可能是一条,也可能是多条并行),执行检索后,我们可能得到来自不同检索器的多组结果。这一层负责将这些结果“化零为整”,得到最终提交给 LLM 的上下文。
结果融合:
- 加权平均:给不同检索器的结果分配权重(可由路由决策引擎输出),按权重计算综合得分。
- 倒数排名融合:这是一种无参数且非常有效的融合方法。它假设不同检索器的结果是互补的。每个文档在不同检索器结果列表中的排名倒数会被相加,总分高的文档排在前面。
- 布尔逻辑:对于某些强规则场景,可以取交集(AND)或并集(OR)。
重排序: 融合后的列表已经是粗排的结果,但精准度还有提升空间。重排序使用一个更精细但通常也更耗时的模型,对 Top K 个粗排结果进行重新打分。
- 交叉编码器:如
BGE-Reranker、Cohere Rerank。它将 Query 和每个候选文档一起输入模型,直接计算相关性分数,比双塔式向量检索的点积计算更准确。 - LLM 重排:用 LLM 根据 Query 对候选文档列表进行排序或评分。成本高,速度慢,但能力最强,通常用于对质量要求极高的场景或作为生成训练数据的工具。
- 交叉编码器:如
实操心得:RRF 是混合检索中“开箱即用”效果很好的融合方法,几乎不需要调参。重排序模块是提升最终效果的关键,但会显著增加延迟。一个常见的折中策略是:“粗排多路召回,精排重序 Top”。即用多种检索器(向量+关键词)召回较多数量的候选文档(如 50 个),经过融合粗排后,选取 Top 10-20 个文档送入重排序模型进行精排,最后将精排后的 Top 5-8 个文档交给 LLM 生成答案。这样在效果和延迟之间取得了较好的平衡。
4. 实战:构建一个基于规则的四层路由系统
让我们用一个具体的例子,串联起整个四层框架。假设我们有一个产品知识库,包含技术规格(结构化表格)和用户手册(非结构化文本)。
4.1 步骤一:构建检索器池
- 向量检索器:使用
BGE-M3模型为所有用户手册文本生成嵌入,存入 Chroma 数据库。 - 关键词检索器:使用 Elasticsearch 索引所有文档(包括手册和产品名称等元数据),配置 BM25 相似度算法。
- SQL 检索器:连接存放产品规格的 PostgreSQL 数据库。
- 混合检索器:用 LangChain 的
EnsembleRetriever包装前两者,设定初始权重为 0.5/0.5。
4.2 步骤二:实现特征提取与规则路由
我们设计一套简单的规则:
- 规则1:如果 Query 能通过预设的 Regex 或简单解析,映射成明确的 SQL 查询模式(如“某产品某参数是多少”),则路由至SQL 检索器。
- 规则2:如果 Query 中包含明确的产品型号(通过关键词列表或NER识别),且意图为“参数查询”、“对比”,则路由至混合检索器(因为需要同时查精确规格和语义相关的评测)。
- 规则3:如果 Query 是“如何...”或“为什么...”这类问题解决型,则路由至向量检索器(侧重于语义理解)。
- 规则4:其他情况,默认使用关键词检索器。
# 简化版路由函数示例 def rule_based_router(query: str, features: dict) -> str: # 规则1: SQL模式匹配 if can_be_sql(query): return "sql" # 规则2: 包含产品型号且为查询/对比意图 if contains_product_model(query) and features.get('intent') in ['query', 'compare']: return "hybrid" # 规则3: 问题解决型 if query.startswith('如何') or query.startswith('为什么'): return "vector" # 规则4: 默认 return "keyword" # 执行检索 def retrieve_with_router(query: str): features = extract_query_features(query) route_to = rule_based_router(query, features) if route_to == "sql": return sql_retriever.retrieve(query) elif route_to == "hybrid": return hybrid_retriever.retrieve(query) elif route_to == "vector": return vector_retriever.retrieve(query) else: return keyword_retriever.retrieve(query)4.3 步骤三:集成融合与重排序
假设我们的路由决策是“hybrid”,那么hybrid_retriever会并行调用向量和关键词检索器,各召回 30 个文档。
- 融合:我们使用 RRF 方法对 60 个候选文档进行融合,得到粗排的 Top 20。
- 重排序:将这 Top 20 个文档与原始 Query 一起,输入到我们本地部署的
BGE-Reranker模型中,获取精排分数。 - 截断:选取精排后的 Top 5 个文档,作为最终上下文,送入 LLM(如 Qwen2.5)生成答案。
4.4 步骤四:评估与迭代
上线后,我们需要建立评估闭环:
- 日志记录:详细记录每个 Query 的特征、路由决策、各检索器返回结果、融合重排后的上下文、LLM 的最终回答。
- 人工评估:定期抽样,由人工判断路由决策是否合理、最终答案是否准确。
- 规则优化:根据 bad cases 分析,调整路由规则。例如,发现很多“对比”类问题用混合检索效果不好,可能是因为关键词部分干扰太大,可以尝试调整混合检索的权重,或者为“对比”类单独设计一个融合策略。
- 数据积累:将人工评估的“Query-最佳检索路径”对保存下来,作为未来训练模型路由的标注数据集。
5. 避坑指南与进阶思考
在实际搭建这套框架时,你会遇到不少坑。这里分享几个关键点:
冷启动问题:系统初期没有数据,如何制定路由规则?我的建议是:基于业务逻辑的小规模采样分析。从目标用户可能提出的问题中,人工收集或生成 100-200 个有代表性的 Query,手动测试不同检索器的效果,总结出初步的规则。这个种子集非常重要。
延迟与成本权衡:并行调用多个检索器虽然可能提升效果,但会增加延迟和计算成本。解决方案是“级联”或“条件执行”。例如,先使用最快的检索器(如关键词),如果其返回结果的置信度(如最高分超过阈值)足够高,则直接返回;否则,再触发更耗时但更精准的检索器(如向量或混合)。这需要为每个检索器定义置信度度量。
路由决策的评估:如何评估路由本身的好坏?不能只看最终答案质量,因为 LLM 可能“兜底”。一个直接的指标是“检索结果相关性”。可以用人工或一个高质量的 NLI/重排模型,去评估路由选择的检索器返回的 Top K 文档,与 Query 的平均相关性分数,是否高于其他未选中的检索器。
走向 Agentic RAG:四层框架已经具备了智能体的雏形。你可以将“路由决策引擎”看作一个“工具选择(Tool Selection)”的智能体。进阶方向是让这个智能体不仅选择工具,还能根据中间结果进行多步推理和迭代检索。例如,先检索到一个概念的定义,发现定义中提到了另一个相关实体,再自动发起第二轮针对该实体的检索。这就是更高级的 Agentic RAG 场景,其核心是让检索过程具有了规划、反思和迭代的能力。
框架不是银弹:这个四层框架提供了一个系统性的思考和工作流程。但对于一些简单、垂直的场景,可能只需要两层(检索器+融合)就够了。始终记住,架构的复杂度要与业务需求相匹配。先从简单方案开始,用数据驱动它演进。
面试官问“有几种检索路径”,他期待的不仅仅是一个数字列表,而是背后对 RAG 复杂性的深刻理解,以及你如何系统化、工程化地解决这些问题的思路。从构建多元的检索器池,到深入理解 Query 意图,再到设计智能的路由策略,最后妥善地融合结果,这四步构成了应对这一挑战的完整蓝图。