第3章:RAG——给 Agent 装上外部知识库
本章解决的核心问题:RAG 到底是什么?它和「训练模型」有什么本质区别?一段文字是怎么变成「能被搜索的向量」的?为什么 Agent 几乎离不开 RAG?
读完这一章,你会彻底理解 RAG 的每一个技术环节,并能判断「这个场景该用 RAG 还是该用微调」。
3.1 一个真实的痛点
先讲一个故事。
2024 年初,某大型律所采购了 ChatGPT Enterprise,满怀期待地想用它来做法律咨询。律师导入了一份 200 页的《民法典》合同编,问:「租赁合同中,出租人未告知房屋瑕疵需要承担什么责任?」
ChatGPT 给出了一份看起来非常专业的回答,引用了三个法条,还分析了责任类型。
律师一看——三个法条里两个是对的,一个是 ChatGPT「编」的。更要命的是,那条编出来的法条编号和真实存在的另一个法条编号一模一样,但内容完全不同。
律所 IT 负责人傻眼了:这叫什么事?知识明明就在那份 PDF 里,模型为什么不去查,偏要自己编?
这个痛点不是个别现象,它是 LLM 的结构性问题:
- 知识截止:模型的知识停留在训练日期。GPT-4 的知识截止在 2023 年 12 月,claude-3.5-sonnet 的知识截止在 2024 年 4 月。任何之后发生的事情——新法条、新数据、新事件——模型一概不知。
- 幻觉:当你问模型一个它不确定的问题时,它倾向于编一个听起来像真的的答案,而不是老老实实说「我不知道」。(原因在第5章讲对齐时会解释——模型被训练成「要有帮助」,而说「我不知道」在训练数据里被视为「没帮助」。)
- 私有知识盲区:你的公司内部文档、你的私人笔记、你的项目代码——这些模型从来没「见过」,也永远不可能通过通用训练获得。
RAG 就是来解决这三个问题的。
3.2 RAG 是什么:一张图解释

🎨 漫画图解:〈闭卷小狗与开卷小狗〉
RAG = Retrieval-Augmented Generation,翻译成人话:检索增强生成。
先有一个直观的理解:
flowchart LR
subgraph 没有 RAG
Q1[❓ 用户提问] --> LLM1[🧠 LLM]
LLM1 --> A1[💬 回答
可能编造、过时、不知道私有信息]
end
subgraph 有 RAG
Q2[❓ 用户提问] --> Search[🔍 从知识库检索相关内容]
Search --> Combine[📋 把检索结果和问题拼在一起]
Combine --> LLM2[🧠 LLM 基于参考资料回答]
LLM2 --> A2[✅ 回答
有据可查、可追溯来源]
end一句话概括 RAG:在用户问问题之前,先从外部知识库里找到最相关的几段资料,塞进 Prompt,然后让 LLM 基于这些资料回答——而不是凭自己的「记忆」回答。
类比:
- LLM 裸答 = 闭卷考试:全靠脑子记,记不清就瞎猜。
- LLM + RAG = 开卷考试:可以翻书查资料,答案有出处。
3.3 RAG 的完整工作流程

🎨 漫画图解:〈RAG 图书馆工厂〉
RAG 分为两个阶段:离线阶段(建索引)和在线阶段(检索 + 生成)。
flowchart TB
subgraph "离线阶段:建立知识库"
Docs[📄 原始文档
PDF · Word · 网页 · Markdown] --> Chunk[✂️ 文档切片 Chunking
把长文档切成小段]
Chunk --> Embed[🔢 向量化 Embedding
把每段文字变成一个数学向量]
Embed --> Store[🗄️ 存入向量数据库
形成可搜索的知识索引]
end
subgraph "在线阶段:回答问题"
Q[❓ 用户提问] --> QEmbed[🔢 问题向量化]
QEmbed --> Retrieve[🔍 在向量数据库中检索
找最相似的 Top-K 段落]
Store -.->|索引查询| Retrieve
Retrieve --> Augment[📋 增强 Prompt
把检索到的段落插入 Prompt 模板]
Augment --> Generate[🧠 LLM 生成回答
基于「问题 + 参考资料」]
Generate --> Answer[✅ 带引用的答案]
end下面我们逐个环节拆开看。
3.4 环节一:文档切片(Chunking)

🎨 漫画图解:〈切片师傅的难题〉
你不能把一本 200 页的书整个塞进 Prompt——一是上下文窗口装不下,二是 LLM 在长文本中找信息的效率很差(还记得第2章说的「迷失在中间」吗)。
所以第一步是把文档切成小块。
切片策略
| 策略 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 固定大小切片 | 每 500 个 token 切一段 | 简单、快 | 可能把一句话拦腰截断 |
| 语义切片 | 按段落/章节自然边界切 | 每段是完整的语义单元 | 段长不均匀 |
| 重叠切片 | 每段和下一段重叠 50-100 token | 减少边界信息丢失 | 存储量增加 |
| 层级切片 | 父文档 + 子文档,检索子文档返回父文档 | 上下文更完整 | 实现更复杂 |
实际项目中最常用的是「重叠切片」。举个例子:
原始文本:
「人工智能的发展经历了三次浪潮。第一次是符号主义时期,第二次是专家系统时期,第三次是深度学习时期。每一次浪潮都伴随着技术的突破和资本的涌入。」切片1(0-25字):「人工智能的发展经历了三次浪潮。第一次是符号主义时期,第二次是专家系统时期」
切片2(15-40字):「第二次是专家系统时期,第三次是深度学习时期。每一次浪潮都伴随着技术的突破和资本的涌入。」
注意切片1和切片2有重叠——「第二次是专家系统时期」出现在了两个切片中。这样当你搜索「专家系统」时,无论命中哪个切片,前后的完整语境都不会丢。
切多大合适?
这是个经验参数,没有标准答案:
- 太小(比如 50 token):一段话被切得支离破碎,检索出来之后 LLM 看不懂前因后果。
- 太大(比如 2000 token):检索精度下降——你搜「合同违约责任」,切片里包含了大量无关内容。
- 一般推荐:中文 300-800 字,英文 256-512 token。这是大多数 RAG 系统的最佳实践区间。
3.5 环节二:向量化(Embedding)

🎨 漫画图解:〈语义城市里的邻居〉
这是 RAG 最核心、也最难直观理解的一步。 请耐心读完这一节——搞懂了向量化,你就搞懂了 RAG 的精髓。
什么是向量?
先忘掉 AI。在数学里,向量就是一串数字。比如 [0.3, -0.7, 0.1, 0.9, -0.2] 就是一个 5 维向量。
什么是文本向量化?
文本向量化就是把一段文字——比如「猫是一种哺乳动物」——转换成一串数字,比如 [0.12, -0.34, 0.78, 0.05, ...]。
关键是:这个转换不是随机的。 这个向量的每一个维度都编码了文字在某个语义维度上的特征。不同的 Embedding 模型编码的维度不同,常见的维度有 768、1024、1536。
但为什么要把文字变成数字?
因为数字可以计算距离。
看一个具体的例子:
「猫」的向量: [0.8, 0.6, 0.1, ...]
「狗」的向量: [0.75, 0.55, 0.15, ...] ← 和「猫」很接近!
「沙发」的向量:[0.1, -0.7, 0.9, ...] ← 和「猫」很远!
- 「猫」和「狗」的向量距离很近 → 它们在语义上是相似的(都是宠物、哺乳动物、四条腿)。
- 「猫」和「沙发」的向量距离很远 → 它们在语义上不相似。
这个「距离」通常用余弦相似度来计算(两个向量的夹角越小,相似度越高)。
flowchart LR
subgraph 语义空间
direction TB
Cat[🐱 猫向量]
Dog[🐕 狗向量]
Sofa[🛋️ 沙发向量]
end
Cat ---|距离近| Dog
Cat -.-|距离远| Sofa一个好玩的实验
如果你把「国王 - 男人 + 女人」做向量运算,结果向量的最近邻居竟然是——「女王」。
Embedding("国王") - Embedding("男人") + Embedding("女人") ≈ Embedding("女王")
这说明 Embedding 向量不只编码了词语的表面意思,还编码了词之间的语义关系。这被称为「词向量的代数性质」,是 Embedding 最迷人的特性之一。
文本 Embedding 模型
现在你理解了:Embedding 模型就是把「文字」变成「向量」的东西。市面上主流的 Embedding 模型:
| 模型 | 维度 | 特点 |
|---|---|---|
OpenAI text-embedding-3-small |
1536 | 便宜($0.02/1M tokens)、够用 |
OpenAI text-embedding-3-large |
3072 | 精度更高、更贵 |
| BGE(BAAI) | 1024/768 | 中文效果好、开源、可本地部署 |
| Cohere Embed | 1024/4096 | 多语言、企业级 |
| Jina Embeddings | 768/1024 | 开源、支持 8K 上下文 |
怎么选? 中文场景优先选 BGE(因为它用中文语料训练过,对中文语义的理解更好)。多语言或英文场景用 OpenAI 或 Cohere。如果你想把所有数据留在本地,选开源模型自己部署。
3.6 环节三:向量数据库
切片做好了、向量也生成了——现在要把这两样东西存起来,而且要能快速搜索。
这就是向量数据库的工作。它和传统数据库(MySQL、PostgreSQL)的核心区别:
| 传统数据库 | 向量数据库 | |
|---|---|---|
| 查什么 | 精确匹配:「找所有年龄 > 30 的用户」 | 语义近似:「找和这句话最像的 5 个段落」 |
| 怎么查 | SQL 查询 | 向量相似度搜索(ANN 近似最近邻) |
| 典型产品 | MySQL、PostgreSQL | Pinecone、Milvus、Chroma、Weaviate、Qdrant |
| 存储内容 | 结构化数据 | 向量 + 元数据(原文、来源等) |
Chroma——最适合入门的向量数据库
在所有的向量数据库中,Chroma 是对个人开发者最友好的——Python 三行代码就能跑起来,不需要单独部署服务器。你在第7章构建 Obsidian Agent 时用到的就是 Chroma。
import chromadb
# 创建一个客户端(数据存在本地文件里)
client = chromadb.PersistentClient(path="./my_knowledge_base")
# 创建一个「集合」(相当于传统数据库的「表」)
collection = client.create_collection(name="obsidian_notes")
# 往里面加数据
collection.add(
documents=["猫是一种哺乳动物,属于猫科。", "沙发是客厅里用来坐的家具。"],
ids=["doc_1", "doc_2"]
)
# 搜索:「宠物」
results = collection.query(query_texts=["宠物"], n_results=2)
# 结果:「猫是一种哺乳动物...」的相似度远高于「沙发是...」
就是这么简单。在第7章我们会用 Chroma 来做 Obsidian Agent 的知识库索引。
3.7 环节四:检索(Retrieval)
用户提问 → 向量化 → 在向量数据库里找最相似的 Top-K 段落。
这个过程看似简单,但有几个关键的设计决策:
返回几个?(Top-K)
- K 太小(比如 3):可能漏掉相关信息。
- K 太大(比如 20):塞进 Prompt 的内容太多,LLM 反而抓不住重点,而且花钱更多(更多 token)。
经验值:K = 5-10 是大多数场景的最佳实践。
检索质量怎么保证?
向量检索不是万能的。常见的问题:
问题1:语义漂移。用户搜「苹果公司最新财报」,但知识库里有很多关于「吃苹果的好处」的文章——因为「苹果」这个词的向量在两种语境下都可能出现。
解决方案:混合检索(Hybrid Search)。不只靠向量相似度——同时用关键词匹配(BM25 算法),把两种检索结果按权重合并。
问题2:检索结果虽然相关,但不够「回答这个问题」。 比如用户问「合同第 12 条说了什么」,检索返回了合同的第 3、5、8 条——都是合同相关内容,但第 12 条没找到。
解决方案:Rerank(重排序)。检索出 20 个候选段落后,用另一个更精密的模型重新排序,把真正对回答有帮助的排到前面。
3.8 环节五:增强 + 生成
检索结果到手之后,剩下的就是把它们「塞进」Prompt,然后让 LLM 输出最终答案。
一个典型的 RAG Prompt 模板长这样:
你是一个知识助手。请基于以下参考资料回答用户的问题。
如果参考资料中没有相关信息,请如实说「参考资料中没有相关信息」。
不要编造参考资料中没有的内容。
【参考资料】
{检索到的段落 1}
{检索到的段落 2}
{检索到的段落 3}
【用户问题】
{用户的问题}
【回答】
注意那个关键指令:「如果参考资料中没有相关信息,请如实说不知道」。这是 RAG 防幻觉的第一道防线——明确告诉 LLM「不要编」。没有这条指令,LLM 还是会倾向于「补充」一些它自己脑子里(训练数据中)的内容。
3.9 RAG vs 微调:什么时候用哪个
这是很多人困惑的问题。RAG 和微调(Fine-tuning)都是「给 LLM 灌输新知识」的方法,但原理截然不同。
| RAG | 微调(Fine-tuning) | |
|---|---|---|
| 原理 | 检索外部知识,塞进 Prompt | 用新数据继续训练模型,更新模型权重 |
| 知识存储位置 | 外部向量数据库 | 模型参数内部 |
| 更新速度 | 即时——更新文档后立即生效 | 慢——需要重新训练(小时到天级别) |
| 可解释性 | 高——可以追溯每句话的出处 | 低——不知道为什么模型回答了某个内容 |
| 成本 | 低——主要是 Embedding + 检索的 API 费用 | 高——训练需要大量 GPU 算力 |
| 适用场景 | 知识频繁更新、需要引用溯源、私有文档 QA | 想让模型学会一种新的「风格」「格式」或「行为模式」 |
| 能处理的事实量 | 近乎无限(只要向量数据库装得下) | 有限——微调数据量有限,且容易「灾难性遗忘」 |
一个判断法则
如果你想让模型「知道更多东西」→ 选 RAG。
如果你想让模型「变成一个不同的人」→ 选微调。
具体来说:
- 该用 RAG:公司文档 QA、产品手册问答、法律/医疗知识库、个人知识管理(比如你的 Obsidian vault)。
- 该用微调:让模型学会用特定品牌的语气说话、让模型输出特定 JSON 格式、让模型学会某种专业领域的推理方式(不只是知识,而是思考方式)。
- 两者结合:很多生产级系统同时使用 RAG + 微调——微调让模型的行为模式变了,RAG 让模型的知识更实时。
3.10 RAG 的典型翻车场景

🎨 漫画图解:〈RAG 检索急诊室〉
诚实地说,RAG 不是一个「搭好就能用」的银弹。以下是最常见的翻车场景:
翻车1:检索回来了无关内容,LLM 被带偏
用户问「去年 Q3 的营收增长率」,结果检索回来的段落全是「去年 Q3 的员工满意度调查」。LLM 看到参考资料里有「Q3」「去年」这些词,就「硬编」了一个增长率。
解法:加上 Reranker、提高检索精度、在 Prompt 中强调「如果参考资料不够相关,不要强行回答」。
翻车2:切片太碎,关键信息被切断
一个 200 页的合同,第 5 页说「甲方应支付 100 万元」,第 200 页附录里说「上述 100 万元不含税」。这两段分别进了不同的切片。用户问「甲方要付多少钱」,检索只命中了第 5 页的切片,LLM 回答「100 万」——但漏掉了「不含税」。
解法:层级检索(检索子文档段落后,返回完整父文档上下文)、更大的切片、或用「Small-to-Big」检索策略。
翻车3:用户的问题本身就很模糊
「那件事后来怎么样了?」——Agent 根本不知道「那件事」是什么。这是多轮对话 RAG 的经典难题:用户的追问缺少上下文。
解法:Query Rewriting(查询重写)——在检索之前,先把用户的追问补全。比如把「那件事后来怎么样了」用 LLM 重写成「XX 项目在 2025 年 Q2 的进展如何」。
本章小结
| 要点 | 一句话 |
|---|---|
| RAG 全称 | Retrieval-Augmented Generation = 检索增强生成 |
| 本质对比 | LLM 裸答 = 闭卷考试;RAG = 开卷考试 |
| 五步流程 | 切片 → 向量化 → 存向量库 → 检索 → 增强 → 生成 |
| Embedding | 把文字变成能计算「语义距离」的向量 |
| 向量数据库 | 做「找最相似的 5 个段落」这种查询 |
| RAG vs 微调 | RAG 让你「知道更多」,微调让你「变成不同的人」 |
| 能否完美 | 不能——检索噪音、切片断裂、查询模糊是三大痛点 |
下一章预告
RAG 解决了「知道更多」的问题。但 Agent 还需要「做到更多」——调用外部工具。下一章我们拆解 Function Calling:LLM 怎么「说出」它想调用的工具?工具描述怎么写?MCP 是什么?Agent 乱调工具怎么办?
| ← 上一章 | 回到目录 | 下一章 → |
|---|---|---|
| 第2章:大脑、手脚与记忆 | 第4章:工具调用 |