Skip to content

第六章 RAG 检索增强生成

6.1 RAG 基础概念:给模型装上知识库

在前面的章节中,我们学习了 Agent 架构的设计与实践。第 5 章中,Agent 通过工具调用、记忆管理和任务规划,展现出了自主完成复杂任务的能力。然而, 当 Agent 面对需要特定领域知识的问题时——比如"公司最新的差旅报销政策是什么" "客户 A 上季度合同中有哪些特殊条款""2026 年 3 月发布的行业标准 GB/T 52981 修订了哪些内容"——仅凭模型自身的参数知识,往往力不从心。

Agent 的"大脑"足够聪明,却缺少一个"知识库"。它需要一个机制,能在回答 问题之前,先查阅相关资料、找到正确答案的出处,再基于这些真实信息给出 回应。这个机制,就是 RAG(检索增强生成)

RAG 是连接 Agent 与外部知识的桥梁。掌握了 RAG,你的 Agent 就从 "闭卷考试"升级为"开卷考试",从"凭记忆答题"变为"查资料答题"。本章将从 RAG 的基本概念出发,逐步深入到 Embedding 模型、向量数据库、检索策略和 高级优化技巧,最终将这些技术融入 Agent 架构,构建出真正"博学"的智能体。


6.1.1 什么是 RAG?——"开卷考试"的智慧

先来看一个生活中常见的场景。

假设你是一名大学生,明天有一场专业课考试。你有两种准备策略:

  • 闭卷考试:考前把所有知识点背得滚瓜烂熟,进考场时只带一支笔, 答题全凭记忆。
  • 开卷考试:进考场时可以带教材、笔记和参考书,答题时可以翻阅 相关章节,找到对应内容后组织答案。

你会选哪种?对于绝大多数人来说,开卷考试的压力小得多——不需要把每个 细节都塞进脑子里,只需要知道"去哪里找"以及"怎么用找到的信息"。

RAG 就是让大模型参加"开卷考试"。

大语言模型在预训练阶段,把海量的互联网文本"背"进了参数里,这就像是 闭卷考试——知识都在脑子里,但有两个问题:一是"背"的内容有截止日期, 训练之后的新知识它不知道;二是"背"的时候难免有偏差,有时会把不确定的 东西"编"出来,这就是幻觉。

RAG(Retrieval-Augmented Generation,检索增强生成)的核心思想非常朴素:

在让大模型回答问题之前,先从外部知识库中检索出相关文档,作为 "参考资料"一起喂给模型。

用一句话概括:

RAG = 检索器(Retriever)+ 生成器(Generator)

检索器负责"翻书"——根据问题从知识库中找到最相关的文档片段;生成器 负责"答题"——将检索到的资料和问题一起交给大模型,让它基于资料 组织答案。

┌──────────────────────────────────────────────────────────────┐
│                       RAG 工作流程                            │
│                                                              │
│  用户提问 ──-> [检索器] ──-> 从知识库中召回 Top-K 相关文档      │
│                                    │                         │
│                                    ▼                         │
│              用户提问 + 召回文档 ──-> [大模型] ──-> 生成答案     │
│                                                              │
│  知识库来源:PDF、网页、数据库、API、Confluence...             │
└──────────────────────────────────────────────────────────────┘

这个"开卷考试"的类比可以延伸得更深:

考试环节对应 RAG 组件说明
课本和笔记知识库(向量数据库)你带入考场的参考资料
目录索引向量索引让你快速定位到相关章节
翻书找答案检索器(Retriever)根据问题快速查找相关内容
阅读理解后作答生成器(LLM)基于找到的资料组织答案
标注引用来源可溯源机制告诉老师答案出自哪一页

RAG 不是什么高深的技术魔法,它就是把人类"查资料再回答"的行为模式, 用工程化的方式赋予了 AI。


6.1.2 为什么需要 RAG?——LLM 的三大困境

要理解 RAG 的价值,首先要理解大语言模型(LLM)存在哪些固有缺陷。 即使是最先进的模型,也无法回避以下三个问题。

问题一:知识截止(Knowledge Cutoff)

模型的训练数据有截止日期。GPT-4o 的训练数据截至 2023 年 10 月, Claude 3.5 的训练数据截至 2024 年 4 月。这意味着 2026 年发布的 新法规、新标准、新产品,模型完全不知道。

实际案例:假设你是一家律师事务所,需要 AI 助手回答关于 《民法典》最新修订的问题。模型的训练数据可能只到 2023 年, 而 2025 年的修订内容它完全不知道。通过 RAG,你可以将最新法规 文档导入向量数据库,AI 就能基于最新法律条文给出准确回答。

问题二:幻觉(Hallucination)

当模型不确定答案时,它不会说"我不知道",而是会"编造"一个看起来 合理但实际不存在的答案。这种"一本正经地胡说八道"的现象就是幻觉。

幻觉的根源在于模型的生成机制——它是基于概率分布预测下一个 token, 而非基于事实数据库查询。RAG 通过将检索到的真实文档作为上下文, 强制模型"基于资料回答",显著降低了幻觉发生的概率。

问题三:领域知识缺失

通用模型不懂企业内部文档、专业领域知识。你问它"公司差旅报销标准", 它会给你一个"看起来合理"的通用回答,而不是你公司的真实政策。

RAG 将私有文档作为知识库,使模型获得领域能力——而无需花费昂贵的 成本去微调模型。

下表总结了这三个问题以及 RAG 的解决方式:

问题描述RAG 如何解决
知识截止训练数据有截止日期,无法回答训练后的事件从实时更新的知识库中检索最新信息
幻觉模型会"编造"不存在的事实强制模型基于检索到的真实文档回答
领域知识缺失通用模型不懂企业内部文档、专业领域知识将私有文档作为知识库,使模型获得领域能力

值得注意的是,RAG 并不能"消灭"幻觉——如果检索到的文档本身不准确, 或者模型忽略了上下文信息,幻觉仍可能发生。但 RAG 提供了一个 可验证、可溯源的机制:你至少可以检查模型参考了哪些文档, 而不是面对一个凭空生成的答案束手无策。


6.1.3 RAG vs 长上下文 vs 微调:如何选型?

继续用"考试"的类比。面对不同的考试需求,你有不同的应对策略:

  • RAG(开卷考试):带参考资料进场,按需翻阅。
  • 长上下文窗口(带全本教材进场):把整本教材翻开放在桌上, 答题时直接看。好处是不用翻目录,坏处是书太重、翻找慢、还费钱。
  • 微调(考前强化训练):考前花大量时间把特定知识"练"进脑子里, 进考场时不用带书。适合改变答题风格,但不适合频繁更新知识。

这是 RAG 学习者最常问的问题。三种方案各有优劣,不存在"银弹"。

方案一:RAG(检索增强生成)

工作方式:检索 -> 拼接上下文 -> 生成

优点

  • ✅ 知识实时更新,无需重新训练——更新知识库即可
  • ✅ 可溯源,答案可追溯到具体文档
  • ✅ 成本低,不需要 GPU 训练
  • ✅ 知识库可按权限控制(不同用户看到不同文档)

缺点

  • ❌ 检索质量直接影响答案质量("垃圾进,垃圾出")
  • ❌ 增加推理延迟(检索 + 重排序需要时间)
  • ❌ 无法改变模型的"风格"或"推理方式"

适用场景:企业知识库问答、客服系统、文档分析、实时信息检索

方案二:长上下文窗口

工作方式:把所有文档直接塞进 Prompt

以 Gemini 2.5 Pro(100 万 token)和 GPT-4o(128K token)为代表, 长上下文让模型直接"阅读"整本书。

优点

  • ✅ 实现简单,无需额外基础设施
  • ✅ 没有检索误差,模型能看到全部信息
  • ✅ 跨文档推理能力强(如比较两本书的观点)

缺点

  • ❌ 成本高(按 token 计费,100 万 token 的 Prompt 可能花费数美元)
  • ❌ "Lost in the Middle"问题:模型对长文本中间位置的信息关注度下降
  • ❌ 延迟高,处理大量文本需要更长时间

适用场景:单次文档分析、代码审查、长文翻译

方案三:模型微调(Fine-tuning)

工作方式:用领域数据继续训练模型,将知识"写入"模型参数

优点

  • ✅ 推理速度快,无需检索步骤
  • ✅ 可改变模型风格、格式、语气
  • ✅ 适合学习"模式"而非"事实"(如分类、格式转换)

缺点

  • ❌ 成本高(需要 GPU 训练)
  • ❌ 知识更新需要重新训练
  • ❌ 容易"灾难性遗忘"(忘记原有能力)
  • ❌ 不适合频繁更新的知识

适用场景:风格定制、特定格式输出、分类任务

选型决策流程图

需要外部知识?

    ├── 知识频繁更新? ──-> YES ──-> RAG

    ├── 知识量小且固定? ──-> 长上下文 或 微调

    ├── 需要溯源引用? ──-> YES ──-> RAG

    └── 需要改变风格/格式? ──-> YES ──-> 微调(可结合 RAG)

最佳实践:在实际项目中,这三种方案往往组合使用。例如:用 RAG 提供实时知识,用微调定制模型风格,用长上下文处理需要深度推理的 复杂任务。RAG 和微调并非互斥——先用微调让模型学会"如何更好地基于 检索内容回答",再用 RAG 提供实时知识,是业界常见的组合策略。


6.1.4 RAG 基础架构:两个阶段,八步流程

回到"开卷考试"的类比。整个 RAG 过程可以分为两个阶段:

  • 考前准备阶段(索引阶段):把课本和笔记整理好,做好目录索引, 方便考场上快速翻阅。这个阶段是离线的,提前做好一次就行。
  • 考试答题阶段(查询阶段):拿到题目后,翻到相关章节, 阅读后组织答案。这个阶段是在线的,每次提问都会执行。

RAG 系统分为两个阶段:索引阶段(离线)查询阶段(在线)

索引阶段(Indexing Phase)——考前准备

原始文档


┌──────────────┐
│ 1. 文档加载   │  ← PDF、网页、数据库...
└──────┬───────┘


┌──────────────┐
│ 2. 文档分块   │  ← 将长文档切成小段落(Chunk)
└──────┬───────┘


┌──────────────┐
│ 3. 向量化     │  ← 用 Embedding 模型将文本转为向量
└──────┬───────┘


┌──────────────┐
│ 4. 向量存储   │  ← 存入向量数据库(Chroma/Milvus...)
└──────────────┘

索引阶段做的事情,就是把杂乱无章的原始文档,变成可被高效检索的 向量索引。文档加载后需要分块(Chunking),因为整篇文档太长, 既不利于精确检索,也无法完整塞入模型的上下文窗口。分块后用 Embedding 模型将每段文本编码为高维向量,最后存入向量数据库。

查询阶段(Query Phase)——考场答题

用户提问


┌──────────────┐
│ 1. 查询向量化 │  ← 用同样的 Embedding 模型编码问题
└──────┬───────┘


┌──────────────┐
│ 2. 相似度检索 │  ← 在向量数据库中找 Top-K 最相似文档
└──────┬───────┘


┌──────────────┐
│ 3. 上下文拼接 │  ← 将检索结果 + 用户问题组成 Prompt
└──────┬───────┘


┌──────────────┐
│ 4. LLM 生成  │  ← 大模型基于上下文生成答案
└──────────────┘

查询阶段的核心是"用同样的 Embedding 模型编码问题"——这一点非常 关键。因为只有问题和文档使用相同的向量空间,它们之间的相似度 比较才有意义。关于 Embedding 模型的原理和选择,我们将在下一节 (6.2 节)深入讨论。

检索阶段找出的 Top-K 文档会被拼接到 Prompt 中,与用户问题一起 送给大模型。模型基于这些"参考资料"生成最终答案。

关键公式

RAG 的核心数学表达:

P(answer|question) = Σ P(chunk|question) × P(answer|question, chunk)

其中:

  • P(chunk|question) 是检索器给出的相关性得分
  • P(answer|question, chunk) 是生成器基于检索内容生成答案的概率

这个公式的含义是:RAG 的答案概率,等于"检索到某文档的概率"乘以 "在该文档条件下生成答案的概率",再对所有文档求和。检索器和生成器 各司其职,共同决定了最终答案的质量。


6.1.5 实战:用 LangChain 搭建最小 RAG 系统

理论讲完,我们来动手实践。目标是搭建一个能回答"什么是 Transformer" 的 RAG 系统。为了便于理解,代码做了逐行注释。

第一步:安装依赖并导入库

bash
# 安装所需依赖包
pip install langchain langchain-openai langchain-chroma chromadb
python
# ====== 导入所需的库和模块 ======
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
# OpenAIEmbeddings: 用于将文本转为向量的嵌入模型
# ChatOpenAI: 用于调用 OpenAI 的聊天模型(如 GPT-4o-mini)

from langchain_chroma import Chroma
# Chroma: 轻量级向量数据库,适合开发调试

from langchain.text_splitter import RecursiveCharacterTextSplitter
# 文本分割器,按字符数递归切分长文档

from langchain_core.documents import Document
# Document: LangChain 中的文档对象,包含正文内容和元数据

from langchain_core.prompts import ChatPromptTemplate
# Prompt 模板,用于规范化输入格式

from langchain_core.runnables import RunnablePassthrough
# RunnablePassthrough: 直接透传输入,常用于 RAG 链中的问题传递

from langchain_core.output_parsers import StrOutputParser
# 输出解析器,将 LLM 输出解析为纯字符串

每个导入都有对应的职责。理解这些组件的作用,是掌握 LangChain RAG 链的关键。

第二步:准备知识库文档

python
# ====== 准备知识库文档 ======
# 这里用三条文档模拟知识库,实际项目中应从文件系统或数据库加载

documents = [
    Document(
        page_content="Transformer 是一种基于自注意力机制的神经网络架构,"
                     "由 Vaswani 等人在 2017 年提出。"
                     "它完全摒弃了循环神经网络(RNN)的结构,"
                     "通过自注意力机制实现并行计算。"
                     "Transformer 的核心组件包括多头注意力、位置编码和前馈神经网络。",
        metadata={"source": "transformer_intro"}  # 元数据:记录文档来源
    ),
    Document(
        page_content="BERT(Bidirectional Encoder Representations from Transformers)"
                     "是由 Google 在 2018 年提出的预训练语言模型。"
                     "它使用 Transformer 的编码器部分,"
                     "通过掩码语言模型(MLM)和下一句预测(NSP)两个任务进行预训练。"
                     "BERT 在 11 项 NLP 任务上取得了当时的最佳结果。",
        metadata={"source": "bert_intro"}
    ),
    Document(
        page_content="GPT(Generative Pre-trained Transformer)是 OpenAI 开发的系列模型。"
                     "GPT-1 于 2018 年发布,使用 Transformer 的解码器部分。"
                     "GPT-3 于 2020 年发布,拥有 1750 亿参数,展示了强大的少样本学习能力。"
                     "GPT-4 于 2023 年发布,支持多模态输入。",
        metadata={"source": "gpt_intro"}
    ),
]

第三步:文档分块与向量化存储

python
# ====== 文档分块 ======
# 将长文档切分为更小的块(Chunk),提高检索精度
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,      # 每个块最大 200 字符
    chunk_overlap=50,    # 相邻块之间重叠 50 字符,避免语义被截断
)
chunks = text_splitter.split_documents(documents)  # 执行分块
print(f"文档被分为 {len(chunks)} 个块")  # 打印分块数量

# ====== 创建向量存储 ======
# 将分块后的文档通过 Embedding 模型转为向量,存入 Chroma 数据库
# 注意:需要提前设置 OPENAI_API_KEY 环境变量
vectorstore = Chroma.from_documents(
    documents=chunks,                    # 传入分块后的文档
    embedding=OpenAIEmbeddings(          # 使用 OpenAI 的 Embedding 模型
        model="text-embedding-3-small"   # 选择轻量级嵌入模型
    ),
    collection_name="transformer_knowledge",  # 集合名称(类似数据库表名)
    persist_directory="./chroma_db"       # 持久化存储路径
)

分块时设置 chunk_overlap 的目的是避免关键信息被截断在两个块的 交界处。例如,"GPT-4 于 2023 年发布"如果恰好被切在块边界,不重叠 的话这段信息就会丢失语义完整性。

第四步:构建 RAG 链

python
# ====== 构建检索器 ======
# 从向量存储创建检索器,设置每次检索返回 Top-3 最相关文档
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

# ====== 定义 Prompt 模板 ======
# 模板规定了输入格式:参考资料 + 用户问题
template = """你是一个知识渊博的 AI 助手。请根据以下参考资料回答用户问题。
如果参考资料中没有相关信息,请如实告知用户。

参考资料:
{context}

用户问题:{question}

请用中文回答:"""

prompt = ChatPromptTemplate.from_template(template)  # 从模板创建 Prompt

# ====== 初始化大模型 ======
# temperature=0 表示确定性输出,减少随机性
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# ====== 格式化函数 ======
def format_docs(docs):
    """将检索到的文档列表格式化为一个字符串,用空行分隔"""
    return "\n\n".join(doc.page_content for doc in docs)

# ====== 组装 RAG 链 ======
# 使用 LCEL(LangChain Expression Language)语法,用管道符串联各步骤
rag_chain = (
    # 第一步:构造输入字典
    # "context" 键:用户问题 -> 检索器 -> 格式化函数,得到检索文档文本
    # "question" 键:用户问题直接透传,不做处理
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt     # 第二步:将字典填入 Prompt 模板
    | llm        # 第三步:调用大模型生成回答
    | StrOutputParser()  # 第四步:解析输出为纯字符串
)

RAG 链是整个系统的核心。用 LCEL 管道语法 | 串联,数据流清晰: 问题输入 → 检索文档 → 填入模板 → 模型生成 → 解析输出

第五步:提问测试

python
# ====== 提问测试 ======
# 设计三个问题,覆盖不同场景
questions = [
    "什么是 Transformer?",          # 问题1:直接命中知识库
    "BERT 和 GPT 有什么区别?",     # 问题2:需要跨文档推理
    "今天天气怎么样?",             # 问题3:知识库中没有,测试诚实性
]

for q in questions:
    print(f"\n{'='*60}")
    print(f"问题:{q}")
    answer = rag_chain.invoke(q)   # 调用 RAG 链,传入问题
    print(f"回答:{answer}")

运行结果解读

  • 问题1(什么是 Transformer?):检索器会命中 Transformer 相关 文档块,模型基于检索到的文档内容给出准确回答,包括自注意力机制、 2017 年提出等关键信息。
  • 问题2(BERT 和 GPT 有什么区别?):检索器会同时召回 BERT 和 GPT 相关文档块,模型进行跨文档对比,指出 BERT 用编码器、GPT 用 解码器等区别。
  • 问题3(今天天气怎么样?):知识库中没有相关信息,由于 Prompt 模板中明确要求"如果参考资料中没有相关信息,请如实告知用户", 模型会诚实地说"参考资料中没有相关信息",而非编造天气情况。

这正是 RAG 的价值——让模型基于真实文档回答,而非凭空编造


6.1.6 实战:用 LlamaIndex 搭建 RAG 系统

LangChain 擅长链式编排,LlamaIndex 则更专注数据索引与检索。 同一个 RAG 系统,用 LlamaIndex 实现会更加简洁:

python
# 安装依赖
# pip install llama-index llama-index-embeddings-openai llama-index-llms-openai

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI

# ====== 配置全局设置 ======
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)

# 方式一:从目录批量加载文档(实际项目常用)
# documents = SimpleDirectoryReader("./my_docs").load_data()

# 方式二:直接创建文档(用于演示)
from llama_index.core import Document

documents = [
    Document(text="LangChain 是一个用于构建 LLM 应用的框架,"
                  "它提供了链式调用、Agent、工具集成等核心抽象。"
                  "LangChain 支持 Python 和 JavaScript。"),
    Document(text="LlamaIndex 是一个数据框架,专注于 LLM 应用的数据层。"
                  "它提供了数据加载、索引、查询等能力,"
                  "特别擅长处理 RAG 场景。"),
    Document(text="LangChain 和 LlamaIndex 可以互补使用。"
                  "LangChain 更擅长 Agent 和工作流编排,"
                  "LlamaIndex 更擅长数据索引和检索。"),
]

# ====== 创建索引(自动完成分块、Embedding、存储)======
index = VectorStoreIndex.from_documents(documents)

# ====== 创建查询引擎 ======
query_engine = index.as_query_engine(similarity_top_k=2)

# ====== 提问 ======
response = query_engine.query("LangChain 和 LlamaIndex 有什么区别?")
print(f"回答:{response}")

# LlamaIndex 会自动返回引用来源
print(f"\n引用来源:")
for node in response.source_nodes:
    print(f"  - {node.text[:100]}... (相似度: {node.score:.3f})")

可以看到,LlamaIndex 将分块、向量化、存储等步骤封装在 VectorStoreIndex.from_documents() 一行代码中,对初学者更友好。 同时它自动返回引用来源和相似度得分,方便溯源验证。

两个框架的选择建议:需要灵活的链式编排和 Agent 集成时选 LangChain, 需要强大的数据索引和检索能力时选 LlamaIndex。两者也可以混用。