最近这两年只要你接触过 AI 编程大概率听过一个词RAGRetrieval-Augmented Generation。但很多小伙伴对 RAG 都只是一知半解导致面试的时候只能说出 “检索增强生成” 这六个字面试官再多问一点就只能 “阿巴阿巴”。而这篇文章是对 RAG 技术的一个全景科普从最初的 Naive RAG、到现在最主流的 Agentic RAG总共 16 种主流 RAG 方案我会一次性给大家讲清楚。以后开发 AI 应用的过程中无论遇到什么场景都能选择最合适的 RAG 方案。干货比较多建议先点赞收藏再慢慢往下看~什么是 RAGAI 大模型有一些硬伤比如知识有截止日期会一本正经地胡说八道也就是我们常说的幻觉缺乏私有知识了解不到内部的文档写了什么比如问 DeepSeek程序员鱼皮的最新项目是什么结果它给我扯了个两年半以前的项目出来技术栈也完全不对解决这个问题就可以用 RAG。RAG 的核心思想是先搜再答让大模型在回答之前先去搜一遍相关资料再基于搜到的知识来组织答案。就跟考试的时候偷偷翻书一样遇到不会的先翻一翻书再根据书里的知识答题。还是问 AI 同样的问题我们主动给 AI 一些参考资料他的回答就会准确一些这个思路听起来简单但在实际工程上 RAG 已经演化出了很多种不同的实现方法从最初的「切块 → 搜索 → 生成」到让 AI Agent 自主决策检索策略的 Agentic RAG复杂度和能力天差地别。有朋友可能会问现在的大模型不是已经支持百万 token 的上下文窗口了还需要 RAG 吗答案是需要而且用得比以前更多了因为把所有文档塞进上下文窗口既贵又不靠谱。上下文越长 token 费用越高而且大模型普遍存在 “Lost in the Middle” 问题顾名思义就是对超长上下文中间部分的注意力会明显下降。这个也不难理解就像听别人说话一样我们对开头和结尾的印象会相对深刻一些中间的总是容易忘记。不过呢RAG 和长上下文也不是互斥的关系现在一般的最佳实践是先用 RAG 给 AI 提供相对精确的资料再利用长上下文窗口进行有针对性的分析推理两者互补。好背景交代完了。下面我们正式进入主题挨个讲讲每种 RAG 方案。我们先从最基础的 RAG 讲起如果你之前完全没接触过 RAG看完这一节就能理解它的核心原理。主流 RAG 方案标准 RAG 及变体Naive RAGNaive RAG 这个词听起来就很牛对不对但其实Naive 是朴素的意思Naive RAG 是最基本的 RAG 实现方案。假设你有一份 200 页的公司员工手册怎么让 AI 基于里面的内容回答员工的提问呢最简单粗暴的做法就是每次提问都把整本手册塞给 AI 大模型。但是一本手册可能几十万字全塞进去又贵又慢而且上下文一长大模型还容易犯前面提到的 “Lost in the Middle” 的毛病导致回答质量下降。所以更合理的思路是员工问什么我们就只把相关的几段内容塞给 AI。那问题又来了怎么定位到相关的那几段呢如果用关键词匹配很容易出现问题和文档里的关键词不一致的问题比如员工问 “老板不批假怎么办”文档里写的是 “请假审批流程”关键词对不上就搜不到。这就需要用到向量了。所谓向量就是把一段文字用一串数字表示出来让计算机可以比较语义上的相似度。举几个例子感受一下文本向量简化示意我喜欢吃鱼[0.21, 0.85, 0.13, ...]我爱吃海鲜[0.23, 0.82, 0.15, ...]今天天气真好[0.88, 0.12, 0.41, ...]语义越接近的句子它们的向量在数学空间里离得越近。负责把文字转成向量的模型叫 Embedding 模型存储这些向量并支持快速相似度搜索的数据库叫向量数据库比如 Milvus、Chroma、Qdrant 等。理解了向量Naive RAG 的做法就很好理解了主要分为两步。第一步是离线索引把文档切成小块chunk每块几百字用 Embedding 模型把每个小块转成向量把向量和对应原文都存进向量数据库第二步是在线查询问答把用户问题也用 Embedding 模型转成向量去向量库里搜最相似的几个文档块比如 Top 5把这几个块和用户问题拼成 Prompt交给大模型生成回答回到开头那份 200 页的员工手册。我们先把它切成几百个小块并向量化入库当员工问 “年假有多少天” 的时候RAG 的执行流程是这样的系统把问题转成向量在向量库里找到最相似的 5 个文档块比如某块写着 “入职满一年享有 10 天年假满三年 15 天…”把这 5 个块连同问题一起交给大模型大模型回答“入职满一年享有 10 天年假”不过 Naive RAG 也有一些比较明显的问题切块方式粗暴可能把一段完整的语义从中间截断检索质量完全依赖 embedding 模型搜不到就没辙搜到了垃圾文档也不管导致输出错误答案这些局限就是后面所有进阶方法要解决的问题。下面我用伪代码来帮助大家理解不熟悉编程的同学可以跳过不影响后续的学习~1离线索引阶段# 1. 把文档切成小块chunk_size500 表示每块 500 字 # chunk_overlap50 表示相邻块之间重叠 50 字避免关键信息被切断 chunks split_into_chunks(documents, chunk_size500, chunk_overlap50) # 2. 把每个小块转成向量连同原文一起存入向量数据库 for each chunk in chunks: vector embedding_model.encode(chunk) # 调用 Embedding 模型编码 vector_store.insert(vector, chunk) # 向量和原文一起入库2在线查询阶段# 1. 用同一个 Embedding 模型把用户问题也转成向量 query_vector embedding_model.encode(user_query) # 2. 在向量库里搜最相似的 Top 5 文档块 top_k_chunks vector_store.search(query_vector, k5) # 3. 把检索到的文档块拼进 Prompt作为参考资料 prompt 基于以下参考资料回答问题\n join(top_k_chunks) \n问题 user_query # 4. 交给大模型生成最终回答 answer LLM.generate(prompt)Multi-Query RAG用户提问的方式千奇百怪。比如同样是想知道公司报销流程有人会问怎么报销有人会问费用审批流程是什么还有人会问花了钱怎么找公司要回来。但文档里可能只写了 “报销申请流程” 这个表述如果用户的措辞和文档差距太大向量检索就可能搜不到正确的内容。Multi-Query 的思路就是既然一种问法搜不全那就让大模型把原始问题改成多种不同的表述分别去搜最后把结果合并去重。这种方法的代价就是每次提问要多调用一次 LLM 做改写再多跑 N 次向量检索延迟和成本都会增加。而且如果 LLM 改写出的问题方向跑偏会把无关文档也带进来影响答案质量。所以它比较适合面向普通用户的客服、电商等场景用户表述口语化、和文档术语差距大多花这点成本还是有必要的。但是在术语规范的专业领域Multi-Query 的收益就很有限了。Multi-Query RAG 的代码实现如下# 1. 让 LLM 把原问题改写成多个表述 queries LLM.generate(请将以下问题改写成 3 个不同的表述 user_query) // 例如 AI 返回: [报销流程是什么, 费用审批怎么操作, 如何提交报销申请] # 2. 每个表述分别走一次向量检索 all_results [] for each query in queries: results vector_store.search(embed(query), k5) all_results.append(results) # 3. 合并去重得到覆盖面更广的候选文档 merged_chunks deduplicate(all_results) # 4. 把合并后的文档 原始问题交给大模型生成回答 answer LLM.generate(merged_chunks user_query)HyDEAI 回复的效果不好可能不是因为用户的问法不好而是用户的问题和文档的语义空间不一致。用户的提问往往很短比如 “KV Cache 是什么”就这么几个字。但文档里关于 KV Cache 的描述可能是一大段技术解释。一短一长在 embedding 空间中可能离得很远就检索不到了。HyDE 的做法就是让大模型凭空写一个答案不必完全准确然后用这段假答案的向量去检索。因为假答案和真文档的文体更接近两者在向量空间中离得也更近。还是上面的例子用户问“KV Cache 是什么”LLM 先编一段假答案“KV Cache 是一种在 Transformer 推理过程中缓存 Key 和 Value 矩阵的优化技术可以避免重复计算…”这段假答案虽然不一定完全准确但它的向量和真实文档里关于 KV Cache 的那段描述非常接近所以能精准命中正确的文档。不过 HyDE 也有一个风险如果 LLM 编的假答案方向完全跑偏了比如把 KV Cache 理解成了 Redis 缓存那检索结果就会更差。所以它比较适合 LLM 对问题领域有基本认知的场景冷门领域或企业私有术语慎用。HyDE 的代码示例如下# 1. 让 LLM 先凭空生成一段“假答案”不必完全准确 hypothetical_answer LLM.generate(请回答 user_query) # 2. 用假答案的向量去检索因为它的文体更接近真实文档 hyp_vector embedding_model.encode(hypothetical_answer) top_k_chunks vector_store.search(hyp_vector, k5) # 3. 把检索到的真实文档 原始问题交给大模型生成最终回答 answer LLM.generate(top_k_chunks user_query)上面这三种方法解决的是「能不能搜到」的问题。但搜到之后资料的质量好不好呢这就是下面要处理的了。提升检索质量语义分块Semantic ChunkingNaive RAG 里最粗暴的一步就是切块每 500 个字切一刀管你是不是正好切在一句话中间。比如某块文本是 “员工请假需要提前 3 天申请超过 5 天需要”刚好在这里被截断了下一个块从 “部门经理审批” 开始。这两个块单独看都不完整检索和 AI 的理解都会打折扣。虽然可以通过 chunk_overlap 设置相邻块之间重复内容的长度但是效果相对有限。语义分块的做法是先把文档按句子拆开计算相邻句子的 embedding 相似度当相似度突然下降时说明话题变了就在这里切一刀尽量保证每个句子的语义都是完整的其余的步骤就跟 Naive RAG 一致了。不过这种方式代价也不小首先要为每一句话都算一次 embedding成本和耗时比按字数切分要高。而且相似度阈值非常难调所以它比较适合结构松散、话题变化比较快的文档像会议纪要、访谈记录这些。对于本身就有清晰章节结构的技术手册、产品说明文档直接按标题切效果也不差还更便宜。语义分块的伪代码示例如下# 1. 把文档拆成单句 sentences split_into_sentences(document) chunks [] current_chunk [sentences[0]] # 2. 遍历相邻句子计算 embedding 相似度 for i from 1 to len(sentences) - 1: similarity cosine_similarity(embed(sentences[i-1]), embed(sentences[i])) # 相似度骤降话题切换 if similarity threshold: chunks.append(join(current_chunk)) current_chunk [] current_chunk.append(sentences[i])层级索引Parent-Child Retrieval切块这件事有一个天然矛盾。切得小吧检索精度高但上下文不足切得大吧上下文丰富但噪声也大。Parent-Child Retrieval 的策略则是两层都要。先把文档切成大块再把每个大块细分成小块。检索时用小块匹配命中后返回它所属的大块。