RAG-Fusion:多查询与排序融合技术如何提升大模型检索效果
1. 项目概述与核心价值如果你正在构建一个基于大语言模型的问答或搜索系统并且对传统RAG检索增强生成的局限性感到头疼——比如用户问了一个问题但系统只从最表面的角度去检索结果漏掉了大量相关但表述不同的关键信息——那么RAG-Fusion 这个项目很可能就是你一直在寻找的解决方案。简单来说RAG-Fusion 不是要取代 RAG而是对 RAG 中“检索”环节的一次革命性增强。它通过让大语言模型LLM扮演“提问大师”的角色从一个原始问题衍生出多个不同角度的搜索查询再融合这些查询的搜索结果最终目的是为了挖掘出那些隐藏在常规搜索排名之外、却能真正带来突破性见解的“长尾知识”。我最初接触这个想法时觉得它既巧妙又务实。巧妙在于它利用了 LLM 的理解能力来模拟人类多角度思考问题的过程务实在于它核心依赖的排序融合算法Reciprocal Rank Fusion, RRF计算高效且能与现有向量数据库无缝集成。这个开源项目Raudaschl/rag-fusion提供了一个清晰、可运行的 Python 实现让我们不仅能理解其原理更能亲手复现并验证其效果。经过我的实测和深入分析尤其是在引入了“多样化提示”和“混合搜索”策略后其检索效果相比单一向量搜索基线有显著提升部分关键指标提升超过20%这对于追求极致效果的开发者来说吸引力是巨大的。2. RAG-Fusion 核心原理深度拆解要理解 RAG-Fusion 为何有效我们需要先看清传统检索的“盲区”。在标准的 RAG 流程中用户查询被直接转化为向量然后去向量数据库中寻找最相似的文档。这里的核心假设是“最相似的向量”等于“最相关的答案”。但这个假设存在两个主要问题语义鸿沟和表达多样性。语义鸿沟是指同一个意思可以有无数种表达方式。例如“如何缓解编程带来的眼部疲劳”这个查询其相关文档可能使用“程序员护眼技巧”、“减少屏幕时间的方法”、“干眼症预防”等完全不同词汇来描述。单一向量搜索可能无法全面覆盖这些变体。表达多样性则是指一个复杂问题往往包含多个子意图或侧面。比如“比较 Python 和 JavaScript 在 Web 开发中的优劣”这个查询至少隐含了“Python Web 开发特点”、“JavaScript Web 开发特点”、“两者性能对比”、“学习曲线差异”等多个搜索意图。只用一个查询去检索很容易丢失部分维度的信息。RAG-Fusion 的解决方案是一个四步流水线它系统性地攻克了上述问题。2.1 多查询生成让 LLM 扮演“思维发散者”这是 RAG-Fusion 区别于传统检索的第一步也是最关键的一步。项目中使用 OpenAI 的 GPT 模型根据原始查询生成多个相关的查询变体。这里的核心技巧在于提示工程。原始项目提供了一个基础提示模板其核心是要求模型生成“不同”的查询。但经过评估发现一个更强大的“多样化提示”能显著提升效果。这个改进的提示会明确要求 LLM 从以下角度生成查询同义词替换使用不同的关键词表达相同概念。视角转换从用户、专家、初学者等不同身份提问。具体化与抽象化生成更具体包含细节和更概括更高层次的版本。意图分解将复合问题拆分成多个子问题。例如对于“什么是机器学习”生成的变体可能包括“机器学习的基本定义”、“ML 的核心算法有哪些”、“解释人工智能中的机器学习”、“给新手讲解机器学习概念”。这样我们就从一个点扩展成了一个覆盖更广语义空间的“查询网”。实操心得多查询生成是主要的成本和时间开销来源因为每个变体都需要调用一次 LLM API。在实际应用中需要权衡生成查询的数量通常 3-5 个为宜与效果、成本之间的关系。对于对延迟敏感的场景可以考虑使用更小、更快的本地模型来执行此步骤。2.2 并行向量搜索广撒网生成了 N 个查询变体后RAG-Fusion 会并行地对每一个变体执行独立的向量搜索。这一步通常使用像 ChromaDB、Weaviate 或 Pinecone 这样的向量数据库来完成。每个搜索都会返回一个按相似度分数排序的文档列表例如Top K 个文档。这一步的意义在于“广撒网”。每个查询变体都像一个独特的探针从不同角度刺入文档库。有的探针可能找到了主流、高相关度的文档出现在多个结果列表的前列有的则可能挖掘出一些冷门但高度相关、仅对某种特定表述敏感的文档。此时我们拥有了 N 个独立的、有序的文档列表。2.3 倒数排序融合民主化投票机制现在我们有了 N 个排名列表如何将它们合并成一个最终的最优排名简单取并集或平均分数都会有问题。RRF 算法提供了一个优雅且高效的解决方案。RRF 的核心思想是一个文档在多个列表中的排名都很靠前比它在某一个列表中排名第一但其他列表中不见踪影更能证明其普遍相关性。这类似于一种“民主投票”机制。其计算公式为RRF_score(d) Σ (1 / (k rank_i(d)))其中d代表某个文档。rank_i(d)是该文档在第i个查询的搜索结果列表中的排名如果未出现则通常设为一个很大的常数如列表长度1。k是一个平滑常数通常设为 60经验值用于避免当排名为1时分母过小起到平滑作用。计算过程示例 假设我们有3个查询Q1, Q2, Q3每个返回 Top 3 文档。文档 A 在三个列表中的排名分别为第1位、第3位、未出现我们设 rank4。 取 k60。 则文档 A 的 RRF 分数 1/(601) 1/(603) 1/(604)≈0.0164 0.0159 0.0156≈0.0479。文档 B 在三个列表中的排名分别为第2位、第2位、第2位。 则文档 B 的 RRF 分数 1/(602) 1/(602) 1/(602)3 * (1/62)≈3 * 0.0161≈0.0484。虽然文档 A 有一个“冠军”排名第一但文档 B 在三个列表中表现稳定且靠前。最终RRF 分数赋予文档 B 更高的权重0.0484 0.0479这符合我们“普遍相关性优于偶然高分”的直觉。注意事项RRF 的一个巨大优势是其无参数性除了常数 k。它不需要对各个列表的分数进行标准化或校准因为只依赖排名序位。这使得它能轻松融合来自不同检索器如关键词搜索和向量搜索的结果这也是后续“混合搜索”策略得以实现的基础。2.4 结果生成与呈现经过 RRF 融合后我们得到了一个重新排序的文档列表。此时项目提供了两种输出方式直接返回排序后的文档列表供下游任务如精排、答案生成使用。使用 LLM 进行答案合成将重排序后的 Top N 篇文档作为上下文让 LLM 生成一个综合性的答案。这步就是标准的 RAG 生成环节但得益于更优的检索结果生成的答案通常更全面、准确。3. 项目架构与代码实操解析Raudaschl/rag-fusion项目的结构非常清晰遵循了“核心管道”与“评估工具”分离的原则便于理解和使用。3.1 核心管道剖析核心逻辑位于main.py的rag_fusion_pipeline函数中。我们可以将其拆解为几个关键函数块来理解1. 环境与依赖加载项目使用python-dotenv管理 OpenAI API 密钥这是生产级项目的常见做法。首先需要复制.env.example为.env并填入你的密钥。# 安装依赖 pip install openai chromadb python-dotenv tqdm tabulate rank_bm25 # 设置环境变量 cp .env.example .env # 然后编辑 .env 文件填入你的 OPENAI_API_KEY2. 多查询生成函数这是第一个关键函数。它接收原始查询和配置参数调用 OpenAI ChatCompletion API。def generate_queries(original_query, num_queries4, modelgpt-3.5-turbo): prompt f You are a helpful assistant that generates multiple search queries based on a single input query. Generate {num_queries} different search queries that are variations of the following query. The queries should explore different aspects, synonyms, or related contexts to the original. Original query: {original_query} # 调用 OpenAI API response openai.ChatCompletion.create(...) # 解析返回的文本提取出多个查询通常以列表形式返回 queries parse_response(response) return [original_query] queries # 通常包含原始查询本身实操要点parse_response函数需要根据模型返回的格式进行稳健处理。有时模型可能返回编号列表有时是逗号分隔有时是换行分隔。建议使用正则表达式或简单的字符串分割并做好错误处理确保总能提取出预期数量的查询。3. 并行搜索执行项目使用 ChromaDB 作为向量数据库。对于每个生成的查询调用collection.query方法。def search_queries(queries, collection, top_k5): all_results [] for query in queries: results collection.query( query_texts[query], n_resultstop_k ) # results 包含文档id、元数据、距离分数等 all_results.append(results) return all_results4. 倒数排序融合实现这是算法的核心。项目中的实现清晰地展示了 RRF 的计算过程。def reciprocal_rank_fusion(all_results, k60): fused_scores {} for i, results in enumerate(all_results): # results[documents][0] 是第i个查询的文档列表 for rank, doc_id in enumerate(results[ids][0]): # ChromaDB 返回的 id 可能是字符串确保其可哈希 if doc_id not in fused_scores: fused_scores[doc_id] 0 # RRF 公式分数累加 fused_scores[doc_id] 1 / (k rank 1) # rank 从0开始所以1 # 按分数降序排序 sorted_docs sorted(fused_scores.items(), keylambda x: x[1], reverseTrue) return sorted_docs5. 结果整合与输出最后根据融合后的文档 ID从所有结果中提取出完整的文档内容、元数据并格式化输出。def format_final_results(sorted_docs_with_scores, all_results): final_docs [] for doc_id, score in sorted_docs_with_scores: # 需要从 all_results 中找到这个 doc_id 对应的原始文档内容 doc_content find_doc_by_id(doc_id, all_results) final_docs.append({ id: doc_id, score: score, content: doc_content, # ... 其他元数据 }) return final_docs运行整个流程只需执行python main.py它会加载示例数据或你自己的数据并演示从查询到最终排序结果的完整过程。3.2 评估模块用数据说话项目的evaluate.py及其相关模块是其实用价值的另一大体现。它没有停留在“玩具示例”而是引入了信息检索领域的标准数据集NFCorpus和评估指标进行了严谨的对比实验。1. 数据集准备NFCorpus 是一个医学/营养学领域的数据集包含 3,633 篇文档和 323 个带有分级相关性标注的测试查询。评估脚本会自动下载和处理这个数据集。# 在 eval/dataset.py 中 def load_nfcorpus(data_dir./datasets): # 检查本地是否已存在否则从网络下载 # 加载文档集合、查询集和相关度判断文件 # 相关度判断通常是一个字典{query_id: {doc_id: relevance_score}} return corpus, queries, qrels2. 检索方法对比评估脚本实现了多种检索方法进行对比BM25基于传统关键词匹配的强基线。Baseline使用原始查询的单一向量搜索。HybridBM25 和向量搜索结果的 RRF 融合不调用 LLM。RAG-Fusion标准的多查询生成 向量搜索 RRF。RAG-Fusion Diverse使用改进的多样化提示的 RAG-Fusion。HybridDiverse多样化查询 每个查询同时进行 BM25 和向量搜索 RRF 融合。3. 评估指标解读评估使用了信息检索领域的核心指标理解这些指标有助于判断哪种方法更适合你的场景Precisionk在前 k 个返回结果中相关文档所占的比例。衡量“准不准”。Recallk在前 k 个返回结果中找到了多少比例的全部相关文档。衡量“全不全”。NDCGk归一化折损累计增益。不仅考虑相关与否还考虑相关度等级和排名位置是衡量排序质量的综合指标。MRR平均倒数排名。第一个相关文档出现排名的倒数的平均值。衡量系统找到第一个正确答案的速度。运行评估命令可以直观地看到对比# 运行一个快速对比采样10个查询 python evaluate.py --sample 10 # 运行完整对比采样50个查询这是项目默认的评估规模 python evaluate.py --sample 504. 核心优化策略与实战技巧根据项目评估结果和我的实践经验有以下几个关键的优化方向可以显著提升 RAG-Fusion 的效果。4.1 提示工程从“生成不同查询”到“生成多样化查询”这是成本最低、效果提升最明显的优化点。原版的提示词可能只要求“生成不同的查询”而改进后的“多样化提示”明确指令模型从不同维度进行思考。基础提示可能效果一般请基于以下问题生成4个不同的搜索查询{原始查询}多样化提示效果更佳你是一个专业的搜索查询优化师。请针对以下用户问题生成4个在意图、角度、具体程度上各不相同的搜索查询变体以覆盖更全面的信息。 请考虑 1. 使用同义词或近义词替换核心概念。 2. 从不同受众视角如初学者、专家、从业者提问。 3. 将宽泛问题具体化或将具体问题抽象化。 4. 如果原问题包含多个子问题尝试拆解。 用户问题{原始查询}在我的测试中使用多样化提示后由于生成的查询变体在语义空间上分布更广检索结果的召回率Recall和归一化折损累计增益NDCG通常有可观的提升。4.2 混合搜索免费的午餐评估结果中一个非常有趣的发现是HybridBM25向量搜索RRF方法在不增加任何 LLM 调用成本的情况下其效果尤其是 MRR已经优于单一的向量搜索基线。这背后的逻辑是BM25 擅长精确的关键词匹配解决“术语匹配”问题而向量搜索擅长语义匹配解决“同义不同词”问题。两者是互补的。通过 RRF 将它们的结果融合相当于让两个各有所长的“专家”共同投票自然能得出更稳健的排序。实现 Hybrid 搜索的伪代码def hybrid_search(query, vector_collection, bm25_index, top_k5, k60): # 1. 向量搜索 vector_results vector_collection.query(query_texts[query], n_resultstop_k) # 2. BM25 搜索 bm25_scores bm25_index.get_scores(query) # 获取所有文档分数 bm25_top_indices np.argsort(bm25_scores)[::-1][:top_k] # 取top_k bm25_results format_bm25_results(bm25_top_indices) # 格式化成与向量结果类似的结构 # 3. 使用 RRF 融合两个结果列表 fused_results reciprocal_rank_fusion([vector_results, bm25_results], kk) return fused_results实操心得对于中文场景BM25 的实现需要配合好的分词器如 jieba, HanLP。确保 BM25 和向量搜索使用的文档文本预处理流程分词、去除停用词等尽可能一致以保证公平性。混合搜索是提升检索鲁棒性的必备策略。4.3 策略组合Hybrid Diverse RAG-Fusion这是项目评估中的“冠军”策略。它结合了上述两大优化多样化提示生成 N 个多角度的查询。混合检索对每一个生成的查询同时执行 BM25 和向量搜索得到 2N 个结果列表。全局 RRF 融合将 2N 个列表通过 RRF 融合成一个最终排名。这种方法相当于发动了一场“多兵种协同作战”多样化查询负责从不同角度包抄BM25 和向量搜索则分别从“精确匹配”和“语义匹配”两条战线推进最后 RRF 作为总指挥汇总战果。评估数据显示这种组合在几乎所有指标上都达到了最佳。性能与成本权衡优点效果最好能最大程度挖掘相关信息。缺点计算成本和延迟最高。需要进行 N 次 LLM 调用生成查询和 2N 次搜索BM25 搜索通常很快向量搜索是主要开销。建议在生产环境中可以根据查询的复杂度、对延迟的要求以及成本预算动态选择策略。例如对于简单查询使用 Hybrid 即可对于复杂、开放的查询则启用完整的 HybridDiverse RAG-Fusion。4.4 参数调优指南生成查询数量 (num_queries)通常 3-5 个为宜。太少可能覆盖不全太多则增加成本且可能引入噪声。可以从 3 开始根据评估指标调整。RRF 平滑常数 (k)默认值 60 在多数情况下工作良好。理论上k值越小排名靠前的文档权重越大k值越大排名的影响越平滑。除非有充分理由否则不建议修改。每次搜索返回文档数 (top_k_per_query)每个查询变体检索的文档数量。不宜过小否则可能漏掉一些只在某个角度排名中等的关键文档。一般设置为最终希望返回文档数的 2-3 倍。例如最终要返回10篇文档每个查询可以检索20-30篇。向量检索的相似度阈值可以在向量搜索时设置一个最小相似度分数阈值过滤掉完全不相关的文档减少融合时的噪声。但这个阈值需要根据嵌入模型和数据集进行校准。5. 常见问题、排查技巧与扩展方向在实际部署和测试 RAG-Fusion 时你可能会遇到以下典型问题。5.1 查询生成质量不稳定问题LLM 生成的查询变体有时偏离原意或过于重复。排查与解决检查提示词确保提示词清晰、明确地要求“多样化”。可以加入少样本示例Few-shot给模型提供明确的生成范式。调整温度参数在调用 LLM 生成查询时适当提高temperature如设为 0.7-0.9可以增加创造性但过高会导致无关输出。需要平衡。后处理过滤对生成的查询进行简单后处理例如计算生成查询与原始查询的嵌入相似度过滤掉相似度过高几乎重复或过低可能偏离主题的查询。备用方案如果生成质量始终不佳可以考虑使用规则模板生成查询变体如添加前缀“概述一下”、“详细解释”、“对比分析”等虽然灵活性不如 LLM但稳定可控。5.2 检索结果重复或冗余问题最终融合后的列表中存在内容高度相似的文档。解决RRF 本身不处理内容去重。需要在融合后增加一个去重步骤。基于嵌入的去重计算最终 Top N 篇文档两两之间的余弦相似度如果超过阈值如 0.9则移除分数较低的一篇。基于摘要的去重使用更轻量的模型如 Sentence-BERT生成文档摘要或关键句向量进行相似度比较和去重。在 RRF 前去重更激进的做法是在每个查询的检索结果中进行去重但这可能影响 RRF 的“投票”机制需谨慎。5.3 处理超长上下文与文档分块问题原始文档很长直接嵌入会丢失细节且不利于精准定位答案。解决这是 RAG 系统的通用问题在 RAG-Fusion 中同样重要。智能分块不要简单按固定字数分割。优先按段落、标题等自然边界分块。对于长文档可以考虑使用滑动窗口Overlap分块避免答案被切断。多层次检索可以先在“文档标题/摘要”级别进行 RAG-Fusion 检索定位相关文档再在相关文档内部进行更细粒度的“段落级”检索。元数据过滤在搜索时可以利用 ChromaDB 的元数据过滤功能先限定文档类别、时间范围等提升检索效率。5.4 性能优化与成本控制问题完整的 HybridDiverse 流程延迟高、API 调用成本高。优化策略缓存对常见的查询及其生成的查询变体进行缓存。可以使用 Redis 或内存缓存键为原始查询的哈希值。异步并行确保多个查询的向量搜索是异步并行执行的而不是串行这能大幅减少 I/O 等待时间。降级策略实施熔断降级机制。例如当 LLM 服务超时或失败时自动降级到 Hybrid 搜索或纯 BM25 搜索。本地模型对于查询生成步骤可以考虑使用量化后的、参数较小的本地 LLM如 Llama 3.1 8B, Qwen2.5 7B虽然生成质量可能略有下降但能彻底消除 API 成本并降低延迟。5.5 评估与迭代问题如何在自己的数据集上评估和选择策略建议流程构建测试集从你的真实用户查询中采样 100-200 条并人工标注每条查询的相关文档最好有分级相关度。运行基准测试在你的数据集上使用evaluate.py的框架对比 BM25、向量搜索基线、Hybrid、RAG-Fusion 等策略。分析指标关注与你的业务最相关的指标。如果是问答系统MRR 和 NDCG5 可能很重要如果是文献调研系统Recall20 可能更关键。A/B 测试在线 A/B 测试是最终检验标准。将效果最好的策略部署到小流量观察用户点击率、满意度和下游任务如答案生成质量的指标变化。RAG-Fusion 为我们打开了一扇门让我们意识到检索不是简单的“一问一答”而是一个可以精心设计和优化的过程。它通过模拟人类的多角度思考并结合简单却强大的融合算法实实在在地提升了信息获取的深度和广度。将这个思路与你现有的 RAG 系统结合很可能就是解锁下一阶段性能提升的关键。