跳到主要内容

RAG:让AI有"记忆"

本章要点

上一节,我们花了很大篇幅理解向量嵌入——把文本、代码、图片变成一串数字,放进语义空间里,让意思相近的内容靠得更近。你可能已经隐约感觉到,这个能力不只可以用来做"语义搜索",它还能做一件更重要的事:让大模型在回答问题之前,先去翻一翻外部资料

这件"翻资料"的事,就是这一节的主题——RAG。

读完这一节,你会获得:

  • 理解 RAG 的核心思想:为什么大模型需要"查资料",以及这个想法从何而来
  • 掌握 RAG 的完整工作流程:从文档入库到生成回答,每一步在做什么
  • 了解知识库的构建要点:文档怎么切、向量怎么存、检索怎么优化
  • 认识 RAG 在 AI 编程工具中的实际应用:代码搜索、文档问答、项目上下文
  • 理解 RAG 的能力边界:它擅长什么,不擅长什么,以及常见的坑在哪里

一个大模型的天生短板

在开始讲 RAG 之前,让我们先正视一个问题。

你使用的 ChatGPT、Claude 这类大语言模型,它们知道的"知识"是从哪里来的?答案你可能已经知道了:来自训练数据。模型在训练时阅读了海量的网页、书籍、论文、代码仓库,从中学习到了关于这个世界的各种模式和事实。

但这里有一个关键的时间差:模型的知识截止于训练完成的那一刻

假设你用的模型是在 2024 年底训练完成的。那么,2025 年发布的新编程框架、新 API 版本、新安全漏洞,它一概不知道。你问它"最新版本的 React 有什么变化",它只能基于训练数据里 2024 年及以前的信息回答——如果它硬要回答,大概率是在一本正经地胡说八道。

这还不是最麻烦的。更常见的问题是:模型不知道你私有的信息

你的公司内部文档、你的项目代码、你的团队规范、你自己的笔记——这些东西模型从未见过。你问它"我们项目的用户认证逻辑是怎么实现的",它不可能知道,因为它根本没读过你的代码。

于是,摆在我们面前的是一个很实际的问题:

有没有办法,让大模型在回答问题的时候,先去看一看它不知道的那些资料,再基于资料来回答?

这就是 RAG 要解决的问题。RAG 的全称是 Retrieval-Augmented Generation,直译过来是"检索增强生成"。这个名字拆开来看,每一个词都有它的含义:

  • Retrieval(检索):先从外部知识库里找相关资料
  • Augmented(增强):把这些资料放进模型的上下文,增强它的"知识储备"
  • Generation(生成):模型基于资料来生成回答,而不是只靠自己的"记忆"

简单来说,RAG 就是把大模型从"闭卷考试"变成了"开卷考试"。

一个开卷考试的比喻

想象你正在参加一场考试。

在传统的"闭卷"模式下,你只能靠自己的记忆来答题。你记得多少,就能答多少。如果考题涉及你从没学过的东西,你就只能猜——而且猜错了你也不知道自己错了。

现在,考试规则变了。考官允许你带一本书进考场。你拿到题目后,可以先翻书,找到相关的章节,读一读,然后基于书上的内容来答题。你不需要记住书里的每一个细节,你只需要知道"怎么找到对的那一页"。

RAG 做的事情,本质上就是给大模型配了一本"参考书"——或者说,一个可以随时查阅的外部知识库。

这个流程看起来简单,但它的意义非常深远。

在没有 RAG 的时候,如果你想让大模型了解你的项目,你只能把相关代码复制粘贴到对话里。每次开新对话,你都要重新粘贴一遍。如果你有几百个文件、几千页文档,你不可能全部贴进去——上下文窗口装不下,你的手也累。

有了 RAG,这些资料被预先处理好,放在一个知识库里。每次提问时,系统自动帮你找到最相关的几段,喂给模型。你不需要手动复制粘贴,模型也不需要"记住"所有东西——它只需要在需要的时候,翻到对的那一页。

RAG 的完整工作流程

现在,让我们把整个流程拆开来看。一个典型的 RAG 系统包含两个大阶段:离线阶段(准备知识库)和在线阶段(回答用户问题)。

离线阶段:把知识"装进"系统

离线阶段的核心任务,是把你的文档、代码、数据变成系统可以检索的形式。这一步通常只需要做一次,之后在数据更新时再做增量维护。

第一步:文档解析。 你的知识来源可能五花八门——PDF 格式的技术文档、Markdown 格式的项目说明、HTML 格式的网页、甚至图片里的文字。第一步要做的,是把这些不同格式的内容统一提取成纯文本。如果文档里有表格、代码块、图表,也要尽量保留它们的结构信息。

第二步:文本切块。 这是整个流程中最容易被低估,但实际影响最大的环节。你不能把一整本书变成一个向量——那样太"粗糙"了,检索时根本找不到具体内容。你需要把文档切成合适大小的片段,也就是"chunk"。

切多大合适?这取决于你的文档类型和使用场景。一般来说,每个 chunk 在 200 到 500 个 token 之间是一个常见的起点。太小了,语义不完整——比如只切出"它会在三分钟后过期",你根本不知道"它"指的是什么。太大了,一个 chunk 里混进太多主题,检索时很难精确命中。

切块的策略也很关键。最简单的做法是"固定长度切分"——每 500 个字切一刀,不管内容。但更聪明的做法是"尊重文档结构"——按段落、按标题、按函数边界来切,让每个 chunk 尽可能是一个语义完整的单元。在实际工程中,递归字符切分(Recursive Character Text Splitter)是很多框架的默认推荐:它先尝试按段落切,段落太大再按句子切,句子还太大再按短语切,尽量不破坏语义边界。

此外,相邻的 chunk 之间通常会保留 10% 到 20% 的重叠。这听起像是浪费,但它的作用是防止关键信息恰好被切在两个 chunk 的边界上。比如一句话的前半部分在 chunk A 的末尾,后半部分在 chunk B 的开头——如果没有重叠,检索时可能两个 chunk 都看起来"不太完整"。

第三步:向量化。 这就是上一节讲的 embedding 派上用场的地方。把每个 chunk 送进 embedding 模型,得到一串数字向量。这个向量就是这个 chunk 在语义空间里的"坐标"。

第四步:存入向量库。 把向量和它的原文、元数据(来源文件名、章节标题、更新时间等)一起存进向量数据库。向量数据库专门为"找最近邻"这种操作做了优化,能在海量向量中快速找到与查询向量最接近的那些。

常见的向量数据库有 Milvus、Pinecone、Weaviate、Qdrant、Chroma 等。选哪个取决于你的规模、部署方式和预算。对于个人项目或原型验证,Chroma 或 FAISS 这样的轻量方案就足够了;对于企业级应用,Milvus 或 Pinecone 提供了更完善的分布式和运维能力。

在线阶段:带着资料回答问题

离线阶段准备好了知识库,在线阶段就是真正"用"起来的时候。当用户提出一个问题,系统做的事情是这样的:

第一步:问题向量化。 用户的问题被送进同一个(或配套的)embedding 模型,生成一个查询向量。这个向量代表了问题在语义空间中的位置。

第二步:向量检索。 系统拿着这个查询向量,去向量库里找"邻居"——也就是语义上最接近的那些 chunk。通常会返回 Top-K 个结果,比如最相似的 5 个或 10 个片段。

这里有一个值得注意的细节:向量检索擅长找"语义相似",但不擅长找"精确匹配"。比如用户搜索"React 19 的新特性",向量检索可能找到"React 18 的升级指南"——因为两者语义很接近,但事实并不一样。为了解决这个问题,很多生产系统会使用混合检索:同时用向量检索(语义匹配)和关键词检索(如 BM25,精确匹配),把两边的结果融合在一起,兼顾"意思像"和"字面匹配"。

第三步:重排序。 从向量库拿回来的 Top-K 个结果,虽然整体上相关,但排序不一定精确。如果 K 取得比较大(比如 50 或 100),里面可能混进一些不太相关的内容。这时候,可以用一个更精细的模型——通常是交叉编码器(Cross-Encoder)——对这 K 个候选重新打分,挑出最相关的几条(比如 3 到 5 条),送给大模型。这一步虽然增加了延迟,但能显著提升最终答案的质量——毕竟,模型只能基于你给它的资料来回答,资料越精准,回答越靠谱。

第四步:拼接上下文,生成回答。 系统把精选出来的资料片段和用户的问题拼接在一起,形成一段完整的提示词,发给大模型。提示词通常会包含明确的指令,比如:

请基于以下参考资料回答用户的问题。如果资料中没有相关信息,请直接说"根据现有资料无法回答",不要编造内容。

参考资料:
[资料1] ...
[资料2] ...
[资料3] ...

用户问题:...

这个指令很重要——它告诉模型"只基于资料回答,没有就承认不知道"。如果没有这个约束,模型可能会在资料不足时,回到自己的"记忆"里去编造答案,那就失去了 RAG 的意义。

第五步:返回回答。 大模型生成回答,返回给用户。在理想情况下,回答中还可以附带引用——告诉用户哪个结论来自哪份资料的哪一段,让用户可以自行验证。

RAG 与微调:两种"教"AI 新知识的方式

说到让 AI 获得新知识,你可能也听说过另一种方法:微调(Fine-tuning)。微调的做法是,拿一个已经训练好的大模型,再用你自己的数据对它进行一轮额外的训练,让模型"学会"你的领域知识。

这两种方法经常被放在一起比较,但它们解决的是不同层面的问题。

微调更像是"让模型内化知识"。经过微调,模型的行为模式、回答风格、领域知识都发生了变化——它变成了一个"专门为你定制"的模型。但微调的成本很高:需要准备训练数据、需要 GPU 资源、需要训练时间。而且,一旦你的数据更新了,模型就需要重新微调。

RAG 更像是"给模型配一本参考书"。模型本身没有变,它只是在回答问题之前多了一个"查资料"的步骤。知识的更新非常简单——你只需要更新向量库里的内容,不需要重新训练任何模型。成本低,更新快,而且可以追溯——你可以清楚地看到模型是基于哪段资料回答的。

在实际应用中,RAG 和微调并不是非此即彼的关系。很多系统会同时使用两者:用微调让模型适应你的领域风格,用 RAG 让模型获取最新的信息。但对于大多数场景,特别是个人开发者和小团队,RAG 是更经济、更灵活的选择

切块策略:RAG 的第一个分水岭

在 RAG 的整个流程中,切块(Chunking)可能是最不起眼但最影响效果的一步。我把它单独拿出来讲,是因为这个环节的质量直接决定了后续所有步骤的天花板——检索再强,如果 chunk 本身切得不好,也不可能找到对的东西。

常见的切块策略

固定大小切块是最简单的方法:设定一个固定的 token 数(比如 500),每 500 个 token 切一刀,相邻 chunk 之间保留一些重叠。这种方法的好处是实现简单、计算开销小,但缺点也很明显——它完全不管内容,可能在句子中间、代码块内部、甚至一个单词的中间切断。对于结构统一的文档,这种策略勉强够用;对于结构复杂的文档,效果就很差了。

递归字符切块是目前最常用的默认策略。它不一次性切完,而是按照一个优先级列表依次尝试:先按段落(双换行符)切,段落太大就按句子(单换行符)切,句子还太大就按空格切,最后才按字符切。这种"由大到小"的策略,尽量保持了语义单元的完整性。LangChain 和 LlamaIndex 等主流框架的默认切分器,用的就是这种思路。

语义切块更进一步:它用 embedding 模型计算相邻句子之间的语义相似度,当相似度发生显著变化时,就在那里切一刀。这样切出来的 chunk,每个内部讨论的是同一个主题,边界恰好是话题转换的地方。效果更好,但计算成本也更高——需要对每个句子都做 embedding。

结构感知切块根据文档的具体格式来切。比如 Markdown 文档按标题层级切,代码文件按函数或类的边界切,表格保持完整不分切。这种方法最尊重文档的原有结构,但需要针对每种文档类型写专门的切分逻辑。

父子索引是一种比较巧妙的策略:把文档切成"小块"(子块,如 200 token)和"大块"(父块,如 1000 token)两层。检索时用小块来匹配——小块更精确,更容易找到具体细节;生成回答时,把小块对应的父块喂给模型——父块包含了更完整的上下文,模型能更好地理解来龙去脉。这种策略把"检索精度"和"生成质量"解耦了,在实际效果上往往比单一粒度要好。

没有万能策略

需要强调的是:没有哪种切块策略是万能的。一个 FAQ 文档和一个代码仓库,需要的切块方式完全不同。FAQ 适合把问答对合并成一个 chunk,代码则适合按函数边界切分。

实际的做法是:从简单策略开始(比如递归切分,500 token 一个 chunk),在真实数据上测试检索效果,根据效果不好的案例反过来调整切块策略。切块不是一次定死的事,而是需要持续迭代的。

从通用 RAG 到 AI 编程中的 RAG

到目前为止,我们讨论的 RAG 都是"通用版本"——给文档做索引,检索出来给模型参考。但 RAG 在 AI 编程工具中的应用,有一些独特的挑战和做法。

代码不是文章

代码和普通文档有一个本质区别:代码是高度结构化的,函数之间的调用关系、类的继承关系、变量的作用域,这些都是"语义"的一部分,但单纯的文本 embedding 很难捕捉到这些结构信息。

比如,两个函数可能用了完全不同的变量名和注释,但它们在调用链上是上下游关系——A 函数处理完数据后传给 B 函数。如果只看文本,它们可能不相似;但如果看调用关系,它们紧密相关。

因此,AI 编程工具中的 RAG 往往不只是做"文本相似度搜索",还会结合代码的抽象语法树(AST)、调用图、文件依赖关系等结构信息,从多个维度判断"哪些代码和当前任务相关"。

代码搜索:用自然语言找代码

AI 编程工具中 RAG 最常见的应用,就是代码搜索。你不需要记住某个函数的确切名字,也不需要知道它写在哪个文件里——你只需要用自然语言描述你想找什么:

处理用户登录 token 刷新的逻辑在哪里?

系统会先去理解你的问题,然后在代码库的向量索引中找到语义相关的函数、类和文件,返回给你。它找到的代码可能包含 refreshTokenupdateAuthSessionrenewCredentials 等函数——这些函数名和你的搜索词"登录 token 刷新"字面不同,但语义相关。

这不只是方便,它从根本上改变了开发者与代码库的交互方式——从"你去找代码"变成了"代码来找你"。

项目上下文:让 AI 知道你的项目在做什么

当你在 AI 编程工具里说"帮我加一个用户注销功能"时,它需要知道很多东西才能写出对的代码:你的项目用的是什么框架、路由是怎么组织的、认证逻辑在哪里、数据模型长什么样、代码风格是什么。

它不可能每次都把整个项目塞进上下文窗口。RAG 在这里扮演的角色,就是帮它快速定位"和当前任务最相关的那些文件"——路由配置、认证模块、用户模型、已有的类似功能——然后把这些文件的摘要或关键片段放进上下文,作为生成代码的参考。

这也是为什么现代 AI 编程工具(如 Claude Code、Cursor、GitHub Copilot)越来越强调"项目上下文理解"的能力——底层支撑这种能力的,正是 RAG 和 embedding 技术的组合。

文档问答:把项目文档变成可对话的知识库

很多项目有大量的文档——README、API 文档、架构设计文档、变更记录、会议纪要。新成员加入团队时,往往要花大量时间翻阅这些文档,而且经常找不到自己需要的信息。

把项目文档做成 RAG 知识库,就可以用自然语言提问了:"数据库迁移的步骤是什么?""部署到生产环境需要哪些权限?"系统自动检索相关文档,大模型基于文档回答,甚至附上引用链接。这对于团队知识管理和新人 onboarding 尤其有价值。

RAG 的边界与局限

RAG 很强大,但它不是银弹。在这一节的最后,我想诚实地和你聊聊它的局限——知道一个工具不擅长什么,比知道它擅长什么更重要。

局限一:检索质量是天花板

RAG 的核心理念是"先检索,再生成"。这意味着,如果检索这一步没找到对的资料,后面的生成再强也没用。模型只能基于你给它的资料回答——如果给的是无关的、过时的、甚至错误的资料,模型就会基于这些"垃圾"来生成回答。

这就是所谓的"垃圾进,垃圾出"(Garbage In, Garbage Out)。在 RAG 系统中,这个原则表现为:检索质量决定了回答质量的上限

局限二:相似不等于正确

向量检索擅长找"语义相似"的内容,但相似不代表事实正确。用户问"如何删除用户账号",系统可能找到了"禁用用户账号"的文档——这两者在语义上很接近,但在业务上,删除和禁用是完全不同的操作:一个不可逆,一个可恢复。

这是 embedding 本身的局限,我们在上一节也讨论过。在 RAG 系统中,这个局限会被放大——因为检索到的内容会直接影响大模型的回答。

局限三:RAG 不能替代推理

如果用户的问题是"根据这三份合同,判断哪一条条款在法律上优先级更高",RAG 可以帮你把三份合同里相关的条款都找出来。但"判断优先级"这件事,需要模型阅读、比较、分析——这是推理能力,不是检索能力。

换句话说,RAG 解决的是"找到相关资料"的问题,不是"基于资料做出复杂判断"的问题。后者需要模型本身具备足够的推理能力。

局限四:知识库需要维护

知识库不是建好就一劳永逸的。文档更新了,旧向量不会自动知道。你修改了接口说明、删除了过期流程、新增了安全规则,都需要重新生成相关 chunk 的向量,更新索引。如果长期不维护,知识库里的信息就会越来越陈旧,RAG 的效果也会越来越差。

这和传统数据库很像——数据变了,索引也要跟着变。只不过这里的索引不是按关键词排序,而是按语义位置组织。

局限五:安全和隐私

把公司内部文档、代码、客户数据放进 RAG 知识库,意味着这些信息可以被检索和生成。如果权限控制不到位,用户可能通过精心设计的提问,套出他不应该看到的信息。

此外,如果你使用的是云端 embedding 服务,你的文档内容会被发送到外部服务器进行向量化。对于敏感数据,这可能是不可接受的。在这些场景下,你需要考虑使用本地部署的 embedding 模型,或者对敏感内容做脱敏处理。

小结

这一节,我们认识了 RAG——检索增强生成,一种让大模型在回答问题之前先去"翻资料"的方法。

RAG 的核心思想,是用外部知识库弥补大模型的两大短板:知识过时和私有知识缺失。 它把大模型从"闭卷考试"变成了"开卷考试"——模型不需要记住所有东西,只需要在需要的时候找到对的那一页。

RAG 的完整流程分为离线和在线两个阶段。 离线阶段把文档解析、切块、向量化、存入向量库;在线阶段把用户问题向量化、检索、重排序、拼接上下文、生成回答。每一步都有它的设计考量,但切块是最容易被低估却影响最大的环节。

切块没有万能策略。 固定大小切分最简单,递归切分最常用,语义切分更精准但成本更高,结构感知切分最尊重文档格式,父子索引把检索和生成解耦。从简单策略开始,在真实数据上迭代,是更务实的做法。

RAG 在 AI 编程工具中有广泛的应用。 代码搜索、项目上下文理解、文档问答,这些能力的底层都离不开 RAG 和 embedding。它不是让 AI 变得更聪明,而是让 AI 能够"看到"更多相关资料。

RAG 有明确的边界。 检索质量决定了回答质量的上限,相似不等于正确,RAG 不替代推理,知识库需要持续维护,安全和隐私问题不容忽视。了解这些局限,才能在实际应用中把 RAG 用在刀刃上。

在上一节,我们学习了 embedding——把内容变成向量。在这一节,我们学习了 RAG——用向量去检索资料,增强模型的回答。把这两节放在一起,构成了一个完整的图景:embedding 是"理解"语义的能力,RAG 是"应用"这种理解来扩展 AI 知识边界的方法

从下一节开始,我们将进入提示词工程的原理,看看如何通过精心设计的提示词,让 AI 更好地完成我们交给它的任务。而 RAG 中学到的"检索—拼接—生成"的思路,也会在后面的 Agent 章节中反复出现——Agent 在做决策时,同样需要"查找资料"和"基于资料行动"的能力。

练习

思考题 1:你的"知识库"在哪里?

想一想,在你日常使用 AI 编程工具的过程中,有哪些信息是你希望 AI "提前知道"的?比如项目文档、代码规范、API 接口说明、团队约定。试着列出 5 到 10 条。

然后思考:这些信息目前是怎么传递给 AI 的?是每次手动粘贴?是写在 CLAUDE.md 或 .cursorrules 里?还是根本没有传递?如果把这些信息做成一个 RAG 知识库,会带来什么改变?

思考题 2:检索失败的原因

假设你做了一个技术文档的 RAG 知识库,用户问:

如何在生产环境中回滚数据库迁移?

但系统返回了以下 chunk:

  • "数据库迁移使用 alembic 管理,通过 upgrade 命令执行..."
  • "生产环境部署需要经过 code review 和 CI 检查..."
  • "数据库备份每天凌晨 3 点自动执行..."

这三个 chunk 都和问题"有点关系",但都没有直接回答"怎么回滚"。

试着分析:问题可能出在 RAG 流程的哪个环节?是切块方式不对(关于回滚的内容被切散了)?是 embedding 模型不够好(没能区分"迁移"和"回滚")?还是检索策略的问题(只靠语义相似度,没有关键词匹配)?如果是你,你会从哪个环节开始排查?

实践题 3:观察 AI 编程工具如何"查资料"

打开一个你熟悉的项目,使用 AI 编程工具(如 Claude Code、Cursor、GitHub Copilot)提出一个涉及项目上下文的问题,比如:

帮我看看项目中用户认证的逻辑是怎么实现的,涉及哪些文件?

观察 AI 的回应。它是否引用了具体的文件?它找到的文件是否都是相关的?有没有遗漏重要的文件?有没有引用不相关的文件?

试着把 AI 的引用和你自己手动搜索的结果做一个对比。AI 找到的和你找到的,有什么不同?这背后,很可能就是 RAG 在起作用。

讨论题 4:RAG 和微调,你会怎么选?

假设你正在做一个团队内部的 AI 助手,需要它回答关于你们公司产品的问题。你有以下资源:

  • 大约 200 页的产品文档(PDF 和 Markdown 格式)
  • 一个内部 Wiki,包含常见问题和解决方案
  • 产品文档每个月更新一次

你会选择 RAG 还是微调?为什么?如果五个月后,你发现用户经常问一些"比较型"问题(比如"A 功能和 B 功能有什么区别"),而 AI 的回答总是不够准确,你会怎么调整你的方案?