☰
RAG 技术演进:从朴素检索到多路召回与重排序的工程实践
2026/10/22 5:30:36 网站建设 项目流程

1. RAG 技术演进的中场战事:从“能用”到“好用”的分水岭

如果你在过去一年里搭过任何一个基于大语言模型的应用,大概率绕不开 RAG 这个词。检索增强生成,Retrieval-Augmented Generation,说白了就是给模型外挂一个知识库,让它回答问题时先去查资料,再组织语言输出。这个思路最早在 2020 年前后被系统性地提出,但真正让它从论文走进工程实践、从实验室走向生产环境的,是最近这一波大模型落地浪潮。

我接触 RAG 的时间不算晚,从最朴素的“向量检索 + 拼接 Prompt”开始,到后面折腾混合检索、重排序、查询改写、多路召回,踩过的坑可以说能写满一个笔记本。这个系列我打算分上中下三篇来聊,上篇讲 RAG 的基本盘和早期方案,中篇也就是这篇,重点聊 RAG 技术演进过程中那些真正让效果产生质变的关键节点——也就是从“能跑通”到“跑得好”之间的那段路。下篇再展开讲工程化和评估体系。

这篇适合谁看?如果你已经搭过一个能跑但效果时好时坏的 RAG 系统,或者你正准备从零开始设计一个 RAG 架构但不确定该在哪些环节下功夫,那这篇的内容应该能帮你少走不少弯路。我不会只告诉你“用什么”,更会解释“为什么这么选”以及“什么情况下别这么选”。

2. 朴素 RAG 的天花板到底在哪里

2.1 最原始的 RAG 链路长什么样

先把最基础的链路捋一遍,方便后面做对比。朴素 RAG 的流程大致是:用户输入一个问题,系统把这个问题通过一个 Embedding 模型转成向量,然后在一个预先构建好的向量数据库里做相似度检索,取回 Top-K 个最相似的文本块,把这些文本块拼接到 Prompt 里,连同原始问题一起丢给大语言模型,模型基于这些上下文生成答案。

这条链路里涉及几个关键决策点:文本怎么切块、用什么 Embedding 模型、向量数据库选哪个、Top-K 取多少、Prompt 怎么组织。每一个决策点都会影响最终效果,但在朴素 RAG 阶段,大多数人不会想太多,直接用默认配置跑起来再说。

我最早搭的那套系统,用的是固定长度切块,每块 512 个 token,重叠 50 个 token,Embedding 用的是当时比较流行的通用模型,向量库选了一个轻量级的本地方案,Top-K 设成 5。跑起来确实能用,问一些简单的事实性问题,比如“某个产品的保修期是多久”,只要知识库里确实有这条信息,基本能答对。

2.2 三个典型的失效场景

但很快问题就暴露出来了。我把常见的失效情况归纳成三类,这三类问题在朴素 RAG 架构下几乎无解。

第一类是检索不到。用户问的问题和知识库里的表述方式差异很大,比如用户问“这个东西坏了怎么修”,知识库里写的是“故障排除指南”,字面上几乎没有重叠,向量相似度自然不高,检索结果里根本没有相关内容。这不是模型的问题,是检索环节就断了。

第二类是检索到了但没用。比如用户问一个需要综合多个文档才能回答的问题,系统只召回了其中一两个片段,模型基于不完整的信息给出了一个看似合理但实际片面的答案。或者召回的片段里包含大量无关内容,把真正有用的信息淹没了。

第三类是检索对了但模型没用好。有时候检索结果明明包含了正确答案,但模型在生成时忽略了关键信息,或者被上下文中的干扰信息带偏了。这种情况在上下文较长、信息密度较低时尤其明显。

这三类问题分别对应了 RAG 链路的三个环节:检索、召回、生成。后续的技术演进,本质上都是在针对性地解决这三类问题。

2.3 为什么单纯加大 Top-K 解决不了问题

很多人遇到检索效果不好的第一反应是把 Top-K 调大,从 5 调到 10 甚至 20。我试过,效果提升非常有限,甚至有时候反而变差。

原因很简单:向量检索返回的是按相似度排序的结果,Top-K 增大意味着引入了更多低相似度的内容。这些低质量内容不仅占用了宝贵的上下文窗口,还会对模型的判断产生干扰。模型在面对一堆相关度参差不齐的文本时,很容易被那些“看起来相关但实际上无关”的内容误导。

更关键的是,上下文窗口是有限的。你把 Top-K 从 5 调到 20,上下文长度可能直接翻倍,模型处理长上下文时的注意力分配会变得分散,真正关键的信息反而被稀释了。所以问题的核心不是“召回更多”,而是“召回更准”。

3. 查询侧优化:让问题更好地被理解

3.1 查询改写为什么有效

查询侧优化的核心思路是:用户输入的问题往往不是最适合检索的形式。口语化的表达、省略的上下文、模糊的指代,这些都会导致检索效果打折扣。查询改写就是在检索之前,先把用户的问题“翻译”成更适合检索的形式。

我最早尝试的是最简单的同义词扩展,比如把“修”扩展成“维修、修理、故障排除”。效果有一些,但不够稳定。后来开始用大语言模型来做查询改写,让模型把用户问题改写成多个不同角度的查询语句,然后分别检索、合并结果。这个思路叫多查询检索,实测下来对召回率的提升非常明显。

具体操作上,我会让模型生成 3 到 5 个改写版本,每个版本侧重不同的表达方式或信息维度。比如用户问“这个功能怎么用”,模型可能会改写成“功能使用步骤”、“功能操作指南”、“功能配置方法”等。然后这些改写版本分别去检索,最后把结果去重合并。

注意:查询改写不是越多越好。我试过生成 10 个改写版本,结果引入了大量噪声,而且检索耗时明显增加。3 到 5 个是比较平衡的选择,具体数量可以根据你的知识库规模和查询延迟要求来调整。

3.2 HyDE 思路的实操细节

HyDE 是 Hypothetical Document Embeddings 的缩写,思路很有意思:与其用问题去检索文档,不如先让模型根据问题“编造”一个假设性的答案,然后用这个假设答案去检索。背后的逻辑是,假设答案和真实文档在表述风格和内容结构上更接近,因此向量相似度更高。

我一开始觉得这个方法有点反直觉——用一个可能不准确的答案去检索,能靠谱吗?实测下来,在特定场景下确实有效。尤其是当用户问题非常简短、信息量很少的时候,HyDE 能显著提升召回质量。

但 HyDE 也有明显的代价:每次查询都需要额外调用一次大语言模型来生成假设答案,延迟增加明显。而且如果模型生成的假设答案偏离太远,反而会引入错误的检索方向。我的经验是,HyDE 适合用在对延迟不敏感、但对召回质量要求很高的场景,比如法律文书检索、技术文档问答等。

3.3 查询分解:复杂问题的拆解策略

有些用户问题本身就很复杂,包含多个子问题或者多层逻辑关系。比如“对比 A 方案和 B 方案在成本和性能上的差异”,这个问题实际上包含了四个子问题:A 的成本、A 的性能、B 的成本、B 的性能。如果直接拿整个问题去检索,很可能只能召回部分相关信息。

查询分解的做法是先用模型把复杂问题拆解成多个简单子问题,分别检索后再综合生成答案。这个思路在处理多跳推理问题时特别有用。我做过一个测试,对于需要综合三个以上文档才能回答的问题,查询分解能把准确率提升 30% 以上。

实操中需要注意的是,分解后的子问题要尽量保持语义独立,避免相互依赖。如果子问题之间有依赖关系,比如第二个子问题的答案需要基于第一个子问题的结果,那就需要串行处理,延迟会成倍增加。

4. 检索侧优化:从单一向量到多路召回

4.1 向量检索的固有局限

向量检索的本质是把文本映射到一个高维空间,然后在这个空间里找距离最近的邻居。这个思路很优雅,但有几个固有局限。

首先是语义漂移问题。Embedding 模型在训练时学到的语义关系,和你的业务场景中的语义关系可能不完全一致。比如在通用语料中,“苹果”和“水果”的向量距离很近,但在你的业务场景中,“苹果”可能指的是某个产品型号,和“水果”毫无关系。这种领域差异会导致检索偏差。

其次是精确匹配能力弱。向量检索擅长捕捉语义相似性,但对精确的关键词匹配反而不如传统的关键词检索。比如用户搜一个特定的错误码“ERR-4032”,向量检索可能会返回一堆语义相似但错误码不同的文档,而关键词检索能精确命中。

最后是多义词问题。同一个词在不同语境下含义不同,但 Embedding 模型通常只能给出一个折中的向量表示,导致检索时无法区分不同含义。

4.2 混合检索的工程实现

混合检索的思路是把向量检索和关键词检索结合起来,取长补短。关键词检索负责精确匹配,向量检索负责语义匹配,两路结果合并后做重排序。

关键词检索我一般用 BM25 算法,这是信息检索领域的经典算法,实现成熟、效果稳定。BM25 的核心思想是:一个词在文档中出现频率越高、在整个语料库中出现频率越低,这个词对文档的区分度就越大。这个思路和 TF-IDF 一脉相承,但在长文档处理和词频饱和方面做了优化。

两路检索的结果合并时,需要解决一个关键问题:分数怎么归一化。向量检索返回的是余弦相似度,范围通常在 0 到 1 之间;BM25 返回的是相关性分数,范围不固定。直接相加没有意义,需要先做归一化处理。我通常用 Min-Max 归一化把两路分数都映射到 0 到 1 之间,然后加权求和。权重比例可以根据实际效果调整,我一般从 0.5 比 0.5 开始试,然后根据评估结果微调。

4.3 重排序模型的价值与选型

混合检索解决了召回率的问题,但返回的结果仍然是一个较大的候选集,可能有三五十条。这些结果的相关度参差不齐,直接送给模型会引入噪声。重排序的作用就是对候选集做精排,把最相关的几条排到最前面。

重排序模型通常是一个交叉编码器,它把查询和文档一起输入模型,直接输出相关性分数。和向量检索的双编码器相比,交叉编码器的计算量大得多,但精度也高得多。所以典型的做法是:先用双编码器做粗筛,召回几十条候选,再用交叉编码器做精排,选出最相关的几条。

重排序模型的选型上,我试过几种不同的方案。有些是通用领域的,有些是专门针对检索任务优化的。我的经验是,如果你的业务场景比较垂直,最好找在类似领域数据上微调过的重排序模型,效果会比通用模型好不少。如果没有现成的,用通用模型也能用,但可能需要自己准备一些标注数据做微调。

实操心得:重排序的候选集大小需要权衡。候选集太小,精排的价值发挥不出来;候选集太大,延迟增加明显。我一般把候选集控制在 20 到 50 条之间,具体根据延迟预算和效果要求来定。

5. 生成侧优化:让模型更好地利用上下文

5.1 上下文压缩的必要性

检索回来的文档块往往包含大量冗余信息。一个 512 token 的文本块里,可能只有一两句话是真正相关的。这些冗余信息不仅浪费上下文窗口,还会干扰模型的判断。

上下文压缩的思路是在把文档送给模型之前,先做一轮筛选和精简。具体做法有几种:一种是抽取式压缩,直接从原文中抽出最相关的句子;另一种是生成式压缩,让模型根据查询重新组织文档内容,只保留相关信息。

我两种都试过。抽取式压缩的好处是信息保真度高,不会引入模型幻觉,但缺点是可能抽取出不完整的句子,导致语义断裂。生成式压缩的好处是输出更流畅、更聚焦,但代价是可能引入模型幻觉,而且需要额外调用模型,延迟增加。

我的选择是:对准确性要求极高的场景用抽取式,对流畅度要求高的场景用生成式。还有一种折中方案是先用抽取式做粗筛,再用生成式做精炼,但链路会变长,需要根据实际情况权衡。

5.2 上下文排序的隐藏价值

一个容易被忽视的细节是:检索回来的文档块送给模型时,排列顺序会影响生成效果。大语言模型在处理长上下文时,存在“中间遗忘”现象——对上下文开头和结尾的信息记得比较牢,中间部分容易被忽略。

所以一个实用的技巧是:把最相关的文档块放在上下文的开头和结尾,把次相关的放在中间。这个调整不需要任何额外计算,但对生成质量的提升是实打实的。我在多个项目里验证过这个技巧,尤其是在上下文较长、文档块较多的时候,效果差异非常明显。

另一个相关技巧是给每个文档块加上来源标注和序号,让模型在生成答案时能够引用来源。这不仅提升了答案的可信度,也方便后续做溯源和验证。

5.3 提示词设计的几个关键原则

生成侧的提示词设计,核心目标是让模型“基于给定上下文回答,不要自己编”。听起来简单,但实际操作中模型很容易忽略上下文,直接用自己的知识回答。

我总结了几条原则。第一,明确指令要放在上下文之后,而不是之前。如果把指令放在最前面,模型在处理完长上下文后可能已经“忘记”了指令。第二,要明确告诉模型“如果上下文中没有相关信息,就回答不知道”,而不是让它自由发挥。第三,可以要求模型在回答中标注信息来源,比如“根据文档 1 的内容”,这样既能提升可信度,也能倒逼模型认真阅读上下文。

还有一个细节是 Few-shot 示例的设计。如果能在提示词里给出一两个“基于上下文回答”的示例,模型的表现会明显更好。示例不需要多,一两个就够,但一定要选得典型,能覆盖你期望的回答风格和格式。

6. 常见问题与排查技巧实录

6.1 检索结果不相关怎么排查

这是最常见的问题。排查思路是从链路末端往前推。先看检索返回的原始结果,确认是检索环节的问题还是后续环节的问题。如果检索结果本身就不相关,那问题出在检索侧;如果检索结果相关但最终答案不对,那问题出在生成侧。

检索侧的问题又可以细分为几种:Embedding 模型不适合你的领域、文本切块方式不合理、查询表述和文档表述差异太大。我一般的排查顺序是:先换一个 Embedding 模型试试,如果效果有提升,说明是模型适配问题;如果没变化,再检查切块策略,看看是不是切得太碎或太大;最后再考虑查询改写。

6.2 答案不准确或胡编怎么处理

模型胡编通常有两个原因:要么是上下文里确实没有相关信息,模型只能自己编;要么是上下文里有相关信息,但模型没找到或者没用好。

第一种情况,需要在提示词里明确要求模型“不知道就说不知道”,并且在检索侧设置一个相似度阈值,低于阈值的结果直接丢弃,不要送给模型。第二种情况,可以尝试调整上下文排序、增加重排序环节、或者用上下文压缩来减少噪声。

还有一个容易被忽视的点是温度参数。温度设得太高,模型更容易自由发挥,胡编的概率也更大。在 RAG 场景下,我一般把温度设得很低,甚至直接设成 0,让模型尽量做确定性的输出。

6.3 响应延迟过高怎么优化

RAG 链路的延迟主要来自几个环节:查询改写、多路检索、重排序、生成。每个环节都需要时间,叠加起来可能达到好几秒。

优化的思路是分优先级处理。如果延迟预算紧张,可以先砍掉查询改写和重排序,只保留基本的向量检索和生成。如果还有余量,再逐步加回这些环节。另一个思路是并行化,比如多路检索可以并行执行,查询改写的多个版本也可以并行检索。

还有一个工程上的技巧是缓存。对于高频查询,可以把检索结果缓存起来,下次相同或相似查询直接命中缓存,省去检索时间。缓存的粒度可以是查询级别,也可以是文档块级别,具体看你的查询分布特征。

问题类型典型表现排查方向解决手段
检索不到返回结果与问题无关Embedding 模型、切块策略换模型、调切块、加查询改写
检索到了但没用结果相关但答案片面召回数量、重排序加混合检索、加重排序
模型没用好上下文有答案但生成错误提示词、上下文排序调提示词、调排序、降温度
延迟过高响应时间超过预期链路各环节耗时砍环节、并行化、加缓存

6.4 几个容易踩的坑

第一个坑是过度依赖默认配置。很多向量数据库和 Embedding 模型都有默认参数,但这些默认值未必适合你的场景。比如默认的相似度度量方式可能是余弦相似度,但在某些场景下内积或欧氏距离可能更合适。这些细节需要根据实际数据做验证。

第二个坑是忽视数据质量。RAG 的效果上限很大程度上取决于知识库的质量。如果原始文档本身就有大量重复、过时或错误信息,再好的检索和生成策略也救不回来。我一般会在构建知识库之前先做一轮数据清洗,去重、去噪、更新过时内容。

第三个坑是不做评估。很多人搭完 RAG 系统后,凭感觉判断效果好坏,没有系统的评估机制。这样很难发现问题的根源,也无法量化优化效果。我建议至少建一个小的评估集,包含几十个典型问题和标准答案,每次调整后都跑一遍评估,用数据说话。

7. 一些个人体会

RAG 这个领域变化很快,每隔几个月就有新的思路和方案出来。但我在实际项目中最大的体会是:没有银弹,只有权衡。查询改写能提升召回,但增加延迟;混合检索能提升精度,但增加复杂度;重排序能提升相关性,但增加计算成本。每一个优化手段都有代价,关键是根据你的业务场景找到那个平衡点。

另一个体会是,评估比优化更重要。我见过太多团队花大量时间调各种参数,但连一个像样的评估集都没有,调来调去也不知道到底有没有变好。先把评估做扎实,再谈优化,效率会高很多。

最后一个建议是,不要一开始就追求大而全的架构。先把最基础的链路跑通,确认核心流程没问题,然后再逐步叠加优化手段。每加一个环节,都验证一下它是否真的带来了提升。这样既能控制复杂度,也能确保每一步优化都是有的放矢。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询