第3章:RAG——给 Agent 装上外部知识库

本章解决的核心问题:RAG 到底是什么?它和「训练模型」有什么本质区别?一段文字是怎么变成「能被搜索的向量」的?为什么 Agent 几乎离不开 RAG?

读完这一章,你会彻底理解 RAG 的每一个技术环节,并能判断「这个场景该用 RAG 还是该用微调」。


3.1 一个真实的痛点

先讲一个故事。

2024 年初,某大型律所采购了 ChatGPT Enterprise,满怀期待地想用它来做法律咨询。律师导入了一份 200 页的《民法典》合同编,问:「租赁合同中,出租人未告知房屋瑕疵需要承担什么责任?」

ChatGPT 给出了一份看起来非常专业的回答,引用了三个法条,还分析了责任类型。

律师一看——三个法条里两个是对的,一个是 ChatGPT「编」的。更要命的是,那条编出来的法条编号和真实存在的另一个法条编号一模一样,但内容完全不同。

律所 IT 负责人傻眼了:这叫什么事?知识明明就在那份 PDF 里,模型为什么不去查,偏要自己编?

这个痛点不是个别现象,它是 LLM 的结构性问题:

  1. 知识截止:模型的知识停留在训练日期。GPT-4 的知识截止在 2023 年 12 月,claude-3.5-sonnet 的知识截止在 2024 年 4 月。任何之后发生的事情——新法条、新数据、新事件——模型一概不知。
  2. 幻觉:当你问模型一个它不确定的问题时,它倾向于编一个听起来像真的的答案,而不是老老实实说「我不知道」。(原因在第5章讲对齐时会解释——模型被训练成「要有帮助」,而说「我不知道」在训练数据里被视为「没帮助」。)
  3. 私有知识盲区:你的公司内部文档、你的私人笔记、你的项目代码——这些模型从来没「见过」,也永远不可能通过通用训练获得。

RAG 就是来解决这三个问题的。


3.2 RAG 是什么:一张图解释

assets/ai-agent-comics/ch03-01-open-book.webp
🎨 漫画图解:〈闭卷小狗与开卷小狗〉

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 基于这些资料回答——而不是凭自己的「记忆」回答。

类比


3.3 RAG 的完整工作流程

assets/ai-agent-comics/ch03-02-rag-factory.webp
🎨 漫画图解:〈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)

assets/ai-agent-comics/ch03-03-chunking.webp
🎨 漫画图解:〈切片师傅的难题〉

你不能把一本 200 页的书整个塞进 Prompt——一是上下文窗口装不下,二是 LLM 在长文本中找信息的效率很差(还记得第2章说的「迷失在中间」吗)。

所以第一步是把文档切成小块

切片策略

策略 做法 优点 缺点
固定大小切片 每 500 个 token 切一段 简单、快 可能把一句话拦腰截断
语义切片 按段落/章节自然边界切 每段是完整的语义单元 段长不均匀
重叠切片 每段和下一段重叠 50-100 token 减少边界信息丢失 存储量增加
层级切片 父文档 + 子文档,检索子文档返回父文档 上下文更完整 实现更复杂

实际项目中最常用的是「重叠切片」。举个例子:

原始文本:
「人工智能的发展经历了三次浪潮。第一次是符号主义时期,第二次是专家系统时期,第三次是深度学习时期。每一次浪潮都伴随着技术的突破和资本的涌入。」

切片1(0-25字):「人工智能的发展经历了三次浪潮。第一次是符号主义时期,第二次是专家系统时期」

切片2(15-40字):「第二次是专家系统时期,第三次是深度学习时期。每一次浪潮都伴随着技术的突破和资本的涌入。」

注意切片1和切片2有重叠——「第二次是专家系统时期」出现在了两个切片中。这样当你搜索「专家系统」时,无论命中哪个切片,前后的完整语境都不会丢。

切多大合适?

这是个经验参数,没有标准答案:


3.5 环节二:向量化(Embedding)

assets/ai-agent-comics/ch03-04-vector-city.webp
🎨 漫画图解:〈语义城市里的邻居〉

这是 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 = 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。
如果你想让模型「变成一个不同的人」→ 选微调。

具体来说:


3.10 RAG 的典型翻车场景

assets/ai-agent-comics/ch03-05-retrieval-clinic.webp
🎨 漫画图解:〈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 乱调工具怎么办?

第4章:工具调用——Agent 怎么做事


← 上一章 回到目录 下一章 →
第2章:大脑、手脚与记忆 第4章:工具调用