RAG技术:让大模型学会"查阅资料"再回答
引言:当大模型遇到”知识盲区”
如果你玩过 Agent,大概率遇到过这样的尴尬:你让 Agent 回答一个公司内部的问题,比如”我们组这周的值班表是谁排的?”,它却一本正经地编出一个根本不存在的人名。
这不是模型不够聪明,而是它根本没见过这些信息。大模型的知识来自训练数据,训练完成那一刻,它的”记忆”就冻结了。之后发生的所有事——新产品文档、最新财报、公司内部规章——它统统不知道。
面对这种”知识盲区”,直觉上有三条路:
- 重新训练或微调模型,把新知识”塞”进参数里;
- 把所有资料塞进 Prompt,让模型一次性读完;
- 让模型先去”查资料”,查到相关内容再回答。
第一条路成本高、更新慢,还可能引发灾难性遗忘;第二条路撞在上下文窗口的墙上,资料一多就装不下,而且模型在长文本里容易”走神”。第三条路,就是本文的主角——RAG(Retrieval-Augmented Generation,检索增强生成)。
一、RAG 究竟是什么
RAG 这个概念由 Lewis 等人在 2020 年的论文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》中正式提出。论文的核心主张一句话就能讲清楚:
把”参数化记忆”(模型自身学到的知识)和”非参数化记忆”(外部检索到的文档)组合在一起,用来生成回答。
拆开 RAG 三个字母,正好对应它的三步动作:
- R(Retrieval,检索):根据用户问题,从外部知识库中检索出相关文档片段;
- A(Augmented,增强):把检索到的内容作为上下文,拼接到 Prompt 里,增强模型的输入;
- G(Generation,生成):大模型基于”问题 + 检索到的资料”生成最终回答。
一句话概括:**RAG让大模型从”闭卷考试”变成了”开卷考试”**。闭卷时它只能凭记忆作答,容易编造;开卷时它可以先翻到对应页码,再据此作答,既准确又能溯源。
二、核心思想:两种”记忆”的协奏
理解 RAG,关键是理解它区分了两种记忆。这一点论文里讲得很透彻,也是后续所有技术选型的根基。
参数化记忆:指模型权重里编码的知识。它来自训练数据,是”内化”在模型里的。优点是回答快、能泛化;缺点是知识会过时、无法解释来源、容易产生幻觉,而且更新必须重新训练。
非参数化记忆:指外部知识库(向量数据库里的文档片段)。它独立于模型存在,可以随时增删改,检索结果可以展示给用户作为引用。
打个比方:参数化记忆就像一个学生大脑里已经记住的知识,非参数化记忆就像考试时桌上摆着的参考书。学生答题时,既调用脑中已有的一般推理能力,又翻阅参考书查找具体事实——前者负责”怎么想”,后者负责”想什么”。
这个分工带来一个重要推论:**RAG 不是要替代大模型,而是给大模型配一个”外接硬盘”**。模型本身的推理、语言能力照旧使用,只是事实性知识改从外部按需取用。于是 RAG 的行为可以用一个公式表达:
1 | Answer = LLM(Query + Retrieved_Context) |
模型不再凭”记忆”回答,而是基于”查到的资料”回答。这一改变,直接缓解了大模型的几个老大难问题:知识过时、幻觉、无法溯源、领域知识缺失。
三、RAG 的完整工作流程
RAG 的流程可以清晰地分成两个阶段:离线的知识库构建和在线的检索生成。前者做一次(或定期更新),后者每次提问都执行。
1 | ┌─────────────────── 离线:构建知识库 ───────────────────┐ |
离线阶段做的是”把资料整理好放进图书馆”:把各种格式的文档解析成纯文本,切成大小合适的片段,每段转成一个向量,连同原文一起存进向量数据库。
在线阶段做的是”读者来图书馆查资料”:把用户的问题也转成向量,在数据库里找最相似的几段,把它们和问题一起拼成 Prompt 交给大模型,模型据此生成答案。
接下来几节,我们逐步拆解这条流水线上的每个关键环节。为了让你看清”为什么这么做”,我会先讲每个环节要解决的问题,再讲它怎么做。
四、第一步:文档处理与文本分块
为什么不把整篇文档直接存进去?三个原因:大模型有上下文长度限制,整篇文档塞不下;检索时整篇文档粒度太粗,匹配不准;而且只把相关片段送给模型,能省下大量 Token 成本。
所以必须把长文档切成小块。这一步叫文本分块(Chunking),听起来简单,却是影响 RAG 效果的最大变量之一。分块分得好,检索精准;分得不好,要么切碎了语义,要么块太大混入无关内容。
分块的核心原则是:在语义完整性和块大小之间找平衡。一个理想的块,应该能独立表达一个完整的意思,又不至于过长。
常见的分块策略有四种,复杂度递增:
- 固定长度分割:按字符数硬切。实现最简单,但会粗暴切断句子,适合日志这种没有语义结构的文本。
- 递归字符分割:按分隔符优先级递归切分,先按段落(
\n\n),再按句子(\n),再按空格,最后按字符。这是最常用的通用策略,能较好地保留段落和句子完整性。 - 文档结构感知分割:按 Markdown 标题、HTML 标签、PDF 章节等结构切分。适合结构化文档,能保留逻辑层次。
- 语义分割:用 Embedding 计算相邻句子的语义相似度,在语义跳变处切分。质量最高,但计算成本也最高。
这里有个容易踩的坑:块边界可能正好切断关键信息。比如一句话”退款需在购买后 7 天内申请”被切成”退款需在购买后”和”7 天内申请”两块,单独检索到任何一块都没用。
解决办法是重叠窗口(Overlap):相邻块之间保留一部分重复内容。比如块大小 500 字符、重叠 50 字符,那么块 2 的开头会重复块 1 的结尾 50 字符。这样即使关键信息落在边界,也能在至少一个块里完整出现。
一个实操建议:块大小一般选 256 到 1024 字符(约 100 到 500 Token),重叠取块大小的 10% 到 20%。具体数值要看你的文档特点——FAQ 类短问答可以用小块,长篇技术文档可以用大块。
五、第二步:向量化(Embedding)
分块之后,每段文本还是一串字符,计算机没法直接算”相似度”。需要把它转成数值向量,这一步叫向量化或Embedding。
Embedding 模型的作用,是把一段文本映射成一个固定长度的浮点数数组,比如 [0.12, -0.34, 0.56, ..., 0.07](通常几百到几千维)。关键性质是:语义相近的文本,向量也相近;语义无关的文本,向量相距甚远。
举个例子,三句话经过 Embedding 后:
1 | "如何退款" → [0.12, -0.34, 0.56, ...] |
“如何退款”和”退货流程”用词不同,但意思相近,向量也靠近;”今天天气”完全无关,向量就远离。这正是语义检索的基础。
这里有个容易混淆的点:稀疏向量和稠密向量。它们代表了文本数字化的两种思路。
稀疏向量(如词袋模型、TF-IDF 生成)像一份清单:维度对应词典里的每个词,某个词出现就在对应位置记个数,没出现就是 0。一份文档的向量绝大多数位置都是 0,所以叫”稀疏”。它的本质是”这篇文档里有哪些词、各出现几次”。
稠密向量(如 BERT、BGE 等 Embedding 模型生成)像一份指纹:维度是固定的几百到上千维,每个位置都是一个连续的小数,没有具体对应哪个词,整体编码了这段话的”意思”。它不再记录”有哪些词”,而是捕捉”说了什么”。
两者的差别直接决定了检索方式的不同:稀疏向量擅长精确的关键词匹配(你查”退款”它就找含”退款”的文档),但理解不了同义词;稠密向量能理解”退货”和”退款”意思相近,但对罕见专有名词的精确匹配反而不如稀疏向量。现代 RAG 通常用稠密向量,并在需要时配合稀疏向量做混合检索(后面会讲)。
六、第三步:向量数据库与相似度检索
向量存到哪里?传统数据库擅长精确匹配(WHERE name = '张三'),但语义检索要的是”找和这个向量最像的几个向量”,这是传统数据库不擅长的。于是有了向量数据库——专门存储、索引和检索高维向量的系统,常见的有 Milvus、Chroma、Weaviate、Pinecone、FAISS 等。
向量数据库的核心能力有三:能集成 Embedding 模型自动生成向量;能同时存向量和元数据(作者、日期、来源等标量字段);能做向量检索和标量过滤的联合查询(比如”只在 2024 年的文档里找语义相似的”)。
检索时怎么判断两个向量”像不像”?靠相似度度量,最常用的有两种:
- 余弦相似度:衡量两个向量的方向是否一致,不管长度。可以理解为”两段文本的话题方向是否一致”。
- 欧氏距离:衡量两个向量在空间中的绝对距离。可以理解为”两段文本在语义空间里的直线距离”。
一个直观的类比:余弦相似度像在判断”两个人是不是朝同一个方向走”,欧氏距离像在判断”两个人站得有多近”。大多数语义检索场景用余弦相似度就够了。
但纯向量检索有个软肋:它”看不懂”用户的真实意图。下一节我们就来解决这个问题。
七、检索进阶:混合检索与重排序
来看一个会让 RAG 翻车的场景。用户问”如何退款?”,向量检索返回的 Top-3 结果是:
- “无法退款的情况说明”
- “退款政策条款”
- “退款流程操作指南”
用户明明想问”怎么操作退款”,应该排第一的是”退款流程操作指南”,可向量检索把”无法退款的情况”排在了第一——因为这三段都含”退款”这个词,向量上很接近,检索器分不清谁更贴合”如何”这个意图。
这个问题有两层:第一层是召回层面,纯稠密检索对关键词不够敏感,可能漏掉含精确术语的文档;第二层是排序层面,初检结果的相关性排序不够精准。
混合检索(Hybrid Search)解决第一层。它同时跑两路检索:
- 稠密检索:用 Embedding 向量做语义匹配,擅长理解同义、近义;
- 稀疏检索(BM25):基于词频统计做关键词匹配,擅长精确匹配专业术语。
两路各返回 Top-K,再用RRF(Reciprocal Rank Fusion,倒数排名融合)把结果合并。RRF 的思路很朴素:一个文档如果在两路检索里都排名靠前,它大概率真的相关;如果只在一路里靠前,可信度就低些。公式是 score = Σ 1/(k + rank_i),k 通常取 60。这样既不丢语义理解,也不丢关键词精度。
重排序(Reranking)解决第二层。初检(无论是稠密、稀疏还是混合)为了速度,用的是双编码器(Bi-Encoder):问题和文档各自独立编码成一个向量,再算相似度。这种方式快,但问题和文档之间没有”细粒度交互”。
重排序用的是交叉编码器(Cross-Encoder):把”问题 + 候选文档”拼在一起送进模型,让模型逐字衡量两者的相关性。精度高很多,但计算量大,所以只能用在初检召回的小批量候选上。
重排序的角色可以用一个类比理解:初检像海选,重排序像复试。海选要快,用粗筛从百万文档里捞出几十篇;复试要精,对这几十篇逐一细看,排出真正的先后。常见的重排序模型有 bge-reranker、Cohere Rerank 等。
经过”混合检索召回 + 重排序精排”,检索质量会有明显提升。这也是生产环境 RAG 的推荐配置。
八、从论文看 RAG 的两种范式
讲完工程细节,我们回到论文,看看 RAG 在学术上最初是怎么设计的。这部分能帮你理解 RAG 的”原汁原味”,也能解释一些你在框架里看到的配置项。
论文提出了两种 RAG 范式,区别在于”检索到的文档如何参与生成”:
RAG-Sequence:检索出 Top-K 篇文档后,对每一篇文档分别让生成器生成一个完整答案,再把 K 个答案的概率加权求和。也就是说,**整个答案序列由同一篇文档”负责”**。
RAG-Token:生成答案时,每一个 Token 都可以参考不同的文档。也就是说,答案的不同部分可以来自不同文档。
用一个类比来区分:RAG-Sequence 像”写一篇作文只能翻一本书”,整篇答案都基于同一份参考资料;RAG-Token 像”写每个句子都可以换一本书参考”,答案可以综合多份资料。
哪种更好?看任务。如果答案的事实集中在某一篇文档里(比如事实问答),RAG-Sequence 更合适;如果答案需要综合多篇文档(比如”对比 A 产品和 B 产品的优缺点”),RAG-Token 更灵活。
值得一提的是,现在工程实践中用的大多数 RAG 框架(LangChain、LlamaIndex、Dify 等)采用的是一种简化版本:检索 Top-K 文档,把它们全部拼进 Prompt,再让模型一次性生成。这更接近 RAG-Sequence 的思路,但省去了对每篇文档分别解码的开销。论文里的精细概率建模在工程上往往被”拼上下文 + 单次生成”取代,因为现代大模型的上下文窗口已经足够容纳多篇文档。
九、上下文构建与提示词工程
检索回来的文档片段,不能直接丢给模型,要组织成结构化的 Prompt。这一步叫上下文构建(Context Assembly),它直接决定模型能不能”用好”检索结果。
上下文构建要回答四个问题:
选哪些片段? 一般取重排序后的 Top-K(如 Top-5)。可以再加一道相似度阈值过滤,只保留相似度足够高的;可以做去重,避免内容雷同;可以兼顾多样性,避免所有片段都来自同一篇文档。
怎么排序? 把最相关的片段放在 Prompt 前面。这不是随意的——研究表明模型对 Prompt 开头和结尾的内容注意力更强,中间部分容易被忽略,这叫”Lost in the Middle”现象。所以重要的内容往前放,或者往前和往后放,别埋在中间。
怎么控制长度? 上下文有 Token 预算:上下文 Token = 模型上下文窗口 - 系统提示 - 用户问题 - 预留输出。超预算时,从相关性最低的片段开始截断;如果单段太长,可以先做摘要压缩。
Prompt 怎么组织? 一个好的 RAG Prompt 通常包含:系统角色定义、行为约束(如”基于参考资料回答,不要编造”)、边界处理(如”资料里没有就说明”)、输出格式要求、参考资料、用户问题。其中动态部分(问题和检索到的资料)只占少数,大部分是预定义的结构化指令。
一个典型的 Prompt 模板长这样:
1 | 你是一个专业的知识问答助手。请基于以下参考资料回答用户问题。 |
注意第 2 条”没有就说明”——这是抑制幻觉的关键。没有这条约束,模型在资料不全时仍会”硬编”一个答案出来。
十、RAG、微调与长上下文:该如何选择
讲到这里,你可能会问:现在大模型上下文窗口动辄几十万、上百万 Token,直接把所有资料塞进去不就行了?微调也能教模型新知识,为什么非要用 RAG?
这三种方案不是互斥的,而是各有适用场景。理解它们的差别,才能在工程中做对选型。
微调改变的是模型的参数(参数化记忆)。它适合让模型学会某种”能力”或”风格”——比如学会用公司的话术写邮件、学会某种推理范式。但微调不适合注入频繁变化的事实知识:成本高、更新慢、还可能遗忘旧知识。打个比方,微调像”送员工去培训”,改变的是他的能力,不是他的资料库。
长上下文把资料直接塞进 Prompt。它适合资料量不大、单次查询的场景,比如”帮我总结这份合同”。但资料量一大就力不从心:Token 成本随资料量线性增长,模型在超长上下文里检索能力会下降(Lost in the Middle),而且每次提问都要重新传一遍资料,既慢又贵。打个比方,长上下文像”每次考试都把整柜子的书搬到考场”,搬不动也翻不过来。
RAG把资料存在外部,按需检索。它适合知识量大、频繁更新、需要溯源的场景。每次只检索相关片段送进模型,成本低、速度快、知识可随时增删、答案可引用来源。打个比方,RAG 像”开卷考试配索引”,需要哪页翻哪页。
实际工程中,三者常常组合使用:用微调让模型掌握领域推理风格,用 RAG 注入实时事实知识,对个别超长文档用长上下文做精读。但如果只能选一个解决”知识更新”问题,RAG 几乎总是性价比最高的那个。
十一、动手实现:一个最小 RAG 示例
讲了一堆原理,不如动手跑一遍。下面用一个最小可运行的示例,把前面讲的分块、向量化、检索、生成串起来。读者有 Python 基础,看这段代码应该很轻松。
我们用 LangChain 框架,它把 RAG 的各环节封装成了可组合的组件。先装依赖:
1 | pip install langchain langchain-community langchain-openai |
然后是一段完整的最小 RAG:
1 | from langchain_community.document_loaders import TextLoader |
这段代码里每一步都对应前面讲的环节:第 1、2 步是离线的文档处理与分块,第 3 步是向量化与存储,第 4 步是检索器,第 5 步是 Prompt 工程,第 8 步用 LangChain 的 LCEL 表达式把”检索 → 拼上下文 → 填模板 → 调模型 → 解析输出”串成一条链。
想进一步升级,可以把第 4 步的检索器换成混合检索(EnsembleRetriever 同时接 BM25 和向量检索),在第 4 步和第 8 步之间加一个重排序器(ContextualCompressionRetriever 配合 reranker 模型)。结构不变,只是组件升级。
十二、RAG 的局限与演进方向
RAG 不是银弹,它也有自己的局限,了解这些局限才能避免误用。
检索质量是天花板。RAG 的回答质量上限由检索质量决定——如果相关文档没被召回,模型再强也答不对。这就是为什么前面花那么多篇幅讲分块、混合检索、重排序:它们都是在抬高这个天花板。
召回但不相关的内容会干扰模型。检索回来的片段如果其实不相关,模型可能被带偏,硬从无关内容里”编”出答案。这就是为什么 Prompt 里要加”没有就说明”的约束,以及为什么相似度阈值过滤很重要。
多跳推理是难点。像”我们公司去年营收增长最高的产品线,今年的负责人是谁?”这种需要先查去年财报、再查今年人事任命的问题,单次检索很难搞定,需要把问题拆解成多步、多次检索。这就是 Agent + RAG 的结合点——用 Agent 做规划,用 RAG 做每一步的事实查询。
结构化数据不擅长。表格、数据库里的数值,用文本向量化效果不好。这类场景更适合 Text-to-SQL,让模型把问题转成 SQL 去查数据库,而不是把表格切成文本块做向量检索。
正因为这些局限,RAG 本身也在演进。学术界已经把 RAG 的发展分成三个阶段:
- 朴素 RAG(Naive RAG):就是本文讲的基础流程,检索-生成。
- 高级 RAG(Advanced RAG):在朴素 RAG 基础上,在检索前加查询改写、查询扩展,在检索后加重排序、上下文压缩,全面提升检索质量。
- 模块化 RAG(Modular RAG):把 RAG 拆成可插拔的模块(检索、记忆、路由、融合、排序等),按需组合,甚至引入多轮检索、迭代检索。
更前沿的方向还有 Graph RAG(用知识图谱代替纯文本块,捕捉实体间关系)、Self-RAG(让模型自己判断要不要检索、检索结果相不相关)等。这些方向的核心思路没变——都是围绕”怎么更准地找到、更聪明地用上外部知识”做文章。理解了本文的基础流程,再去读这些前沿工作会顺畅很多。
结语
回到开头的那个值班表问题。用 RAG 解决它,思路其实很朴素:把公司的排班文档整理进知识库,每次提问时先检索出本周的值班记录,再让模型据此回答。模型不再需要”记住”这些信息,只需要”查到”它们。
这就是 RAG 的本质——**让大模型从”凭记忆作答”转向”凭证据作答”**。它没有改变模型的推理能力,只是给模型配了一个随时可更新的外接知识库。这个看似简单的改变,却精准地击中了大模型在知识密集场景下的几个核心痛点:过时、幻觉、不可溯源、领域盲区。
如果你正在做 Agent,RAG 几乎是必备的能力——Agent 的”工具调用”解决”做事”,RAG 解决”知道事”。两者结合,才能做出真正可用的知识型应用。
理解 RAG,从这篇文档开始;用好 RAG,则要在自己的数据上反复调参、反复对比。希望这篇梳理能帮你建立一个清晰的知识框架,剩下的,就交给实践。
参考资料
- Lewis P, Perez E, Piktus A, et al. Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. arXiv:2005.11401, 2020. https://arxiv.org/abs/2005.11401
- LangChain 官方文档:https://python.langchain.com
- Karpukhin V, et al. Dense Passage Retrieval for Open-Domain Question Answering. EMNLP 2020.





