认识新朋友RAG——为什么大模型需要"外脑"
第 1 章 认识新朋友 RAG——为什么大模型需要"外脑"
1.1 一个让人哭笑不得的真实故事
小明的爸爸是一名程序员,他最近遇到了一件特别尴尬的事。
那天,公司要做一个关于"猫"的演示。爸爸兴奋地打开那个叫 ChatGPT 的网页对话框,信心满满地输入了一句话:
"请帮我写一段关于波斯猫的趣味介绍。"
不到 5 秒钟,对面出现了一段流畅的文字。爸爸读着读着,发现了一个奇怪的地方——里面居然说"波斯猫的原产地是意大利的佛罗伦萨"。爸爸心里犯起了嘀咕:自己虽然不是动物学家,但也听说过波斯猫来自波斯(现在的伊朗)呀,怎么会是意大利呢?
他又问了几个问题,结果发现了更多"奇怪但听起来很专业"的句子:
- "波斯猫的尾巴平均长度是 35 厘米。"(没有数据来源)
- "波斯猫最早是在 1880 年被一位英国船长带入欧洲的。"(年份像是凭空编的)
- "波斯猫的耳朵可以旋转 270 度。"(这是蝙蝠的能力呀!)
爸爸气得笑了出来——这个号称"博览群书"的大模型,居然会一本正经地说错话,而且错得那么自然,就好像它真的知道一样。
这种现象,就叫做"幻觉"(Hallucination)。
幻觉是大模型的第一个大问题,但它不是唯一的问题。在接下来的内容里,我们会看到大模型的"三大症状",以及一个聪明的解决办法——RAG。
---
1.2 大模型的三大"金鱼脑"症状
我们先来做一个有趣的比喻。
金鱼是一种非常漂亮的小鱼,但是它有一个著名的"缺点"——记忆力很短。科学实验发现,金鱼被广泛流传的"3 秒记忆"其实是不准确的,但它的记忆时间相比人类确实短得多,常常只能记住几分钟前发生的事情。
如果我们把大模型比作一条"金鱼脑"的小动物,它也有类似的三大症状:
1.2.1 症状一:知识截止时间——金鱼只记得"上个月"的事
大模型并不是真正的"神"。它虽然读过数以亿计的网页、书籍、文章,但它读的所有内容都有一个时间点——我们叫它"训练数据截止时间"(Knowledge Cutoff Date)。
打个比方:
- 如果大模型是在 2024 年 4 月之前完成训练的,那么 2024 年 4 月以后发生的任何事情,它都不知道。
- 就像一个只读到 4 月份报纸的小学生,5 月份发生的事情它完全没听说过。
我们来动手试试看。
实验 1:问一个"最近发生"的事情
打开任意一个大模型(比如 Kimi、通义千问、文心一言、豆包、ChatGPT 等),输入:
``` 请告诉我:2025 年 1 月 1 日,中国发生了哪些重要的科技新闻? ```
你很可能会看到这样的回答:
> "抱歉,我无法提供 2025 年 1 月 1 日的具体新闻,因为我训练数据的截止时间是 [某个日期]。"
或者更糟糕的:
> "2025 年 1 月 1 日,中国发布了 [一段听起来很专业但完全是虚构的内容]。"
判断依据:大模型对"训练截止时间之后"的事情几乎完全无能为力。即便它能猜,也是瞎猜。这种"不知道就说不知道"的能力,有时候反而比"胡说八道"更让人放心。
如果你问的是"2023 年 9 月 1 日中国发生了哪些科技新闻"——它会回答得很好,因为那是在它知识范围内的内容。
这就是大模型的第一个局限:知识有截止时间,没法知道最新的事。
1.2.2 症状二:幻觉问题——爱编故事的小话痨
"幻觉"是大模型领域里最让人头疼的问题之一。它的本质是:
大模型并不知道自己不知道什么。
它生成文本的过程,本质上是基于概率预测下一个词。当它对自己不确定的内容时,它不会沉默,而是会"填补"——用一个听起来很合理但完全虚构的内容填上空白。
这就像一个有点"嘴硬"的小孩,被问到不会的题目时,不是说"我不会",而是胡编一个答案,编得有模有样。
实验 2:问一个虚构的问题,看它怎么"上当"
输入:
``` 请告诉我:三国时期,诸葛亮发明过哪些数学公式? ```
(注意:诸葛亮是三国时期的政治家、军事家,历史上并没有记载他发明过数学公式。)
你可能会看到类似这样的回答:
> "诸葛亮发明了著名的'孔明算法',用来计算粮草分配。他还改进了《九章算术》中的'方程术',提出了'诸葛方程'……"
看起来非常专业对不对?但是——
- "孔明算法"——历史上不存在。
- "诸葛方程"——历史上不存在。
- 这些都是大模型**编造**出来的。
判断依据:这是大模型最容易让人"上当"的地方。它编出来的内容,往往包含真实的历史背景(诸葛亮确实是三国人物)和真实的术语(《九章算术》确实存在),混在一起让人真假难辨。
这就是大模型的第二个局限:会一本正经地胡说八道。
1.2.3 症状三:不知道你的小秘密——无法访问私有/专属数据
大模型虽然读过海量的公开网页,但它并不知道:
- 你妈妈的菜谱上写了什么
- 你班级的考试成绩单
- 你爸爸公司的内部技术文档
- 你自己写的日记
- 你家的宠物叫什么名字
- 你上周五数学课学了第几单元
实验 3:问一个专属的问题
输入:
``` 请告诉我:我家小狗"豆豆"上周三下午 3 点在做什么? ```
你几乎肯定得不到正确答案——大模型根本不知道"豆豆"是谁,更不知道它上周三做了什么。
这是因为:
1. 大模型在训练时没有读过你家的私人数据(隐私保护的需要)。 2. 即使它读过,也无法为每一个用户记住个性化的信息。 3. 企业的内部文档、学校的教学资料、医疗机构的病例档案——这些"私域知识"(Private Domain Knowledge)大模型一概不知。
判断依据:大模型的"知识"是通用知识,但它缺乏"为你服务"的专属知识。这就好比一个博学的图书管理员,他读过全世界所有的书,但他不认识你,不知道你最近在关心什么。
这就是大模型的第三个局限:没有专属记忆,无法访问私域数据。
---
1.3 三大症状的共同根源:模型参数是"固定"的
我们要稍微深入一点点,看看这三大症状的背后,到底是什么原因。
现代的大语言模型(Large Language Model,简称 LLM),比如 GPT 系列、Claude 系列、通义千问、Kimi、豆包等,它们的核心是一个巨大的神经网络,里面有几十亿、几百亿甚至上千亿个参数(Parameters)。
参数是什么?你可以把它想象成"神经网络的记忆细胞"——这些参数里编码了训练数据中学到的所有知识。
关键问题来了:
一旦训练完成,这些参数就被"冻结"了。
这意味着:
- 训练截止时间之后的新事件 → 不会进入参数
- 用户的私人数据 → 永远不会被自动加入
- 模型犯的错 → 没法"告诉"它,让它改正
打个比方,这就像一本已经印好的百科全书:
- 已经印好的内容,没办法再加新的章节
- 出版社不在你家,你家的相册不会被收录进去
- 如果哪一页写错了,除非再版一次,否则永远错下去
那怎么办?难道我们只能忍受一个有"金鱼脑"的大模型吗?
当然不是!这就是 RAG 登场的时候了。
---
1.4 RAG 登场:给大模型装一个"外脑"
1.4.1 什么是 RAG?
RAG 是 Retrieval-Augmented Generation 的缩写,中文翻译是检索增强生成。
这个名字听起来很专业,我们来拆解一下:
- **Retrieval**(检索):从一堆资料里找到和问题相关的内容
- **Augmented**(增强):把找到的内容"喂"给大模型,让它"参考"
- **Generation**(生成):大模型结合自己的能力和参考资料,生成最终的回答
一句话概括 RAG:
> 在回答问题之前,先去查资料,然后带着资料回答问题。
这就是我们小时候考试时,老师说的"开卷考试"模式!
1.4.2 图书馆小管家的比喻
为了让你更容易理解 RAG 的工作方式,我们用一个故事来类比。
场景设定:
假设你是一个 10 岁的小朋友,你的家里有一个超大型图书馆,里面装满了各种各样的书。但问题是——这些书太多了,多到你根本读不完,也记不住。
有一天,你的好朋友问你:
> "小华,你能告诉我恐龙的祖先是谁吗?"
你一脸茫然。这时候,你的家里走出来一个叫小 RAG 的图书馆小管家。
小 RAG 做了一件事:
1. 听懂问题:它听到"恐龙的祖先",知道这是一个关于古生物的问题。 2. 去图书馆找书:它冲到图书馆,在"古生物学"那一排书架上快速翻找。 3. 找到最相关的几本书:比如《恐龙大百科》《地球生命简史》《古生物学家日记》。 4. 把书拿给你:它把这几本书递给你说:"这几本书里可能有答案。" 5. 你开始读书思考:你读了书,找到了"恐龙最早的祖先是 2.3 亿年前的初龙"。 6. 组织语言回答:你合上书,对朋友说:"恐龙的祖先是 2.3 亿年前的初龙。"
这就是 RAG 的核心流程!
在这个比喻里:
- **图书馆** = 知识库(Knowledge Base),里面装着所有文档
- **小 RAG** = 检索器(Retriever),负责找书
- **你** = 大模型(Generator),负责理解和生成回答
- **朋友的问题** = 用户提问(Query)
- **那几本书** = 检索到的相关文档(Retrieved Documents)
1.4.3 流程对比:闭卷 vs 开卷
我们用一张流程图来直观对比"传统大模型"和"RAG 大模型"的回答过程:
```mermaid flowchart LR subgraph closed["🔒 闭卷答题 传统大模型"] Q1["用户问题"] --> LLM1["大模型<br/>仅靠参数记忆"] LLM1 --> A1["回答<br/>可能过时 / 幻觉 / 不懂私域"] end
subgraph open["📖 开卷答题 RAG 大模型"] Q2["用户问题"] --> R["检索器<br/>像小管家找书"] KB[("📚 知识库<br/>文档 向量数据库")] --> R R --> D["相关资料 Top-K<br/>最相似的几篇"] D --> LLM2["大模型<br/>结合资料生成"] Q2 --> LLM2 LLM2 --> A2["回答<br/>准确 可溯源 实时"] end ```
看图说话:
- 左边(闭卷):用户问题直接丢给大模型,大模型只能"凭记忆"回答。结果就是可能答错、答得旧、或者瞎编。
- 右边(开卷):用户问题先去"检索器"那里查资料,找到相关的文档片段,然后带着这些文档一起丢给大模型。大模型就像带着"小抄"上考场的学生,答案就有理有据了。
判断依据:这两种方式的根本差异,在于"知识"的来源不同。闭卷模式的知识全部"烙"在模型参数里,无法更新;开卷模式的知识可以独立更新——只要把新的文档加入知识库,模型立刻"学会"了。
---
1.5 动手实验:开卷 vs 闭卷,到底差多少?
理论讲完了,我们来做一个真正的实验,看看你能不能复现这个效果。
1.5.1 实验设计
实验目标:证明在同样的问题下,"开卷"模式(带参考资料)的回答比"闭卷"模式(不参考资料)更准确。
实验材料:
- 一台能上网的电脑
- 任意一个大模型(比如 Kimi、豆包、通义千问等)
- 一段虚构的"参考资料"
实验步骤:
第一步:准备一段虚构资料。
假设我们家有这样一份"家规"(纯属虚构):
```text 【小明家2025年家规】 1. 每天晚上 8 点必须开始写作业。 2. 每天看动画片的时间不能超过 30 分钟。 3. 每周六是"家庭游戏日",必须玩两局桌游。 4. 小明的零花钱每周 20 元,需要自己记账。 5. 每个月必须读完一本课外书,并且写读后感。 6. 周末可以吃一次麦当劳,但是只能点一份开心乐园餐。 ```
第二步:闭卷模式提问。
打开大模型,输入:
``` 请告诉我小明家的家规里,关于动画片时间和零花钱的规定是什么? ```
你很可能看到这样的回答:
> "关于动画片时间,建议每天不超过 30 分钟,避免影响视力。关于零花钱,建议根据家庭实际情况,养成记账的好习惯……"
注意!大模型给出的回答看起来"合理",但完全是它的"通用建议",不是小明家的真实家规。
第三步:开卷模式提问(带参考资料)。
输入:
``` 请根据以下资料回答问题:
【参考资料】 小明家2025年家规: 1. 每天晚上 8 点必须开始写作业。 2. 每天看动画片的时间不能超过 30 分钟。 3. 每周六是"家庭游戏日",必须玩两局桌游。 4. 小明的零花钱每周 20 元,需要自己记账。 5. 每个月必须读完一本课外书,并且写读后感。 6. 周末可以吃一次麦当劳,但是只能点一份开心乐园餐。
【问题】 请告诉我小明家的家规里,关于动画片时间和零花钱的规定是什么? ```
这一次,你会看到非常精准的回答:
> "根据家规,每天看动画片的时间不能超过 30 分钟。小明的零花钱是每周 20 元,需要自己记账。"
实验结论:
| 模式 | 回答是否基于真实资料 | 是否会编造 | | --- | --- | --- | | 闭卷 | ❌ 否 | ✅ 容易编造 | | 开卷(RAG) | ✅ 是 | ❌ 极大降低 |
这就是 RAG 的威力。
1.5.2 用代码感受一下 RAG 的雏形
如果你已经会一点点 Python 编程,我们可以看一段极简的代码,来感受 RAG 的核心思想。不用怕,这段代码只有十几行。
```python
一个超简化的 RAG 演示代码
目的:体验"先检索,再生成"的思想
=== 第一步:准备一个小型知识库 ===
knowledge_base = [ "Python 是一门由 Guido van Rossum 设计的编程语言,1991 年发布。", "Java 是一门由 Sun Microsystems 公司的 James Gosling 设计的编程语言,1995 年发布。", "JavaScript 是由 Brendan Eich 在 1995 年用 10 天时间设计出来的。", "C++ 是 Bjarne Stroustrup 在 1979 年开始设计的。", "Go 语言是由 Google 公司的 Robert Griesemer 等人在 2009 年发布的。", ]
=== 第二步:实现一个最简单的"检索器" ===
def simple_retriever(question, knowledge_base, top_k=2): """找出知识库中和问题最相关的 top_k 条资料""" scored_docs = [] for doc in knowledge_base:
用关键词重叠数作为相似度(仅用于演示)
score = sum(1 for word in question.split() if word in doc) scored_docs.append((score, doc)) scored_docs.sort(reverse=True, key=lambda x: x[0]) return [doc for score, doc in scored_docs[:top_k]]
=== 第三步:模拟"生成"过程 ===
def simple_rag_answer(question, knowledge_base):
1. 检索相关资料
retrieved_docs = simple_retriever(question, knowledge_base)
2. 把资料和问题组合在一起(这就是"Augmented")
prompt = f"""请根据以下参考资料回答问题:
参考资料: {chr(10).join(['- ' + doc for doc in retrieved_docs])}
问题:{question}
回答:"""
3. 把 prompt 送给大模型(这里用模拟输出代替)
print("【送给大模型的 Prompt】") print(prompt) print("\n【大模型的回答】") print("(实际场景下,这里调用大模型 API 即可得到回答)") return prompt
=== 运行实验 ===
question = "JavaScript 是谁设计的?什么时候发布?" simple_rag_answer(question, knowledge_base) ```
代码解读:
1. `knowledge_base` 就是一个装满"资料"的列表(真实场景下可能是几万、几十万、几百万条文档)。 2. `simple_retriever` 是一个最简化的检索器。它用"关键词出现次数"来衡量相关性。真实场景下会用更复杂的向量检索。 3. `simple_rag_answer` 把问题和检索到的资料拼在一起,形成一个更"丰满"的 prompt 送给大模型。
判断依据:这段代码虽然简陋,但它包含了 RAG 系统的三大核心组件——知识库、检索器、生成器。后面我们 14 章的内容,就是把这三个组件一步步做得更专业、更高效。
---
1.6 RAG vs 其他方法:什么时候用 RAG?
有的小朋友可能会问:"除了 RAG,还有别的方法也能让大模型'学会'新知识吗?"
有的。最常见的有两种:
1. 传统问答系统(比如客服机器人) 2. 模型微调(Fine-tuning)
我们用一个表格来对比:
| 方法 | 原理 | 优点 | 缺点 | 适用场景 | | --- | --- | --- | --- | --- | | 传统问答系统 | 用关键词匹配、规则模板找答案 | 简单、可控、可解释 | 不灵活、不能理解自然语言、覆盖面窄 | 简单的 FAQ、规则固定的产品咨询 | | 模型微调 | 用新数据重新训练模型,改变参数 | 回答风格可定制、能力可提升 | 成本高、需要专业数据、不能实时更新、容易遗忘旧知识 | 行业专属模型、风格定制 | | RAG | 先查资料,再生成回答 | 实时更新、可溯源、成本低、私域友好 | 依赖检索质量、需要维护知