当 AI 开始'翻书':RAG 的故事起点
当 AI 开始"翻书":RAG 的故事起点
"我们的校训是什么?"
这是高一学生小李第一次使用 ChatGPT 时提出的问题。他正在为校庆征文苦恼,想让 AI 帮忙润色一段话,但 AI 给出的开头让他愣住了——"你们学校的校训是'厚德载物,自强不息'"。小李皱起眉头:明明是"博学于文",怎么会变成"厚德载物"?他以为自己记错了,翻开学生手册一看才确认 AI 在编造。
这只是开始。在接下来的一周里,小李又陆续发现了 AI 的另外两个"怪癖":你问它上周发生的科技新闻,它开始"一本正经地胡说";你丢给它一份公司财报让它分析,它说"我没有权限访问您提供的具体文件"。三次独立的失灵,引出了同一个根源问题——大模型并不是一个真正"无所不知"的百科全书,它有自己无法跨越的三道墙。本章,我们就从这三道墙开始,展开全书的故事。
1.1 大模型的"三大原罪"
要理解 RAG(Retrieval-Augmented Generation,检索增强生成)的价值,我们首先要看清大模型(LLM, Large Language Model)的三个原生缺陷。这不是个别产品的 bug,而是当前所有基于 Transformer 架构训练的语言模型都难以摆脱的结构性局限。为了让这三大缺陷更有体感,我们仍然以小李的一周作为线索。
1.1.1 幻觉:模型会"一本正经地胡说"
"幻觉"(Hallucination)是 LLM 最让人头疼的特性之一。它指的是模型在生成内容时,会以高度自信的语气说出实际上不准确、甚至完全虚构的事实。这不是模型"故意撒谎",而是它在概率分布驱动下,"顺着语义流"自然产生的"接龙式错误"。
小李遇到的第一个问题就是典型的幻觉。校训明明是"博学于文,修身立德",模型却说成"厚德载物,自强不息"。这是一个组合幻觉——模型在训练语料里见过大量以"厚德载物"开头的句子(比如《周易》中的引用、清华大学校训等),因此在生成"你们学校的校训是……"时,它优先调取了最"高频"的表达,而不是从内部参数里检索准确的那一条。
幻觉的危险在于它的"自信感"。模型不像我们写代码会报错,它输出的每句话语气笃定、句式流畅,甚至还会主动补上细节——正是这种"低错误信号、高表达自信"的反差,让幻觉成为大模型在严肃场景中最难接受的缺陷。如果医生依据 AI 给出的"药物禁忌"开方,后果可能比"答错校训"严重得多。
1.1.2 知识截止:模型生活在"时间的切片"里
小李第二个遇到的问题是知识截止(Knowledge Cutoff)。他问 AI:"2025 年 3 月,英伟达发布了哪款新的 AI 芯片?" 模型停顿片刻后答复:"截至我所掌握的信息……2025 年的最新发布信息我无法告诉你。" 而真实情况是,2025 年 3 月英伟达确实发布了 Blackwell B200 系列。这一问一答之间,暴露了大模型的第二个局限——模型的"知识"被冻结在训练完成的某一刻。
任何 LLM 都需要经历两个阶段:首先用海量文本进行预训练,让模型学得语言规律与世界知识;然后通过微调对齐人类偏好。无论哪个阶段,模型一旦训练结束,它的"参数"就固定了下来。下次再有新事件发生,模型无法自动"吸收",必须重新训练——这在工业界是一个成本极高、周期极长的工程。
简而言之,今天你在用的 ChatGPT、文心一言、通义千问,它们的知识都停留在某一个"时间切片"上。任何切片之后发生的事件,它们要么说不知道,要么根据自己的"猜测"重新补全,这正是幻觉与知识截止常常相伴而生的原因。
1.1.3 私域盲区:模型看不到你的私有数据
小李遇到的第三个问题来自他暑期实习的公司。他想把一份内部销售数据丢给 AI,让它总结本月业绩趋势并给出建议。结果模型回复:"抱歉,我无法访问您提供的具体文件内容,目前我只能基于通用语料和训练数据回答……"
这并非模型在"装糊涂"。背后有一个根本原因——模型在训练时使用的是公开互联网数据,它根本没有见过任何一家公司、一所学校的内部文档。这意味着,无论是看起来多么强大的通用大模型,对你的"私域知识"(Private Domain Knowledge)都是"瞎子"。它不知道你们公司的客户名单,不知道你们学校的校训,不知道你们团队昨天定的产品方案,更不知道哪份合同里写了某项关键技术指标。
这三大缺陷构成了大模型在严肃生产环境下的"三大原罪":幻觉让输出不可信,知识截止让时效性失守,私域盲区让模型与企业内部场景之间永远隔着一道玻璃墙。任何想用大模型做"严肃问答"的系统——客服、法律咨询、医疗辅助、企业知识管理——都必须想办法绕过这三道墙。RAG,正是为此而生的工程方案。
1.2 原理揭秘:为什么 LLM 会"胡说"又"健忘"
要彻底弄懂 RAG 为何能解决上述问题,我们先要拨开 LLM 工作机制的表层,把镜头对准它的"内部引擎"。下面三个机制层共同决定了 LLM 的能力边界,也决定了 RAG 的设计动机。
1.2.1 模型的知识都"压"在参数里
LLM 本质上是一个巨大的神经网络,它的"知识"以数十亿甚至上千亿个浮点参数(weights)的形式被"压"在模型文件中。当你向模型提问时,模型会做两件事:第一,把你输入的文字转成 token,再转成向量;第二,逐层在这些参数矩阵上做矩阵运算,最终输出下一个 token 的概率分布。
这意味着什么?意味着模型并不是在"检索知识库",而是在"压缩重构世界"。它见过上万亿个 token 的语料,把这些语料里隐含的统计规律、事实信息、语言风格全部"蒸馏"进了参数。当你问它"牛顿第二定律是什么",它并不是从某处调出一段原文,而是基于对物理教材的高维统计印象,重新构造出一段看起来像答案的文字。
这种"压缩记忆"的代价,是模型无法精确知道"哪段知识"在"哪个位置"。它只有"模糊的整体印象",没有"指针"。这就是幻觉的结构性根源——它不是在说谎,它是在"重新拼图",而拼图有可能拼错。
1.2.2 训练是离线的,推理是在线的
第二个机制层面:模型的训练(training)是一次性、离线、昂贵的工程。一个千亿参数的模型,预训练往往需要数千张 GPU 耗时数周;训练一旦完成,模型的"知识状态"就凝固了。它在和你对话时做的工作叫做推理(inference),推理本质上是只读的——它不会"记住"你在本次对话里告诉它的事(除非你把这些信息显式塞进上下文窗口里)。
这就是为什么"今天 ChatGPT 知道的"和"两个月后 ChatGPT 知道的"是不同的时间快照。也正是为什么无论你怎么告诉 AI"请记住,校训是 X",关掉对话后再开,它又"忘了"——除非开发者真的把这条新事实写回了模型参数,也就是重新训练或继续微调。
1.2.3 上下文窗口:模型的"短期记忆"很有限
第三个机制层面:模型在一次对话里能看到的输入是有限额的,这块额度被称为上下文窗口(context window)。早期模型只能看 2K token(约 1500 个汉字),现在主流模型已经扩展到 32K、128K,甚至百万 token。但无论窗口多长,把它当作"喂给模型一本书让它读完再回答"都是不现实的——一方面是成本(token 数直接换算成人民币或美元),另一方面是"长上下文中的中间遗忘"问题(Lost-in-the-Middle):研究表明,当关键信息出现在长上下文的中间位置时,模型对它的关注度会显著下降。
总结这三个机制层面:大模型的知识以"压缩形式"存在参数里,训练是一次性离线事件,上下文窗口是有限的。这三层结构,共同决定了"想让模型准确回答私域 / 时效性问题"必须找别的出路。出路大致有三条:微调(Fine-tuning)、长上下文(Long Context)、检索增强生成(RAG)。本章我们暂时只展开第三条,但请先记住一句话——RAG 不是 LLM 的替代品,而是 LLM 的"外挂知识库"。
1.3 RAG 的核心思想:让模型先"翻书"再回答
如果把所有技术抽象都翻译成生活语言,RAG 的核心思想可以用一句话概括——开卷考试。传统的 LLM 问答像是"闭卷答题",模型只能用自己脑子里压住的那点东西;RAG 则像是允许模型"翻参考书"再下笔:问题来了,先去查资料;查到的材料和你一起看;最后基于材料写答案。
1.3.1 一个生活化的类比:会查资料的同学
让我们把这个类比展开。假设你是班长,老师突然问你:"上周三午餐后,班主任有没有让大家交一份手写读后感?"
- **完全闭卷**:你只能凭记忆答。可能记错,可能答"好像有吧"。
- **开卷(带参考)**:老师允许你翻本班工作日志。你翻开那一页,找到原话:"第三项:提醒周三下午自习课前交读后感"。你基于日志答:"日志里写的是让周三下午交。"
这两种答法的区别不在于你聪不聪明,而在于你是否有"查资料"这一动作。RAG 给 LLM 增加的就是这个"动作"。它没有改变 LLM 的"聪慧程度",但给了它"查询能力"和"可引用材料"。这也是为什么很多工程师戏称 RAG 是"给模型装了个搜索引擎"——它本质上就是让模型在回答前先做一次精准的信息检索。
1.3.2 RAG 的两阶段架构:检索 + 生成
技术上的 RAG 系统由两个核心阶段组成:
1. 检索阶段(Retrieval):把用户的问题转化为查询(query),去一个外部知识库里查找相关的文档片段。 2. 生成阶段(Generation):把"用户问题 + 检索到的文档片段"一起作为上下文输入给大模型,让模型基于这些"参考资料"生成回答。
正是这个"两阶段"——"检索 + 生成"——让 RAG 同时缓解了幻觉、知识截止、私域盲区三个难题:
- 因为回答必须基于检索到的具体文本,模型凭空编造的可能性大幅降低(缓解幻觉)。
- 知识库可以实时更新(比如每天同步一次最新新闻),所以"资料"始终新鲜(缓解知识截止)。
- 知识库可以是私有的,可以装进公司文档、规章制度、内部 wiki(缓解私域盲区)。
1.3.3 RAG 的完整流程图
我们用一个流程图把 RAG 的整体结构画出来,对比"普通 LLM 问答"和"RAG 问答"的差异,让差异一眼可见:
图 1-1 普通 LLM 问答与 RAG 问答的整体流程对比
```mermaid flowchart TD A[用户提问] --> B{是否启用 RAG} B -->|否| C[直接送入 LLM] --> D[生成回答] B -->|是| E[将问题转为查询向量] --> F[在向量知识库中检索] --> G[取出 Top K 个文档片段] G --> H[拼接 Prompt 问题+参考材料] --> I[送入 LLM] --> J[生成带依据的回答] ```
注意看左边的"普通问答"分支:用户问题 → LLM → 答案。这条路径上没有任何"外部知识"的注入,模型只用自己参数里的"压缩知识"作答。RAG 则在问题与模型之间插入了一个"检索模块"。当用户的问题到来时,RAG 系统先去知识库里挑出最相关的若干片段,然后把这些片段连同问题一并交给模型。模型的角色从"唯一的知识源"变成了"基于给定材料的写作助手"。
这就是 RAG 与普通 LLM 问答的本质差异:普通 LLM 问答让模型既是作者又是资料员,RAG 让模型只做作者,资料员由检索系统担任。
1.4 30 行代码跑通 RAG:亲手体验"检索 + 生成"
理论说再多,不如亲手跑一次。本节我们用一个最小可运行示例(约 30 行 Python 代码),让你在本地就能看到 RAG 的"魔法时刻"。我们仍然以"我们学校的校训是什么"这个问题为例,对比"无 RAG"与"有 RAG"时的输出差异。
1.4.1 准备工作:环境与依赖
在开始写代码前,我们需要准备以下几样东西:
- **Python 3.10 以上版本**。这是 LangChain 0.1+ 的最低要求。
- **安装必要的依赖**:运行 `pip install langchain openai faiss-cpu tiktoken`。其中 `langchain` 是 RAG 的胶水框架,`openai` 提供 LLM 与嵌入 API,`faiss-cpu` 是 Meta 开源的本地向量数据库。
- **准备一份"学校知识"的纯文本文件 `school.txt`**,你可以填入真实的校训、办学历史、特色专业等信息。
- **一个 OpenAI(或兼容)的 API key**,配置到环境变量 `OPENAI_API_KEY` 中。如果你没有 OpenAI 账号,也可以使用国内替代(如通义千问、智谱 GLM 等),只需相应替换 LLM 与 Embedding 类的实现。
1.4.2 没有 RAG 的情况:模型大概率会编
在没有 RAG 的情况下,我们直接问模型"我们学校的校训是什么?"。这时模型其实根本不知道你指的是哪所学校,它的反应往往是:
> 我不知道你指的是哪所学校。一般来说,许多学校的校训会包含"博学"、"厚德"、"笃行"等关键词,比如清华大学校训是"自强不息,厚德载物"……
你看,模型开始"借力"——它拿清华的校训来凑数。如果你不仔细看,很可能被它误导。这就是没有外部知识库时模型的真实状态:它不可能精确回答一个具体学校的校训,因为它压根不知道你说的是哪所学校。
1.4.3 加上 RAG:模型开始"言之有据"
现在我们进入 RAG 模式。以下是一段最小可运行的示例代码(核心部分约 30 行):
代码清单 1-1 最小可运行的 RAG 问答示例
```python from langchain.document_loaders import TextLoader from langchain.text_splitter import CharacterTextSplitter from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import FAISS from langchain.chains import RetrievalQA from langchain.llms import OpenAI
1) 准备本地知识文件:school.txt
school_text = """ 我们学校的校训是:"博学于文,修身立德"。 学校创办于 1958 年,占地 300 亩,图书馆藏书 12 万册。 2024 年,学校新增了人工智能学院,开设智能科学与技术专业。 """ with open("school.txt", "w", encoding="utf-8") as f: f.write(school_text)
2) 加载与切分文档
loader = TextLoader("school.txt", encoding="utf-8") documents = loader.load() text_splitter = CharacterTextSplitter(chunk_size=200, chunk_overlap=0) docs = text_splitter.split_documents(documents)
3) 把文档片段转成向量并建索引
embeddings = OpenAIEmbeddings() vector_db = FAISS.from_documents(docs, embeddings)
4) 组装"检索 + 生成"问答链
qa = RetrievalQA.from_chain_type( llm=OpenAI(temperature=0), chain_type="stuff", retriever=vector_db.as_retriever(search_kwargs={"k": 2}) )
5) 提问
question = "我们学校的校训是什么?" answer = qa.run(question) print("RAG 回答:", answer) ```
短短 30 行代码背后其实跑通了四件事,下面我们一一拆解:
1. 加载与切分。把整篇文档拆成模型好处理的"小块"。原因是模型一次性读不了太长文本,而小块更容易精确匹配;`chunk_size=200` 表示每段不超过 200 个字符,`chunk_overlap=0` 表示相邻块不重叠(这是个故意简化的设置,在生产中你通常会调成几十字符的重叠)。
2. 嵌入与建索引。通过 OpenAI 的 Embedding 模型把每段文本转成高维向量(一串浮点数),再用 FAISS 这种向量数据库建索引。这一步相当于把"文字版的资料"翻译成"向量版的资料",让相似度比较变成数学计算——我们会在第 3 章详细讲向量与相似度。
3. 检索。当用户提问"我们学校的校训是什么"时,系统会把问题也转成向量,去库里找出最相似的 Top-K 段(这里我们设 `k=2`,意思是返回最相关的 2 段)。
4. 生成。把"检索到的段落 + 用户问题"拼进 prompt 一起发给 LLM,让它基于这些材料作答。`temperature=0` 是为了让模型更"严肃"地引用资料,而不是自由发挥。
下面我们对比一下结果:
- **没有 RAG**:模型可能答错,或者甩出清华校训凑数。
- **有了 RAG**:模型大概率会回答——"根据资料,你们学校的校训是'博学于文,修身立德'"。它不仅答对了,还能在 prompt 设计得当的情况下附带引用,告诉用户"信息来源于第 1 段"。
这就是 RAG 带给 LLM 的根本性变化:从"自由发挥的作者"变成"有据可依的写作助手"。这种差别,在企业客服、医疗咨询、法律援助等容错率极低的场景里,价值千金。
1.4.4 进一步:你可以怎么扩展这个小例子
即便只有这 30 行代码,它也已经是一套可工作的 RAG 系统的最小单元。你还可以做以下这些扩展来加深理解,把"玩具"变成"准生产系统":
- 把 `school.txt` 换成你自己学校的真实资料,看模型是否会"引用"正确的校训。
- 把 `school.txt` 替换成一份企业内部产品手册,体验"私域问答"。
- 修改提问,让模型"用列表形式列出资料中的关键信息",观察 RAG 的列表生成质量。
- 在回答后让模型"返回引用段落编号",体会可追溯性。
- 把向量数据库从 FAISS 换成 Chroma、Milvus,观察工程选型的多样性。
- 让 LLM 在资料不足时回答"不知道",进一步压低幻觉率。
这些扩展不需要修改算法本身,只需要更换数据源、调整 prompt、替换底层组件。这是 RAG 工程化的一个核心美学——它不是黑盒研究,而是可拆解、可重组、可替换的工程模块栈。后续的章节我们会逐