1. 为什么普通RAG扛不住复杂问题:先认清瓶颈在哪
过去半年我一直在和检索增强生成(RAG)系统打交道,最有感触的一点是:普通RAG上线容易,做好很难。很多人以为把文档切块丢进向量库,检索之后拼到Prompt里就完事了,结果一问到“去年Q3各区域销售漏洞对比”这类需要多文档综合的问题,答案就开始东拼西凑,甚至直接幻觉。本文要拆解的,正是业界在朴素RAG基础上沉淀出来的7种高级RAG架构,每种架构对应哪一类问题、原理是什么、实现时要注意什么,我会尽量按实际使用的经验来讲,而不是给一堆名词解释。这篇内容适合正在做RAG项目、已经跑通Demo但效果不理想的AI开发者,从头搭管线的人也能当索引清单来用。
1.1 朴素RAG的四个典型失败场景
先想一个最简单的RAG流程:把文档切成几百个Token的块,做Embedding存进向量库;用户提问时把问题Embedding后召回Top-5块;最后把这些块塞进Prompt让大模型生成答案。这套流程跑Demo很好,一旦放到真实业务里,问题就暴露得很快。
我做过一个某公司内部文档问答项目,场景是让员工直接查制度、报销、IT流程。朴素RAG上线后,统计下来主要有四类失败:
召回不全。用户问“如果出差回来发票弄丢了怎么报销”,Embedding检索只匹配到“报销流程”相关的片段,但真正讲“发票丢失处理”的那一段因为关键词和问题差异太大,排在了第12位,根本没进Top-5,于是模型只能照着别的流程硬编答案。
噪声过多。查询命中了一个包含“报销”但主要内容是别的流程的大段落,前缀部分与问题无关,大模型被带偏,回答里混入了别部门的流程细节。你会发现,Top-5里真正有用的可能只有一两个,其余全是干扰项。
粒度失衡。整页文档切块后,有的块太小缺上下文,比如只切到一句“请参照附件”,没有附件描述;有的块太大涵盖多个主题,检索出来的块既不聚焦也不完整。粒度一旦失衡,召回准了也没用,因为拿到的内容本身就残缺。
多跳问题。有些问题需要把两三份文档的信息串起来,比如“某项目的合同期和付款条件是否匹配”,单次检索根本找不到同时包含合同期和付款条件的片段,模型只能猜。这四类问题,就是后面七种架构要逐一处理的对象。
1.2 高级RAG的分层拆解法
既然瓶颈分散在查询、索引、检索、生成四个环节,那解法也应该分层对应。我把这套思路总结成一个简单框架,后面讲每种架构时都会往这个框架里放:
查询侧想办法改进用户的原始查询,生成多个变体、把查询转成假答案,让检索器更容易命中目标,多查询检索、HyDE、RAG-Fusion的查询改写部分都属这一类。
索引侧调整切块策略和索引结构,同时兼顾召回粒度和上下文完整性,父子文档切块是这里最典型的手段。
检索后处理,检索完不直接送生成器,先做重排序和上下文压缩,把Top-20里的东西筛成Top-5、压缩成精华片段,这一步直接决定最终答案质量的上限。
生成侧让模型或一个额外评估器检查检索质量,判断“该不该用这些文档”,必要时触发纠错或知识精炼,这就是自省式与纠正式RAG做的事。
系统侧把单个检索动作升级成多步决策过程,让模型自己决定查什么、换什么查询、要不要再查一轮,这就是Agentic RAG的范畴。
这样拆解之后,你再看网上一堆RAG名词就不会觉得零散了。它们本质上都是针对上述某几个环节的定向补丁,彼此之间不是竞争关系,而是可以叠加的关系。下一章我把七种架构按这个分层逻辑一个个拆开。
2. 七种高级RAG架构逐一拆解:原理、位置与实现要点
这七种架构并不互斥,实际项目里经常两三种叠着用。为了理解方便,我按“查询侧 → 索引侧 → 检索后处理 → 生成侧 → 系统侧”的顺序来拆,这样最符合问题出现的先后逻辑。
2.1 多查询检索:用多个问题视角弥补单个查询的盲区
多查询检索(Multi-Query Retrieval)的原理一句话就能说清:让大模型把用户问题改写成多个不同的查询,分别检索后再合并去重。它解决的是用户提问和文档语言表达之间的语义鸿沟。用户不一定知道知识库里的关键词,比如他问“报销单据弄丢了”,文档里写的是“原始凭证遗失处理”,两种说法在向量空间里未必足够接近,但大模型知道它们是同一件事。让大模型基于原问题生成3到5个变体查询,比如“原始凭证遗失怎么办”“发票丢失报销流程”“报销票据遗失说明”,就等于用不同词面把同一意图打了一遍,召回率自然上来了。
实现时我建议变体数量控制在3到5条。太多会让成本翻倍,而且会出现大量重复结果;太少又起不到扩充召回的作用。我常用的一种做法是让大模型输出JSON列表,逐条解析,过滤掉与原问题语义完全重复的变体,再做一次简单的去重,最后合并各路召回结果。多查询检索最直接的效果是召回率明显上涨,副作用是后续重排序的压力变大,因为它会把更多相关结果捞上来,也会把更多无关结果带上来,所以它几乎一定要和重排配合使用。
2.2 HyDE:先写一个“假设答案”再拿它去检索
Hypothetical Document Embeddings(HyDE,假设文档嵌入)的核心动作是:先用大模型生成一个基于问题的假设性回答文档,再用这个假答案的Embedding代替问题去检索。
为什么这样能有效?Embedding模型把问题和文档映射到向量空间时,问题通常很短、很口语化,而文档很长、很正式,两者在向量空间里天然有一段距离。HyDE相当于把问题先翻译成“文档语言”,生成一个150到200词的假设回答,用假回答去和文档做相似度匹配,匹配到的通常就是真正包含相关内容的事实证据。这个思路在短查询场景里经常比直接Embedding问题效果更好,因为假回答里包含了文档里可能出现的实词和句式。
这里有个常见误区:HyDE不是让模型“猜答案”,而是让它“列出答案里可能包含的主题、实体和上下文”。我实际跑下来的体会是,Prompt里明确写“不要给出具体事实结论,只描述一份包含该问题答案的文档可能长什么样”,比直接要它给答案稳定得多。HyDE的缺点是成本高,每次检索前都要多一次大模型调用,而且生成结果不稳定,所以我会把它当作多查询检索之外的补充方案,而不是完全替代。
2.3 RAG-Fusion:把多路结果用排名融合变成一路
RAG-Fusion解决的是“单一检索器信号不可靠”的问题。向量检索擅长语义相近,但可能漏掉精确关键词;关键词检索命中准确,却对同义词、改写毫无办法。两边都有道理,那就融合。
具体做法是多路并行检索:一路向量检索,一路关键词检索,甚至把多查询检索的每一个变体都算一路。每路各自返回一个排序列表,然后用RRF(Reciprocal Rank Fusion,倒数排名融合)把多个列表合并成一个最终序列。RRF公式很简单:一个文档的融合得分是它在每个列表中排名的倒数再求和,通常写作 score(d) = Σ 1/(k + rank_i(d)),其中k取经验值60。
这个公式最大的优点在于它只关心排名,不关心不同检索器给出的原始相似度分数。向量检索打出的0.82和关键词检索打出的15.4根本没有可比性,但排名是可以比的。我在某个知识库项目里用“向量+关键词”双路融合后,召回质量明显比单路好,尤其是在专有名词、编号、型号这类场景里,关键词那一路经常能给出意外惊喜。RAG-Fusion的代价是维护多路检索器,每次查询的延迟也会上升,但这个代价通常值得。
2.4 父子文档切块:检索要小的、喂给模型要大的
父子文档切块(Parent-Child Chunking)是我强烈建议做的一步,它解决的是切块粒度的矛盾。父块里面放整篇文档或一个较长的章节,直接作为生成阶段的内容;子块则是从父块继续拆分出的小片段,用于Embedding和向量检索。原理很简单:小片段语义更聚焦,容易命中;大片段上下文更完整,送进Prompt不容易断章取义。
实操上我常用子块128到200个Token、父块512到1024个Token,并在父块开头保留原有标题。检索命中的是子块,但返回给模型的是子块对应的父块。注意这里要求索引阶段就建立子块ID到父块ID的映射关系,检索之后要把父块内容取出来替换,而不是把子块直接丢给模型。这么做的代价是索引里的块数量变多了,因为同一个父块会被拆成多个子块并各自生成Embedding,存储和写入成本上升。另外,如果父块跨了多个主题,比如一整节“费用报销”包含差旅费、招待费、交通费,父块照样会混入杂质,所以父块也不能无脑大,要尽量跟原文档的章节标题对齐。
2.5 重排序与上下文压缩:把召回来的噪声干掉
把重排序(Re-ranking)和上下文压缩(Contextual Compression)放在一起讲,是因为它们在管线里常常连续出现,目标都是清理召回结果的质量。
重排解决的是“Top-5不等于最相关的5个”的问题。向量检索粗召回一批候选,通常是Top-20或Top-50,然后把(query, doc)对交给一个交叉编码器重新打分,最后按新分数取Top-5。交叉编码器能同时看到问题和文档的完整关系,比双塔式的Embedding判断更准,但计算量也更大,所以不能替代第一轮粗召回。
上下文压缩则是对进入Prompt的文档做一次“瘦身”。常见做法是把候选文档切句子,只保留与问题最相关的那几句,或者让大模型抽取关键段落。原因很现实:向量检索返回的片段里经常混着大段无关上下文,直接把整段送进Prompt,大模型会“有样学样”地引用这些无关信息,产生幻觉和跑题。压缩之后,Prompt更短、更聚焦,答案质量通常会提升一截。
成本方面要注意,重排序器的输入长度通常有限制,一次只能处理不超过几百个Token的文本,所以它更适合做第二轮精排。正确顺序是:粗召回 → 上下文压缩 → 重排 → 送生成器。
2.6 自省式RAG与纠正式RAG:给检索结果加一层质检
自省式RAG(Self-RAG)和纠正式RAG(Corrective RAG,CRAG)出场时机一样:在“检索结果已经准备好”和“生成答案之前”之间插入一个质量检查环节。它们的目标很明确,就是减少“模型拿错误文档硬答”的情况。
Self-RAG的思路是让大模型在推理时生成反思标记,判断检索文档是否相关、是否足以回答、是否被引用支持,从而决定是用检索结果还是直接用内部知识。实现难度稍高,因为它通常需要对模型输出格式做改造;在工程上也可以退化成Prompt里显式要求模型“先检查文档相关性再回答”。CRAG则更工程化:单独训练一个轻量评估器,先给检索结果打一个“质量分”,分数高走正常生成;分数低,说明这一轮检索大概率不靠谱,就触发纠错——重写查询、换一个检索源、查外部权威来源,实在不行就明确告诉用户“没有找到相关信息”。之后还会有一个“知识精炼”步骤,从原始检索结果里提取只与问题相关的内容,拼接到生成Prompt中。
我测试过CRAG风格的方法,最直接的变化是“无引用幻觉”明显下降。以前模型会把我给它的文档里的细节和它自己编的细节混在一起,加了这个质检环节之后,它要么回答有出处,要么直接承认没找到,不会强行编造。
2.7 Agentic RAG:从检索增强到智能体决策
最后这个是我认为最接近未来方向的架构。Agentic RAG的核心变化是:检索不再是一个固定管线里的步骤,而是由大模型主导的一个决策循环。
典型实现有三种形态。Router型:智能体先判断问题类型,决定走向量检索、关键词检索还是调用外部工具,再决定要不要用检索结果。多步检索型:第一轮结果不够,智能体改写查询再检索一轮,直到它认为自己拿到足够信息。多智能体型:拆成检索Agent、审查Agent、写作Agent,各司其职,协作完成。第三种已经不只是“检索增强生成”,更接近一个完整的Agent系统。
代价是复杂度和成本都会明显上升,所以必须有严格的控制:限制最大轮数、记录已经查过的查询、对低分结果强制改写查询,否则很容易出现同一个查询反复检索的空转。但如果你要做跨多个来源、需要多轮推理的复杂问答,Agentic RAG带来的效果提升是固定管线给不了的。
3. 七种架构怎么选:先看痛点再看成本
没有一种架构能解决所有问题,选型应该先明确“当前最主要的失败场景是什么”。我把七种架构的核心信息放在表格里,方便对照。
| 架构 | 主要解决的问题 | 典型适用场景 | 主要代价 |
|---|---|---|---|
| 多查询检索 | 查询表述与文档语言不匹配 | 用户提问口语化、关键词变化多 | 检索次数与Token成本增加 |
| HyDE | 短查询与长文档的语义鸿沟 | 问题短、文档正式、术语多 | 多一次LLM生成,结果不稳定 |
| RAG-Fusion | 单一检索器信号不可靠 | 含编号、型号、专有名词的混合场景 | 需要维护多路检索器,融合参数要调 |
| 父子文档切块 | 切块粒度不平衡 | 长文、章节结构清晰的资料库 | 索引量和存储开销上升 |
| 重排序+上下文压缩 | 粗召回噪声过多 | 所有候选多、Prompt长度受限的场景 | 增加推理时延与额外模型调用 |
| 自省/纠正式RAG | 检索结果不可信 | 对准确性有强要求的客服、合规场景 | 需要额外评估器,阈值要调 |
| Agentic RAG | 固定流程无法多步推理 | 跨文档、多轮检索的复杂问题 | 延迟高、易循环、维护复杂 |
表格只能覆盖一部分,实际上选型还有两个很实际的角度。
第一个角度是成本预算。如果你的系统对延迟和Token消耗非常敏感,那就优先做索引侧和查询侧的低成本组合,也就是多查询检索、RAG-Fusion、父子文档切块,这些不会增加太多生成阶段的开销。重排序和CRAG会显著增加每次查询的成本,Agentic RAG的Token消耗更是成倍增长,所以预算有限时不要一上来就全都上。
第二个角度是问题复杂度。大部分内部知识库问答停留在“单次检索能回答”的层面,用前面六种架构的组合就已经能解决绝大多数问题。只有当你明确看到“需要跨文档综合、需要多轮判断”的需求时,再往Agentic方向走。我个人的建议路线是:先做便宜的组合,多查询、父子切块、融合、重排;如果仍然出现幻觉,再加纠错;如果问题复杂度进一步上升,再加Agentic。
4. 一个能跑的参考管线:把多查询、融合、重排、纠错拼起来
理论讲再多,不如给一条可落地的参考管线。下面这段伪代码表达的是我常用的一条组合管线,它把多查询、RAG-Fusion、重排序和CRAG式纠错串在了一起。
# 伪代码示意:组合管线,方便你迁移到自己的工程里 def generate_answer(question: str): # 1. 查询侧:让大模型生成查询变体 variants = llm.generate_variants(question, n=3) # 2. 检索侧:向量检索 + 关键词检索,多路并行 results = [] for q in variants + [question]: results.append(vector_store.search(q, top_k=20)) results.append(keyword_store.search(q, top_k=20)) # 3. 融合:对多路结果做RRF fused = reciprocal_rank_fusion(results, k=60) # 4. 重排:粗召回候选降到精排5个 candidates = fused[:20] reranked = reranker.rerank(question, candidates)[:5] # 5. 纠错:对精排结果做质量评估 quality = evaluator.score(question, reranked) if quality < low_threshold: # 重写查询后再检一次,仍然没有就诚实回答 reranked = retrieve_with_rewritten_query(question) if not reranked: return no_support_answer(question) # 6. 压缩与生成 compressed = compress_for_prompt(question, reranked) return llm.generate(question, compressed)这个管线里的每一步都可以单独摘出来用。如果你今天的场景只是“召回率不够”,那从第2、3步开始就够了;如果问题是“Top-5里有噪声”,那就加第4步重排;如果问题变成“模型总在没根据的时候硬答”,第5步纠错才是关键。
我把第5步放在重排之后,是因为重排质量高的时候,评估器对相关性的判断会更准确。如果先评估一个Top-20的粗糙结果,很多相关文档会被无关文档稀释,质量分反而不可信。另外,第6步的压缩放在重排之后,可以减少送入生成器的Token量,也避免重排器处理超长文本。
这个管线的吞吐量并不适合在每轮对话里都全量跑。我实际使用时会加两个开关:简单问题只走第1、2、3、4、6步,跳过纠错;只有高价值或高风险的查询才全量走到第5步。工程上要允许自己“按需取舍”,而不是把每一条查询都压到最重配置。
5. 把这几种架构跑进生产后,我踩过的七个坑
每一章配方都很美好,但真正落地时,坑几乎全在细节里。我把踩过的坑按环节整理出来,希望你不用再走一遍我的弯路。
5.1 查询侧与索引侧的坑
第一个坑出现在多查询检索的Token消耗上。我最早让大模型生成5个查询变体,结果其中3个几乎一样,检索出的片段也高度重复,最后Prompt里全是重复内容,有效信息反而少了。后来加了“语义去重”,先对查询变体做相似度比较,重复的只保留一个,再限制变体数量为3个,成本降下来,召回质量没有降。这个操作看着简单,省掉的Token量却非常可观。
第二个坑是HyDE生成的假答案和文档风马牛不相及。我遇过一次很典型的案例,问题问的是离职交接流程,HyDE生成的假设文档却把入职、转岗、社保全写进去了,结果检索回来一堆完全不相关的块。原因是我给了模型太多发挥空间。修正办法是:把Prompt从“生成一段假设答案”改成“列出该问题答案文档可能包含的标题与要点”,并限制输出在100词以内。这个改动之后,召回稳定性好了很多。
第三个坑是父子切块的父块设到了2048个Token。父块大确实意味着上下文完整,但一个父块经常覆盖四五个子主题,检索命中的子块对应父块后,无关内容依旧很多,整块塞进Prompt反而是灾难。我最终把父块调到512到1024个Token,切块时尽量让父块跟原有章节标题对齐,效果比单纯调大小好很多。
5.2 融合与重排的坑
第四个坑是RRF的k值不能随便拍。一开始我用k=10,结果排名靠前的文档把分数垄断了,融合结果跟直接取某一路几乎没区别。改成k=60之后,各路由结果才有合理竞争,局部排名靠后的文档才可能挤进前面。建议在业务数据集上跑一遍小网格搜索,k通常在50到80之间取,不要默认一个值就不动了。
第五个坑是重排序截断把关键信息截丢了。我用的重排器最长只能处理512个Token,而很多文档块超过这个长度。结果文档结尾的关键细节根本没进入重排器,自然排不上去。后来我先把候选文档切成句子,抽出与问题相关的句子再做压缩,压缩后的短文本再进重排器,情况明显改善。这个坑很容易被忽视,因为重排器本身不会告诉你它截断了什么。
5.3 纠错与智能体的坑
第六个坑是CRAG的阈值调太高等于没装。我最初把评估分数阈值设为0.7,跑了一周几乎没触发过一次纠错,因为大多数召回的片段都有一些相关性,分数在0.5到0.7之间晃。后来抽了200条人工标注的问题结果,画出分数分布,把阈值卡在分布低谷0.55附近,纠错才开始正常工作。这个分布必须先画,否则阈值就是个玄学。
第七个坑是Agentic RAG死循环,同一个查询反复查。智能体在拿不到可回答内容时,很容易重新生成一个和原来几乎相同的查询继续检索。我加了两个防御:全局最多5步;维护一个“已检索查询集合”,如果新查询和集合里的某个查询相似度超过0.9,直接判定为重复,强制智能体改写查询。没有这两道防御,Agent在复杂场景里真的会空转到超时。
6. 落地前最后补一句:评测、成本与迭代顺序
这一节我要讲的是容易被技术分享冷落、但真正决定成败的事情。
6.1 没有评测就不该上高级RAG
如果不做评测,你根本分不清多查询检索到底有没有带来收益。我现在的习惯是建一个业务评估集:至少100个问题,按难度和类型分桶,分单跳简单问题、多跳问题、含专有名词问题、明确该回答“未找到”的问题。每个问题记两件事:答案正确度、是否有引用支撑。上线前跑一遍这个评估集记下基线,每加一种架构再跑一遍,看哪个分桶涨、哪个分桶跌、哪个分桶被牺牲。这一步能帮你挡掉大量“感觉变聪明了,实际变差了”的改动。
6.2 成本与延迟能省则省
高级RAG的代价首先是Token和时间。多查询一次查询要多次向量检索,重排要做交叉计算,纠错又多一次大模型调用,Agentic甚至可能要跑好几轮。省成本有几个方向:对高频重复问题做缓存,相同的业务问题直接命中缓存不要重复检索;对简单问题直接跳过重排和纠错;对查询变体数量做动态控制,问题很简单就只用一个查询。延迟方面,重排和压缩是最明显的瓶颈,可以考虑用更轻量的模型做初筛,把真正的重排留给关键场景。
6.3 按这个顺序一步步往上加
我建议的迭代顺序是这样的:第一版只做最基础的优化,换更好的Embedding模型、语义化切块、建立父子文档结构。第二版加多查询检索和RRF融合,这一步对召回率的提升最明显,成本也可控。第三版加重排和上下文压缩,把精排质量提上来。第四版再加纠正式RAG,处理幻觉;最后看业务是否需要多跳推理,再上Agentic RAG。每一步都回到评估集上验证,再决定要不要继续往下走。
最后再说一点我自己的体会:七种架构不是集邮,不要为了“高级”而高级。每种架构都应该对应一个具体的失败场景,加上之前先问自己一句“它治的是哪个病”。我用这套方法把一个客服问答项目从严重跑题推进到了能挡住绝大多数幻觉的程度,真正的体会就是,把评测和迭代节奏踩稳,比任何单一架构都重要。所以,当你准备对现有RAG系统动手时,不要一次性上七个,先找一瓶药,对症下药。