你问模型”公司最新的报销政策是什么”,它答得头头是道——但政策上个月刚改,它说的全是错的。你让它总结内部文档,它却编造了根本不存在的条款。

这不是模型”笨”,是它根本没有访问这些信息的通道。

RAG(检索增强生成)解决的就是这个问题:在生成答案前,先检索相关文档,让模型有据可查。知识截止、幻觉、无法访问私有文档——都可以通过 RAG 缓解。

本篇解析 RAG 的三阶段工作流(检索 → 分块 → 重排 → 融合)、核心组件和进阶优化,帮你构建可追溯的知识增强系统。


📋 一、RAG 核心机制:三阶段工作流

RAG 的本质:先检索,再生成——把”猜”变成”查”。

1.1 为什么需要 RAG?

企业客服要把内部手册、FAQ 接入问答;法务要基于合同条款生成摘要——纯 LLM 无法访问私有文档,只能”瞎猜”。传统 LLM 有三道坎过不去:

问题 表现 影响
知识时效性 训练数据截止到某个时间点 无法回答最新事件、政策、技术
知识范围 只能使用训练时的数据 无法访问企业私有文档、内部知识库
幻觉问题 可能编造看似合理但错误的信息 答案不可信,无法验证来源
上下文限制 Context Window 有限 无法处理超长文档或大量知识

RAG 的解决方案

将外部知识库与 LLM 结合,让模型在生成答案前先检索相关文档,然后基于检索到的内容生成答案。

这样既保证了答案的准确性(有据可查),又突破了知识时效性和范围的限制。

1.2 RAG 的三阶段工作流

RAG 的工作流程可以概括为三个阶段:

阶段 核心任务 机制概述 核心挑战
1. 检索(Retrieval) 在海量文档中定位与用户查询语义相关的片段 使用 Embedding Model 将文本转向量并进行向量搜索 召回率低:可能漏掉重要信息
2. 增强(Augmentation) 将检索到的片段注入 LLM Prompt Prompt 重构,将上下文作为参考资料 上下文污染:无关信息可能误导模型
3. 生成(Generation) LLM 基于注入的上下文生成答案 条件生成,结合 Schema 或 Prompt 约束 整合能力:综合多个片段准确生成答案

完整流程示例

# RAG 三阶段流程(伪代码)

# 阶段1:检索(Retrieval)
user_query = "用户登录功能如何实现?"
query_embedding = embed_model.encode(user_query)
relevant_chunks = vector_db.search(query_embedding, top_k=5)
# 输出:5个相关的文档片段

# 阶段2:增强(Augmentation)
context = "\n\n".join([chunk.text for chunk in relevant_chunks])
prompt = f"""
基于以下文档内容,回答用户问题:

文档内容:
{context}

用户问题:{user_query}

请基于文档内容回答,如果文档中没有相关信息,请说明"文档中未找到相关信息"。
"""

# 阶段3:生成(Generation)
answer = llm.generate(prompt)
# 输出:基于检索到的文档生成的答案

1.3 RAG 的优势对比 Fine-Tuning

第2篇中,我们详细讲解了 Fine-Tuning(微调)。这里我们对比 RAG 和 Fine-Tuning,帮助你选择合适的技术方案:

维度 RAG Fine-Tuning 说明
知识更新 实时/高效:更新知识库即可 滞后/高成本:需重新训练模型 RAG 只需更新向量数据库,Fine-Tuning 需要重新训练
知识来源 外部/可追溯:答案可链接原始文档 内部/不可见:知识融入模型参数 RAG 可以追溯答案来源,Fine-Tuning 无法追溯
知识范围 可访问海量外部文档 受训练数据限制 RAG 可以访问任意文档,Fine-Tuning 只能使用训练数据
成本 低:主要是存储和检索成本 高:需要 GPU 训练 RAG 成本低,Fine-Tuning 成本高
适用场景 快速变化的领域、访问海量私有数据、需要可追溯答案 改变模型风格、格式或语气、领域特定任务 场景不同,选择不同

决策指南

需求 推荐方案 原因
需要访问最新信息 RAG 可以实时更新知识库
需要访问企业私有文档 RAG 可以访问任意外部文档
需要可追溯的答案 RAG 答案可以链接到原始文档
需要改变模型输出风格 Fine-Tuning 可以训练模型改变风格
需要领域特定能力 Fine-Tuning 可以通过训练提升领域能力
需要快速上线 RAG 实现简单,成本低
需要长期稳定使用 Fine-Tuning 训练后模型行为稳定

💡 核心理解

  • RAG 更适合”知识动态更新 + 高可信度 + 可追溯”的场景
  • Fine-Tuning 更适合”风格与格式调整 + 领域特定能力”的场景
  • 两者可以结合:Fine-Tuning 后的模型仍可使用 RAG 访问外部知识
  • Agent 场景下的微调实践(LoRA、PEFT)详见第 16 篇能力调优

🧱 二、基础组件深度解析:构建知识库

一个高效的 RAG 系统依赖三个核心组件:Chunking、Embedding、Vector Database

这三个组件构成了 RAG 系统的”基础设施”,理解它们的工作原理是构建高质量 RAG 系统的前提。

2.1 Chunking(分块):语义完整性的艺术

你有一份 100 页的技术手册要做成知识库。用户问”登录功能怎么实现?”——分块方式决定了能召回哪些内容。切得太碎,语义断裂;切得太大,噪音太多。

定义:将大型文档拆成适合检索和 Context Window 的小片段(Chunk)。关键原则:每个 Chunk 语义完整,否则模型收到的信息会断裂。

为什么需要分块?

  • Context Window 限制:LLM 的上下文窗口有限(如 GPT-4 是 128k Token),无法一次性处理整本书
  • 检索精度:小片段更容易精确匹配用户查询
  • 计算效率:处理小片段比处理整个文档更高效

常用分块策略

1. 固定大小分块(Fixed Size Chunking)

  • 方法:按固定字符数或 Token 数切分(如每 500 字符一段)
  • 优点:简单易用,实现成本低
  • 缺点:容易切断句子,破坏语义完整性

示例

# 固定大小分块(伪代码)
chunks = []
chunk_size = 500 # 字符数
for i in range(0, len(document), chunk_size):
chunk = document[i:i+chunk_size]
chunks.append(chunk)

2. 语义分块(Semantic Chunking)

方法:不是机械地按固定大小切分,而是按照文档的自然结构(标题、段落、句子边界)或使用 NLP 技术识别语义边界,保证每个 Chunk 包含完整主题。

具体做法

  • 基于段落:按段落(\n\n)分割,每个段落作为一个 Chunk
  • 基于标题:识别文档中的标题(如 # 标题),每个标题下的内容作为一个 Chunk
  • 基于句子边界:在段落内按句子(.)分割,组合句子直到达到合适大小
  • 基于 NLP 技术:使用语义相似度计算,找到语义边界(如 LangChain 的 SemanticChunker

优点:保持语义完整性,检索更准确

缺点:实现复杂,需要理解文档结构

示例

# 语义分块(伪代码)

# 方法1:基于段落分块
paragraphs = document.split('\n\n') # 按段落分割
chunks = []
for para in paragraphs:
if len(para) > 500: # 段落太长,继续分割
# 按句子分割
sentences = para.split('。')
# 组合句子直到达到合适大小
current_chunk = ""
for sentence in sentences:
if len(current_chunk + sentence) < 500:
current_chunk += sentence
else:
chunks.append(current_chunk)
current_chunk = sentence
else:
chunks.append(para)

# 方法2:基于标题分块(Markdown 文档)
import re
# 识别 Markdown 标题(如 # 标题、## 子标题)
headings = re.findall(r'^#+\s+(.+)$', document, re.MULTILINE)
# 按标题分割文档
for heading in headings:
# 提取该标题下的内容作为一个 Chunk
chunk = extract_content_under_heading(document, heading)
chunks.append(chunk)

# 方法3:基于 NLP 语义相似度(使用 LangChain)
from langchain.text_splitter import SemanticChunker
splitter = SemanticChunker()
chunks = splitter.create_documents([document])

3. 父文档/子文档策略(Parent-Document RAG)

  • 方法:检索小而精准的子 Chunk,增强阶段注入整个父 Chunk,确保上下文完整
  • 优点:兼顾检索精度和上下文完整性
  • 适用场景:文档结构清晰,有明确的父子关系

示例

# 父文档/子文档策略(伪代码)
# 第一层:大块(父文档)
parent_chunks = split_by_section(document) # 按章节分割

# 第二层:小块(子文档)
child_chunks = []
for parent in parent_chunks:
children = split_by_paragraph(parent) # 按段落分割
child_chunks.extend(children)
# 记录父子关系
for child in children:
child.parent = parent

# 检索时:用子 Chunk 检索(精确)
# 增强时:用父 Chunk 增强(完整上下文)

📝 比喻理解:Chunk 就像”知识砖块”,分块方式决定了模型搭建知识大厦的稳定性。

  • 固定大小 = 机械切割,可能切坏砖块
  • 语义分块 = 按纹理切割,保持砖块完整
  • 父文档策略 = 小砖块定位,大砖块使用

2.2 Embedding Model(嵌入模型):语言的数字指纹

定义:将文本 Chunk 转换为高维向量(Embedding)的模型。

功能:语义相似的 Chunk 在向量空间距离更近,使得向量搜索能够找到语义相关的内容。

Embedding 的工作原理

第1篇讲过 Token Embedding(词→向量)。RAG 用的是 Text Embedding:把整个 Chunk(句子、段落)转成一个固定维度的向量。语义相近的 Chunk 在向量空间里距离更近,检索时就能”按语义找邻居”。

示例

# Embedding 示例(伪代码)
from sentence_transformers import SentenceTransformer

# 加载 Embedding 模型
model = SentenceTransformer('all-MiniLM-L6-v2')

# 将文本转换为向量
text = "用户登录功能需要验证用户名和密码"
embedding = model.encode(text)
# 输出:一个 384 维的向量,如 [0.1, -0.3, 0.5, ...]

选型原则

考虑因素 说明 示例
语言匹配 中文知识库用中文 Embedding 模型 text2vec-chinesem3e-base
领域匹配 专业领域用领域模型 医学领域用医学 Embedding 模型
维度平衡 维度越高精度越好,但计算成本越高 768 维 vs 1536 维
模型大小 大模型效果好但推理慢 小模型速度快但精度略低

💡 实战提示

  • 中文场景:推荐 text2vec-chinesem3e-base
  • 多语言场景:推荐 multilingual-e5-base
  • 英文场景:推荐 all-MiniLM-L6-v2text-embedding-ada-002

2.3 Vector Database(向量数据库):高效检索核心

定义:存储与管理所有 Chunk 向量的数据库。

核心功能:当用户查询向量输入时,通过 ANN(Approximate Nearest Neighbor,近似最近邻) 搜索快速返回 Top K 相关 Chunk。

为什么需要向量数据库?

传统数据库无法高效处理向量相似度搜索。向量数据库专门优化了向量检索,能够在百万级甚至千万级向量中快速找到最相似的 Top K 个结果。

关键技术:HNSW 索引

HNSW(Hierarchical Navigable Small World) 是向量数据库常用的索引算法:

  • 原理:构建多层图结构,从粗到细逐层搜索
  • 优势:检索速度快(O(log n)),精度高
  • 适用场景:大规模向量检索(百万级以上)

简单理解

就像地图导航:先看国家地图找到大致区域,再看城市地图找到具体位置,最后看街道地图找到精确地址。

常见向量数据库对比

数据库 特点 适用场景
Pinecone 云服务,易用,性能好 快速原型、中小规模项目
Weaviate 开源,功能丰富,支持多模态 中大规模项目,需要复杂查询
Milvus 开源,性能强,可扩展 大规模项目,需要自部署
Qdrant 开源,Rust 实现,性能好 对性能要求高的项目
Chroma 轻量级,易集成 小规模项目,快速开发

🔹 实战提示

  • 快速原型:使用 Pinecone 或 Chroma
  • 生产环境:根据规模选择 Milvus 或 Weaviate
  • 关键要求:支持动态更新(新增文档)、高并发查询、持久化存储

🚀 三、进阶优化策略:打造高精度 RAG

基础 RAG 容易出现 低召回率(漏掉重要信息)和 上下文污染(无关信息误导模型),生产环境需要进阶策略。

基础 RAG 的问题

问题 表现 原因
低召回率 检索不到相关文档 查询与文档的语义不匹配
低精确率 检索到不相关文档 向量搜索只考虑相似度,不考虑实际相关性
上下文污染 检索到的文档包含无关信息 没有对检索结果进行筛选和排序
单轮限制 无法处理多步骤推理 一次检索无法回答复杂问题

3.1 Re-ranking(重排):提升精确率

用户问”登录失败怎么办”,向量检索可能召回”登录功能说明”、”登录按钮设计”——语义相近,但用户要的是故障排查

Re-ranking 用 Cross-Encoder 对 Top N 结果二次排序,同时考虑查询 + 文档的交互,筛掉”看似相关实则不相关”的片段。

为什么需要 Re-ranking?

向量检索是”单向”的:只看文档语义,不看查询意图。Re-ranking 做”双向”判断,能识别”登录失败”和”登录功能”的差别。

工作原理

# Re-ranking 流程(伪代码)

# 第一步:向量检索(快速,但不够精确)
query_embedding = embed_model.encode(user_query)
top_n_chunks = vector_db.search(query_embedding, top_k=20) # 检索 Top 20

# 第二步:Re-ranking(慢,但精确)
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
scores = []
for chunk in top_n_chunks:
# 计算查询和文档的交互得分
score = reranker.predict([user_query, chunk.text])
scores.append((score, chunk))

# 第三步:按得分排序,取 Top K
top_k_chunks = sorted(scores, reverse=True)[:5] # 取 Top 5

效果对比

方法 精确率 速度 适用场景
仅向量检索 中等 对精度要求不高的场景
向量检索 + Re-ranking 中等 生产环境,对精度要求高

💡 实战提示

  • 检索阶段:用向量检索快速筛选(Top 20-50)
  • 重排阶段:用 Cross-Encoder 精确排序(Top 5-10)
  • 平衡点:在精度和速度之间找到平衡

3.2 Query Transformation(查询转换):提升召回率

用户问”这个功能怎么用?”——向量库不知道”这个”指什么。或者只输入”登录”,太短,召回不全。

查询转换用 LLM 把模糊、依赖上下文的查询改写成独立完整的句子,提升召回率。

查询重写(Query Rewriting)

场景:用户问”这个功能怎么用?”,需要结合历史对话才知道”这个”指登录功能

方法:使用 LLM 将依赖上下文的查询转为独立完整的句子。

示例

# 查询重写(伪代码)

# 原始查询(依赖上下文)
user_query = "这个功能怎么用?"
conversation_history = [
"用户:我想了解用户登录功能",
"助手:用户登录功能支持邮箱和手机号登录..."
]

# 使用 LLM 重写查询
rewritten_query = llm.generate(f"""
请将以下查询改写为独立完整的句子,不依赖上下文:

历史对话:
{conversation_history}

当前查询:{user_query}

改写后的查询:
""")

# 输出:用户登录功能怎么用?

查询扩展(Query Expansion / RAG-Fusion)

场景:用户查询简短,可能遗漏相关关键词。

方法:添加同义词或相关关键词,生成多个查询版本,分别检索后合并结果。

示例

# 查询扩展(伪代码)

# 原始查询
user_query = "登录"

# 使用 LLM 扩展查询
expanded_queries = llm.generate(f"""
请为以下查询生成3个相关的查询变体:

原始查询:{user_query}

查询变体:
1.
2.
3.
""")

# 输出:
# 1. 用户登录
# 2. 账号登录
# 3. 登录验证

# 分别检索每个查询
all_results = []
for query in [user_query] + expanded_queries:
results = vector_db.search(embed(query))
all_results.extend(results)

# 合并并去重
final_results = merge_and_deduplicate(all_results)

效果对比

方法 召回率 计算成本 适用场景
原始查询 查询已经很完整
查询重写 查询依赖上下文
查询扩展 查询简短,需要高召回率

💡 实战提示

  • 简单查询:直接使用原始查询
  • 依赖上下文:使用查询重写
  • 需要高召回率:使用查询扩展

3.3 RAG 与 Agent 的集成(Multi-Hop RAG)

场景:多步骤推理任务,单轮 RAG 无法回答,如”总结 A 文档对 B 项目的影响”。

机制:Agent 将复杂任务拆成子问题,分别调用 RAG 检索知识,收集 Observation 后综合生成最终答案。

为什么需要 Multi-Hop RAG?

单轮 RAG 的局限

  • 一次检索只能回答一个问题
  • 无法处理需要多步骤推理的复杂问题
  • 无法融合多个文档的知识

Multi-Hop RAG 的优势

  • 支持多步骤推理
  • 可以融合多个文档的知识
  • 可以处理复杂的决策任务

工作原理

# Multi-Hop RAG 示例(伪代码)

# 用户查询
user_query = "总结用户登录功能文档对测试平台项目的影响"

# Agent 拆解任务
sub_questions = agent.plan(user_query)
# 输出:
# 1. 用户登录功能文档的主要内容是什么?
# 2. 测试平台项目的当前状态是什么?
# 3. 用户登录功能如何影响测试平台?

# 多轮检索和推理
observations = []
for question in sub_questions:
# 检索相关文档
relevant_docs = rag.retrieve(question)

# 生成观察结果
observation = llm.generate(f"""
问题:{question}

相关文档:
{relevant_docs}

请基于文档回答这个问题:
""")
observations.append(observation)

# 综合所有观察结果,生成最终答案
final_answer = llm.generate(f"""
原始问题:{user_query}

子问题和答案:
{observations}

请综合以上信息,回答原始问题:
""")

与 ReAct 结合

Multi-Hop RAG 可以与 ReAct 思维链结合,让 LLM 在每一步检索后都进行推理与自我修正。

示例

Thought: 用户问的是"总结用户登录功能文档对测试平台项目的影响",这需要:
1. 先检索用户登录功能文档
2. 再检索测试平台项目文档
3. 最后综合分析两者的关系

Action: retrieve(query="用户登录功能文档")

Observation: [检索到的文档内容]

Thought: 现在我了解了用户登录功能的内容,接下来需要检索测试平台项目的信息。

Action: retrieve(query="测试平台项目当前状态")

Observation: [检索到的文档内容]

Thought: 现在我有足够的信息来回答问题了。

Final Answer: [综合两个文档的信息,生成最终答案]

💡 实战提示

  • 简单问题:使用单轮 RAG
  • 复杂问题:使用 Multi-Hop RAG
  • 需要推理:结合 ReAct 思维链

🔍 总结:RAG 的工程化价值

RAG 将 LLM 从”静态百科全书”升级为动态知识专家,解决了 LLM 的两大核心问题:

RAG 的核心价值

问题 传统 LLM RAG 解决方案
知识时效性 训练数据截止到某个时间点 实时访问最新信息和私有数据
知识范围 只能使用训练时的数据 可以访问企业私有文档、内部知识库
幻觉问题 可能编造错误信息 答案可追溯、可验证,有据可查
上下文限制 Context Window 有限 通过检索只注入相关文档,突破限制

构建高效 RAG 系统的关键环节

  1. 分块(Chunking):保证语义完整性,选择合适的分块策略
  2. 向量化(Embedding):选择合适的 Embedding 模型,确保检索精度
  3. 索引(Vector Database):选择高性能向量数据库,支持大规模检索
  4. 后处理(Re-ranking、Query Transformation):提升精确率和召回率

RAG 与 Agent 的结合

RAG 不仅是知识检索工具,更是 Agent 的”知识库”:

  • 单轮 RAG:回答简单问题,提供知识支持
  • Multi-Hop RAG:处理复杂推理任务,融合多文档知识
  • RAG + ReAct:结合思维链,实现可控的知识检索和推理

💡 核心理解:RAG 是 Agent 时代高可信度回答的基石。掌握了 RAG,你就掌握了让 LLM 访问外部知识、突破知识限制的关键技术。


📚 延伸阅读(含可直接访问链接)

以下资源按主题分类,每个资源都附有简要说明,帮助你选择合适的学习材料。

🔍 RAG 综述与原理

🧱 Chunking(分块)

🔢 Embedding(嵌入)

🗄️ Vector Database(向量数据库)

🚀 进阶优化

🛠️ 实战框架

  • LangChain RAG 教程:LangChain 的 RAG 实战教程,包含完整示例。强烈推荐,适合想快速上手的开发者。

  • LlamaIndex RAG 教程:LlamaIndex 的 RAG 教程,包含多种检索策略。适合想系统学习的开发者。

  • Haystack RAG 教程:Haystack 的 RAG 教程,包含完整流程。适合使用 Haystack 的开发者。


🔔 系列说明

本文是《🧠 LLM/Agent 从入门到精通:告别浅尝辄止》系列第 4 篇。上一篇:想要 JSON 却得到废话?Prompt 工程的三大核心技巧。下一篇:GPT-4、Claude、Llama 怎么选?模型选型避坑指南 —— 参数量、推理速度、成本如何权衡?