跳转到内容

RAG 为什么成为刚需?基本原理与架构

到目前为止,我们构建的所有应用——无论是情感分析器、对话助手还是多模态系统——都有一个共同的假设:模型本身就知道答案。你问它 Python 装饰器是什么,它能答;你让它写一段排序代码,它也能写。因为这些知识在它的训练数据里。

但现实中的很多问题,模型的训练数据里并没有。比如你问:"我们公司上季度的 Q3 销售目标完成率是多少?"或者"按照我们内部的报销政策,差旅费超过 500 元需要谁的审批?"——这些信息存在于你的私有文档、数据库或内部系统中,模型不可能知道。这时候,无论提示词写得多么精妙,模型也只能瞎编一通(这就是所谓的"幻觉"问题)。

RAG(Retrieval-Augmented Generation,检索增强生成) 就是来解决这个问题的。

RAG 解决的三大痛点

第一,知识时效性。LLM 的训练数据有截止日期。GPT-4o 的训练数据大约截止到 2024 年 4 月,在这之后发生的事情它不知道。通过 RAG,你可以把最新的文档、新闻、报告注入到问答过程中:

python
# 没有 RAG — 模型不知道最新消息
question = "2025 年 3 月 OpenAI 发布了什么新模型?"
# 模型可能回答:"我的训练数据截止到 2024 年 4 月,无法回答这个问题"

# 有 RAG — 先检索相关新闻,再让模型基于新闻内容回答
retrieved_docs = search_knowledge_base("OpenAI 2025年3月 新模型")
answer = llm.answer(question, context=retrieved_docs)

第二,私有领域知识。企业的内部文档、产品手册、技术规范、客户记录等,这些数据永远不会出现在公开的训练集中。RAG 让 LLM 能够安全地访问这些私有知识:

python
# 企业内部知识库问答
question = "我们的 API 速率限制是多少?"
docs = retrieve_from_internal_wiki("API rate limit")
answer = llm.answer(question, context=docs)
# "根据《API 使用指南 v2.1》,免费版每分钟 20 次,专业版每分钟 1000 次"

第三,减少幻觉。当模型没有足够的信息时,它倾向于"编造"一个看起来合理的答案。RAG 通过提供真实的参考材料,大幅降低了这种编造的概率——因为模型有了事实依据,不需要靠猜测来填充答案。

一个最简单的类比

想象你去一家餐厅面试,面试官问你:"你们店的招牌菜是什么?"

如果你没看过菜单,只能猜:"可能是宫保鸡丁吧?"——这是纯 LLM 的方式,靠猜,容易错。

但如果你被允许先翻一下菜单再回答,你会说:"招牌菜有三道:秘制红烧肉、松鼠桂鱼和手工水饺。"——这就是 RAG 的方式,有据可依,准确可靠。

这个"翻菜单"的动作就是 检索(Retrieval),而基于看到的内容来回答就是 增强生成(Augmented Generation)。两者合起来就是 RAG。

RAG 的完整数据流:6 步架构

理解 RAG 的最好方式是跟踪一条数据从输入到输出的完整旅程。标准 RAG 系统包含 6 个关键步骤

步骤 1:文档加载(Loading)

你的知识来源可能是 PDF 文件、Markdown 文档、网页、数据库记录甚至 YouTube 视频字幕。第一步就是把这些不同格式的原始数据统一加载成 LangChain 能处理的 Document 对象——每个 Document 包含 page_content(文本内容)和 metadata(元信息,如来源文件名、页码等):

python
from langchain_core.documents import Document

doc = Document(
    page_content="Python 的 GIL(全局解释器锁)使得同一时刻只有一个线程执行 Python 字节码",
    metadata={"source": "python-intro.pdf", "page": 12}
)

print(doc.page_content)   # 实际文本内容
print(doc.metadata)       # {'source': 'python-intro.pdf', 'page': 12}

这一步对应大纲中的 4.2 文档加载器,我们会在下一节详细介绍如何从 PDF、网页、Notion 等 100+ 数据源读取数据。

步骤 2:文本分块(Splitting)

一篇完整的文章通常太长,直接塞进模型的上下文窗口会超出 token 限制(而且即使塞得下,太多无关信息也会干扰模型的判断)。所以需要把长文档切分成较小的 块(chunk),每个块通常包含几百个字,足够表达一个完整的语义单元:

python
原始文档:
"第一章介绍...第二章讨论...第三章总结..."

分块后:
Chunk 0: "第一章介绍 Python 的历史..."
Chunk 1: "第二章讨论 Python 的核心特性..."
Chunk 2: "第三章总结学习路线..."

这一步对应大纲中的 4.3 文本分割,切分策略直接影响后续检索的质量。

步骤 3:向量化(Embedding)

这是 RAG 最关键的技术环节之一。嵌入模型(Embedding Model) 把每个文本块转换成一个固定长度的数字数组(向量),使得语义相似的文本在空间中的距离更近

python
text_a = "猫坐在垫子上"
text_b = "一只小猫躺在地毯上"
text_c = "今天股市大涨"

vec_a = embedding_model.embed(text_a)   # [0.12, -0.34, 0.56, ...]
vec_b = embedding_model.embed(text_b)   # [0.11, -0.32, 0.58, ...]  ← 和 vec_a 很接近
vec_c = embedding_model.embed(text_c)   # [-0.78, 0.45, -0.21, ...] ← 和 vec_a 差很远

这一步对应大纲中 4.4 向量存储与嵌入模型 的前半部分。

步骤 4:存储与索引(Storage)

生成的向量需要存到一个专门的数据结构中,支持高效的相似度搜索。这种数据结构叫做 向量数据库(Vector Store)。常见选项包括 Chroma、FAISS、Milvus 等。LangChain 对它们做了统一的抽象封装,切换底层存储几乎不需要改代码:

python
vectorstore.add_texts([
    "Python 是一门动态类型语言",
    "Java 使用静态类型检查",
    "Go 语言的并发模型基于 goroutine"
])

results = vectorstore.similarity_search("动态语言的特点", k=2)

这一步同样属于 4.4 向量存储与嵌入模型

步骤 5:检索(Retrieval)

当用户提问时,系统先把问题也做向量化,然后在向量存储中找到与问题向量最接近的那些文档块。这个过程叫做 相似度搜索(Similarity Search)向量检索(Vector Retrieval)

python
query = "Python 和 Java 在类型系统上有什么区别?"
relevant_docs = vectorstore.similarity_search(query, k=3)

除了基础的相似度搜索,还有高级策略如 MMR(最大边际相关性) 用于增加结果多样性,以及 上下文压缩 用于减少无关信息。这对应大纲中的 4.5 检索器

步骤 6:生成(Generation)

最后一步是把检索到的文档和用户的原始问题组合成一个精心设计的提示词,发送给 LLM,让 LLM 基于这些参考资料来生成最终答案:

python
prompt = f"""
基于以下参考资料回答问题。如果资料中没有相关信息,请说"我不知道"。

参考资料:
{format_docs(relevant_docs)}

问题:{query}
"""

answer = chat.invoke(prompt)

完整流程图

把 6 个步骤串起来:

用户提问 → [1.加载文档] → [2.文本分块] → [3.向量化] → [4.存入向量库]

                                    ← [5.检索相关文档] ← [问题向量化]

                              [6.组装提示词] → [LLM生成] → 结构化回答

                                   参考资料

这个流程分为两个阶段:

  • Indexing 阶段(离线):步骤 1-4,只需执行一次或在知识库更新时重新运行
  • Querying 阶段(在线):步骤 5-6,每次用户提问时执行

RAG vs 微调:什么时候选哪个?

维度RAG微调
知识存放位置外部知识库(随时可更新)模型参数内部(更新需重新训练)
数据更新频率实时/按需更新更新成本高,周期长
适合场景知识量大、变化快、需要溯源行为模式/风格/格式调整
幻觉风险低(有据可查)中等(仍可能编造)
成本低(只需存储+检索 API)高(GPU 训练资源)

简单来说:如果你的需求是"让模型能查到并引用具体的事实性信息",选 RAG;如果你想改变模型说话的方式/风格/行为模式,选微调。实际项目中两者经常结合使用。

接下来的一节(4.2),我们将详细学习如何从各种数据源加载文档——PDF、网页、Notion、数据库等等。

基于 MIT 许可发布