上一篇:速通 AI(六):LLM 应用开发 — 提示词工程、LangChain、OpenAI API
目标:深入掌握 RAG 检索增强生成的完整体系——向量数据库、高级检索策略、GraphRAG、评估体系。
前置要求:上一篇(LangChain 基础、Embedding 概念、RAG 链入门)
本阶段知识依赖图
flowchart TD
A["LangChain + RAG入门(上一篇)"]
A --> E["RAG系统深入(核心)"]
E -->|PDF/Markdown解析| E2[文档加载与切片]
E -->|BGE-Large/BM25| E4[Embedding]
E -->|Milvus/FAISS/Chroma| E1[向量数据库]
E -->|高级检索/自适应RAG| E5[检索策略]
E -->|Corrective RAG| E6[自我纠正]
E -->|Adaptive RAG| E7[自适应检索]
A --> F["向量数据库深入"]
F -->|HNSW/IVF| F1[底层原理]
F -->|选型对比| F2[数据库选型]
A --> G["GraphRAG"]
G -->|知识图谱增强| G1[核心思想]
G -->|传统RAG对比| G2[优势分析]
A --> H["Advanced RAG"]
H -->|检索优化| H1[策略]
H -->|生成优化| H2[策略]
H -->|评估体系| H3[RAG评估]
1. RAG 系统深入——检索增强生成
为什么需要 RAG ?——大模型的三大硬伤
- 硬伤 1 :知识截止日期
- GPT-4 的知识截止到 2024 年 4 月
- 问它“今天的新闻” → 完全不知道
- 硬伤 2 :幻觉(Hallucination)
- 大模型会“一本正经地胡说八道”
- 问它一个它不知道的事实 → 可能编造看起来合理但错误的答案
- 硬伤 3 :企业私有数据
- 大模型从未见过你公司的内部文档、产品手册、客户数据
- 问它“我们公司的退货政策是什么” → 完全不知道
RAG 的解决方案:不让大模型“凭记忆回答”,而是先帮它“查资料”,再让它“基于资料回答”。
- 知识可以实时更新(查最新的资料)
- 减少幻觉(有据可依)
- 可以访问私有数据(先检索企业文档)
RAG 的完整流程——每一步详解
flowchart LR
subgraph Offline["离线索引(一次性)"]
D[文档] --> S[文本切分]
S --> E[Embedding]
E --> V[(向量数据库)]
end
subgraph Online["在线查询(每次)"]
Q[用户问题] --> QE[Query Embedding]
QE --> Search[相似度搜索]
V --> Search
Search --> R[Top-K 文档块]
R --> P[Prompt 组装]
Q --> P
P --> LLM[LLM]
LLM --> A[回答]
end
Step 1 :文档处理(离线,一次性完成)
- PDF/Word/Markdown → 文本提取 → 文本切分 → Embedding → 存入向量数据库
Step 2 :检索(在线,每次查询时执行)
- 用户问题 → 问题 Embedding → 向量数据库中搜索最相似的文档块
Step 3 :生成(在线,每次查询时执行)
- 检索到的文档块 + 用户问题 → 组装提示词 → 发送给 LLM → 生成回答
类比:
- Step 1 = 把图书馆的书整理好,建立索引
- Step 2 = 根据读者的问题,从图书馆找出相关的书
- Step 3 = 让 AI 助手阅读这些书,然后回答读者的问题
数据加载与切片——决定 RAG 质量的关键
# PDF解析
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("enterprise_doc.pdf")
docs = loader.load()
# Markdown解析
from langchain_community.document_loaders import UnstructuredMarkdownLoader
loader = UnstructuredMarkdownLoader("readme.md")
# 语义切分(推荐)
from langchain_experimental.text_splitter import SemanticChunker
splitter = SemanticChunker(OpenAIEmbeddings())
chunks = splitter.split_documents(docs)
切分方式对比:
- 固定长度切分:每 500 个字符切一刀
- 优点:简单
- 缺点:可能在句子中间切断,语义不完整
- 递归切分(RecursiveCharacterTextSplitter):按段落 → 句子 → 词的优先级切
- 优点:尽量在自然边界处切分
- 缺点:块大小不均匀
- 语义切分(SemanticChunker):根据语义相似度自动找到切分点
- 优点:每个块语义完整
- 缺点:计算量较大(需要调用 Embedding 模型)
实际推荐:
- 快速原型:用递归切分
- 生产环境:用语义切分
- 特定格式:用专门的解析器(如 Markdown 用 MarkdownHeaderTextSplitter)
向量数据库——存储和检索的"仓库"
| 数据库 | 类型 | 适用场景 | 特点 |
|---|---|---|---|
| FAISS | 内存型 | 快速原型/小数据 | 速度快,需手动调用 faiss.write_index() 持久化 |
| Chroma | 嵌入型 | 本地开发/小项目 | 简单易用,自动持久化到磁盘 |
| Milvus | 服务型 | 生产环境/大数据 | 分布式、高性能、企业级 |
| Pinecone | 云服务 | 无需运维 | 托管服务,按量付费 |
类比:
- FAISS = 纸质便签——快速方便,但容易丢
- Chroma = 笔记本——持久保存,但容量有限
- Milvus = 数据中心——高性能、高可靠,但需要运维
- Pinecone = 云存储——不用操心基础设施,但要付费
高级检索策略——提升 RAG 效果的关键
# 1. 混合检索(Hybrid Search):语义 + 关键词
# 类比:搜索时同时考虑"意思相近"和"关键词匹配"
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
bm25_retriever = BM25Retriever.from_documents(chunks) # 关键词检索
vector_retriever = vectorstore.as_retriever() # 语义检索
# 70%语义 + 30%关键词
ensemble_retriever = EnsembleRetriever(
retrievers=[vector_retriever, bm25_retriever],
weights=[0.7, 0.3]
)
# 为什么需要混合检索?
# 纯语义检索的问题:可能检索到语义相似但关键词不匹配的文档
# 纯关键词检索的问题:可能漏掉语义相关但用词不同的文档
# 混合检索取长补短
# 2. 多查询检索(Multi-Query):一个问题,多种表述
# 类比:搜索时同时用多个关键词
from langchain.retrievers import MultiQueryRetriever
retriever = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(),
llm=llm
)
# 原始问题:"什么是RAG?"
# 自动生成多个变体:
# "解释检索增强生成技术"
# "RAG的定义和原理"
# "描述RAG的工作流程"
# 合并所有查询的检索结果 → 召回率大幅提升
# 3. 上下文压缩(Contextual Compression):只保留相关内容
# 类比:从一本书中只摘抄和问题相关的段落
from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank
compressor = CohereRerank()
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vectorstore.as_retriever()
)
# 检索到的文档块可能包含大量无关信息
# 压缩后只保留与问题最相关的部分 → 减少噪音,提高回答质量
Corrective RAG——自我纠正
标准 RAG 的问题:检索到的文档可能不相关,但模型仍会基于这些文档生成回答(“垃圾进,垃圾出”)。
Corrective RAG 的改进流程:
- 检索文档
- 评估文档与问题的相关性(用 LLM 判断)
- 如果相关性低 → 用网络搜索补充更相关的信息
- 如果相关性高 → 直接使用
- 生成回答后,再评估回答的质量
- 如果质量不达标 → 重新检索或重新生成
类比:一个负责任的研究员
- 普通 RAG = 随便找几本书就回答
- Corrective RAG = 先检查找的书是否相关,不相关就换一批,回答后还要检查质量
Adaptive RAG——自适应检索
核心思想:不同类型的问题需要不同的处理方式。
- 简单事实问题:“法国的首都是哪?”
- 大模型自己就知道,不需要检索
- 直接回答,省时省力
- 复杂知识问题:“比较 RAG 和微调的优劣”
- 需要检索相关文档
- 基于检索结果回答
- 推理问题:“如果 A > B , B > C ,那么 A 和 C 的关系?”
- 不需要检索(这不是知识问题,是推理问题)
- 让模型用思维链推理
Adaptive RAG = 先判断问题类型 → 再选择最合适的处理方式 → 效率最高、效果最好。
RAG vs 微调 vs 提示词工程决策表
| 考量维度 | 提示词工程 | RAG | 微调 |
|---|---|---|---|
| 开发成本 | 最低(几小时) | 中等(几天) | 最高(几周) |
| 数据需求 | 无需数据 | 需要文档库 | 需要标注数据 |
| 计算成本 | 极低 | 低(检索+生成) | 高(训练+推理) |
| 知识更新 | 不支持 | 实时更新 | 需重新训练 |
| 适用场景 | 简单任务、快速原型 | 知识问答、文档查询 | 专业领域、格式要求 |
| 推理延迟 | 无额外延迟 | 有检索延迟 | 无额外延迟 |
| 推荐优先级 | 先试这个 | 其次考虑 | 最后考虑 |
模块小结: RAG 系统
| 你学到了什么 | 为什么重要 |
|---|---|
| RAG 的三大硬伤 | 知识截止、幻觉、私有数据 |
| RAG 完整流程 | 文档处理→检索→生成 |
| 数据切分策略 | 固定长度 vs 递归 vs 语义 |
| 高级检索策略 | 混合检索、多查询、压缩 |
| Corrective/Adaptive RAG | 自我纠正、自适应检索 |
2. 向量数据库深入
向量搜索的底层原理
类比:在图书馆找"最相似的书"
- 传统数据库(MySQL):精确匹配
SELECT * FROM books WHERE title = '深度学习'- 只能找到标题完全匹配的书
- 向量数据库:语义相似度搜索
- “找到和‘深度学习’含义最接近的书”
- 能找到“神经网络”“机器学习入门”“TensorFlow 实战”等相关书籍
向量搜索的核心算法:
- 暴力搜索(Brute Force)
- 计算查询向量和所有向量的距离 → 取最近的 k 个
- 精度: 100% (不会漏掉任何结果)
- 速度:$O(n\times d)$,$n$=向量数量,$d$=维度
- 问题:数据量大时极慢(100 万个 1536 维向量需要计算 15 亿次)
- 近似最近邻(ANN)
- 用一些“聪明的策略”加速搜索,牺牲少量精度换取大量速度
HNSW——最常用的 ANN 算法
HNSW (Hierarchical Navigable Small World)= 分层可导航小世界图。
类比:找人的社交网络
- 第 1 层(稀疏层):只有“超级节点”(明星、大 V)→ 快速定位大致区域
- 第 2 层(中等层):普通节点 → 缩小范围
- 第 3 层(密集层):所有节点 → 精确定位
搜索过程:
- 从最顶层的某个节点开始
- 在当前层找到最近的邻居
- 跳到下一层,继续找最近的邻居
- 重复直到最底层 → 找到最近的向量
精度: 95-99% (偶尔会漏掉,但几乎不影响结果)。 速度:$O(\log n)$,比暴力搜索快几个数量级。
动手计算:HNSW 搜索过程示例
假设 6 个向量(A-F),构建 3 层索引:
Layer 0(稀疏层):A ———————— D
Layer 1(中间层):A —— C —— D —— F
Layer 2(密集层):A — B — C — D — E — F(所有节点互连)
搜索 query 最近邻:
- 从 Layer 0 的 A 开始 → A 到 D 的距离更近 → 跳到 D
- 在 Layer 1 从 D 出发 → 检查 C、F → C 最近 → 跳到 C
- 在 Layer 2 从 C 出发 → 检查 B、D → B 最近 → 返回 B
只需检查 5 个节点(而非暴力搜索的全部 6 个)。当 $n=100$ 万时,HNSW 只需检查 $\log(100万) \approx 20$ 个节点。
IVF——另一种常用算法
IVF (Inverted File Index)= 倒排文件索引。
类比:图书馆的分区
- 把所有书按主题分成 100 个区域
- 找书时先确定在哪个区域,再在区域内搜索
- 不需要翻遍整个图书馆
实现:
- 训练时:用 K-Means 把向量分成若干个聚类(Voronoi cells)
- 搜索时:先找到查询向量最近的几个聚类,只在这些聚类中搜索
- 参数 nprobe :搜索多少个聚类(越大越精确,越慢)
动手计算:IVF 检索示例
假设 12 个向量,K-Means 分成 3 个聚类:
| 聚类 | 包含的向量 |
|---|---|
| 聚类 1(中心 [0.2, 0.3]) | A, B, C, D |
| 聚类 2(中心 [0.8, 0.7]) | E, F, G, H |
| 聚类 3(中心 [0.5, 0.9]) | I, J, K, L |
查询向量 $q = [0.7, 0.8]$,距离各聚类中心:
- 到聚类 1:$\sqrt{(0.7-0.2)^2+(0.8-0.3)^2} = 0.71$
- 到聚类 2:$\sqrt{(0.7-0.8)^2+(0.8-0.7)^2} = 0.14$ ← 最近
- 到聚类 3:$\sqrt{(0.7-0.5)^2+(0.8-0.9)^2} = 0.22$
nprobe=1:只搜索聚类 2(4 个向量),暴力搜索的 1/3
nprobe=2:搜索聚类 2 + 聚类 3(8 个向量),更精确但更慢
与暴力搜索对比:12 个向量暴力搜索需比较 12 次,IVF(nprobe=1) 只需比较 4 次 + 3 次聚类中心 = 7 次。当 $n=100$ 万、聚类数=1000 时,IVF 只需搜索 1000 个向量(0.1%),速度提升 1000 倍。
量化压缩
PQ (Product Quantization)= 乘积量化。
类比:用“代表色”代替所有颜色
- 一张图片有 1600 万种颜色
- 用 256 种“代表色”来近似 → 存储空间大幅减少
实现:
- 把 1536 维向量切成多段(如 8 段,每段 192 维)
- 每段用一个“码本”(codebook)中的最近码字代替
- 原始向量: 1536 × 4 bytes = 6 KB
- 量化后: 8 × 1 byte = 8 bytes → 压缩 768 倍
向量数据库选型深入
| 数据库 | 索引算法 | 持久化 | 分布式 | 适用场景 |
|---|---|---|---|---|
| FAISS | HNSW/IVF/PQ | ❌ 内存 | ❌ 单机 | 快速原型、小数据 |
| Chroma | HNSW | ✅ 磁盘 | ❌ 单机 | 本地开发、小项目 |
| Milvus | HNSW/IVF/DiskANN | ✅ | ✅ 分布式 | 生产环境、大数据 |
| Qdrant | HNSW | ✅ 磁盘 | ✅ 分布式 | 新兴选择, API 友好 |
| Weaviate | HNSW | ✅ 磁盘 | ✅ 分布式 | GraphQL 接口 |
| Pinecone | 托管 | ✅ 云 | ✅ 云 | 无需运维,按量付费 |
选型建议:
- 学习和原型: FAISS (最快上手)
- 小型生产: Chroma 或 Qdrant
- 大型生产: Milvus (最成熟)或 Qdrant
- 不想运维: Pinecone
模块小结:向量数据库
| 你学到了什么 | 为什么重要 |
|---|---|
| 向量搜索原理 | 语义相似度检索的基础 |
| HNSW 算法 | 最常用的 ANN 算法 |
| IVF 倒排索引 | 聚类加速搜索 |
| PQ 乘积量化 | 压缩向量,节省存储 |
| 数据库选型 | 不同场景的最佳选择 |
3. GraphRAG
什么是 GraphRAG ?
类比:从"图书馆"到"知识图谱"
- 传统 RAG = 图书馆找书
- 把文档切成片段,用向量搜索找最相关的片段
- 问题:片段之间没有“关系”——不知道 A 片段和 B 片段有什么联系
- GraphRAG = 知识图谱 + 向量搜索
- 不仅找到相关片段,还找到片段之间的“关系”
- 能回答需要“综合多个信息源”的复杂问题
GraphRAG 的核心思想
- 从文档中提取实体和关系
- 文档:“张三是百度的 CTO ,百度是李彦宏创办的公司”
- 实体:张三、百度、李彦宏
- 关系:(张三, 是 CTO, 百度), (百度, 创办者, 李彦宏)
- 构建知识图谱
- 社区检测
- 把紧密相关的实体聚成“社区”
- {张三, 百度, 李彦宏} 是一个社区
- 为每个社区生成摘要
- “百度是一家由李彦宏创办的公司,张三担任 CTO”
- 检索时同时搜索向量和图谱
- 问题:“张三在哪家公司工作?”
- 向量搜索找到相关文档片段
- 图谱搜索找到张三 → 百度的关系
- 综合回答:“张三在百度担任 CTO”
flowchart TD
A[提取实体与关系] --> B[构建知识图谱]
B --> C[社区检测]
C --> D[社区摘要]
D --> E["检索:向量 + 图谱"]
E --> F[综合回答]
GraphRAG vs 传统 RAG
| 特性 | 传统 RAG | GraphRAG |
|---|---|---|
| 数据结构 | 文档片段(扁平) | 知识图谱(结构化) |
| 检索方式 | 向量相似度 | 向量 + 图遍历 |
| 多跳推理 | 困难 | 自然支持 |
| 全局摘要 | 不支持 | 支持(社区摘要) |
| 适用场景 | 单文档问答 | 多文档、复杂关系推理 |
| 复杂度 | 低 | 高(需要构建图谱) |
GraphRAG 的实现
Microsoft 的 GraphRAG 实现:
示例:8 个实体、8 条关系的知识图谱
张三 --CTO--> 百度 <--创办-- 李彦宏
张三 --同事--> 李四 百度 --竞品--> 谷歌
谷歌 --CEO--> 桑达尔 谷歌 --总部--> 山景城
百度 --总部--> 北京 李四 --工程师--> 百度
社区检测后聚为 2 个社区:
- 社区 1:{张三, 百度, 李彦宏, 李四, 北京} → “百度由李彦宏创办,总部在北京,张三任 CTO”
- 社区 2:{谷歌, 桑达尔, 山景城} → “谷歌由桑达尔领导,总部在山景城”
传统 RAG 找不到"百度和谷歌的关系"(没有文档片段同时提到两家公司)。GraphRAG 通过图谱中的"百度—竞品—谷歌"直接回答。
- 使用 LLM 从文档中提取实体和关系
- 构建知识图谱
- 使用 Leiden 算法进行社区检测
- 为每个社区生成 LLM 摘要
- 检索时:局部搜索(向量 + 图谱)+ 全局搜索(社区摘要)
适用场景:
- “这个公司的组织架构是什么?” → 需要从多份文档中综合信息
- “这些产品的共同竞争对手是谁?” → 需要跨文档推理
- “总结一下这个领域的最新进展” → 需要全局视角
模块小结: GraphRAG
| 你学到了什么 | 为什么重要 |
|---|---|
| GraphRAG 核心思想 | 知识图谱 + 向量搜索 |
| 实体关系提取 | 从文档构建图谱 |
| 社区检测与摘要 | 全局视角的信息聚合 |
| GraphRAG vs 传统 RAG | 结构化 vs 扁平化 |
4. Advanced RAG 工程实践
为什么需要"高级"RAG ?
基础 RAG 的问题:
- 检索质量不稳定——有时找到的文档不相关
- 生成质量参差——有时回答不准确或“幻觉”
- 系统鲁棒性差——输入格式变化就可能出错
- 评估困难——不知道效果好不好,怎么改进
Advanced RAG = 在每个环节都做优化。
检索优化策略
- 查询改写(Query Rewriting)
- 原始问题:“这个东西怎么用?”
- 改写后:“XX 产品的使用方法和操作步骤”
- 让问题更明确,检索更精准
- 查询扩展(Query Expansion)
- 原始问题:“RAG”
- 扩展为:[“RAG”, “检索增强生成”, “Retrieval Augmented Generation”]
- 多角度检索,提高召回率
- 假设文档嵌入(HyDE)
- 先让 LLM 生成一个“假设的回答”
- 用这个假设回答去检索(而不是用问题检索)
- 假设回答和真实文档的语义更接近
- 上下文压缩(Contextual Compression)
- 检索到的文档块可能很长,只有部分与问题相关
- 用 LLM 提取出与问题最相关的部分
- 减少噪音,提高回答质量
生成优化策略
- 提示词优化
- 明确告诉模型“只基于提供的资料回答”
- 要求模型“如果不确定就说不知道”
- 指定输出格式(如“先总结再详细说明”)
- 引用追溯
- 要求模型在回答中标注信息来源
- “根据文档 A 第 3 段…”、“根据文档 B…”
- 用户可以验证回答的准确性
- 多轮验证
- 生成回答后,用另一个 LLM 调用检查回答是否与原文一致
- 类似于“编辑审查”流程
RAG/Agent 评估体系
面试必问:“你怎么衡量 RAG 的效果?”
RAG 评估的三个维度:
- 检索质量(Retrieval Quality)
动手计算:检索评估指标数值示例
假设用户问"什么是 RAG?",数据库中有 5 个相关文档,检索系统返回 Top-3 结果:
| 排名 | 文档 | 是否相关 |
|---|---|---|
| 1 | 文档 A(介绍 RAG 原理) | ✅ 相关 |
| 2 | 文档 B(介绍 Transformer) | ❌ 不相关 |
| 3 | 文档 C(RAG 实现代码) | ✅ 相关 |
Recall@3 = 检索到的相关文档数 / 全部相关文档数 = $2/5 = 0.4$(5 个相关文档只找到了 2 个)
$$\text{Recall@k} = \frac{|\{\text{相关文档}\} \cap \{\text{Top-k 结果}\}|}{|\{\text{全部相关文档}\}|}$$Precision@3 = 检索结果中相关的数 / k = $2/3 = 0.67$(Top-3 中有 2 个相关)
$$\text{Precision@k} = \frac{|\{\text{相关文档}\} \cap \{\text{Top-k 结果}\}|}{k}$$MRR(Mean Reciprocal Rank)= 第一个相关文档的排名的倒数。第一个相关文档排第 1,所以 $MRR = 1/1 = 1.0$。如果第一个相关文档排第 2,则 $MRR = 1/2 = 0.5$。
$$\text{MRR} = \frac{1}{|Q|}\sum_{i=1}^{|Q|}\frac{1}{\text{rank}_i}$$NDCG(Normalized Discounted Cumulative Gain):考虑排名位置——排第 1 的相关文档比排第 10 的更有价值。
$$\text{DCG} = \sum_{i=1}^{k}\frac{2^{\text{rel}_i}-1}{\log_2(i+1)}, \quad \text{NDCG} = \frac{\text{DCG}}{\text{IDCG}}$$以上例计算:$DCG = \frac{2^1-1}{\log_2(2)} + \frac{2^0-1}{\log_2(3)} + \frac{2^1-1}{\log_2(4)} = 1 + 0 + 0.5 = 1.5$。理想排序(相关文档排前 2)的 $IDCG = 1 + 0.63 = 1.63$。$NDCG = 1.5/1.63 = 0.92$。 2. 生成质量(Generation Quality)
- Faithfulness (忠实度):回答是否基于检索到的文档(没有编造)
- Relevance (相关性):回答是否和问题相关
- Correctness (正确性):回答是否正确
- Completeness (完整性):回答是否完整
- 端到端质量(End-to-End)
- Answer Correctness :最终回答是否正确
- Answer Relevance :最终回答是否和问题相关
评估工具:
- RAGAS:最流行的 RAG 评估框架
- 自动生成评估数据集
- 计算 Faithfulness 、 Relevance 、 Context Precision 等指标
- 代码示例:
from ragas import evaluate
result = evaluate(dataset, metrics=[faithfulness, answer_relevancy])
Agent 评估:
Agent 评估比 RAG 更复杂,因为 Agent 涉及多步决策:
- 任务完成率: Agent 是否完成了用户交给它的任务?
- 工具调用准确率: Agent 是否选择了正确的工具?
- 参数正确率: Agent 传给工具的参数是否正确?
- 步骤效率: Agent 用了多少步完成任务?(越少越好)
- 错误恢复: Agent 遇到错误时能否自动修正?
评估方法:
- 人工评估:找人来判断 Agent 的表现(金标准,但成本高)
- 自动评估:用另一个 LLM 来评判 Agent 的表现(成本低,但可能不准)
- 基准测试:用标准化的测试集来评估(可比较,但覆盖有限)
RAG 的三代演进与完整 Pipeline
一个生产级 RAG 系统经历了三代演进:
| 代际 | 名称 | 核心思路 | 问题 |
|---|---|---|---|
| 第一代 | Naive RAG | 文档切分→向量检索→生成 | 检索精度低、chunk 切分丢失上下文 |
| 第二代 | Advanced RAG | 在 Naive 基础上增加查询改写、混合检索、重排、自纠错 | 流程固定,无法动态决策 |
| 第三代 | Agentic RAG | 用 Agent 动态决策:是否需要检索、检索什么、是否需要重试 | 复杂度高、成本高 |
第二代 Advanced RAG 的完整 Pipeline:
flowchart TD
subgraph "索引阶段(离线)"
A["原始文档"] --> B["文档解析<br/>PDF/表格/扫描件"]
B --> C["Chunk 切分"]
C --> D["Embedding 编码"]
D --> E["存入向量数据库 + BM25 索引"]
end
subgraph "查询阶段(在线)"
F["用户查询"] --> G["查询改写<br/>Multi-Query / HyDE / Step-back"]
G --> H["混合检索<br/>稠密 + 稀疏 → RRF 融合"]
H --> I["Reranker 重排"]
I --> J["LLM 生成"]
end
第三代 Agentic RAG 将在第八篇补充章节中详细介绍。
Chunk 策略进阶
Chunk 是 RAG 效果的第一大决定因素。切得不好,即使检索"召回"了正确的文档,chunk 边界破坏了信息完整性,LLM 拿到残缺的上下文,自然回答不好。
Chunk 大小的影响:
| 问题 | 原因 | 后果 |
|---|---|---|
| Chunk 太大 | 噪声多,向量表示被稀释 | LLM 被不相关信息误导,消耗更多 token |
| Chunk 太小 | 缺乏上下文,信息不完整 | “张三在阿里"和"巴巴工作了10年"被切断 |
| 切得不好 | 在语义中间切断 | 即使检索命中,信息也是残缺的 |
方法 1:语义切分(Semantic Chunking)
在"语义发生变化"的地方切分,而非固定位置。步骤:将文档按句子切分→对每个句子做 Embedding→计算相邻句子的余弦相似度→相似度骤降的位置 = 切分点。
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
chunker = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=95 # 相似度降到 5% 分位数以下就切
)
# document 为待切分的原始文本字符串
chunks = chunker.split_text(document)
方法 2:Parent-Child Chunking(生产环境最推荐)
解决了核心矛盾:检索需要小 chunk(向量表示精准),生成需要大 chunk(上下文完整)。
flowchart TD
P["Parent Chunk(大块,如 2000 字)"]
P --> C1["Child Chunk 1(小块,如 300 字)← 用于检索"]
P --> C2["Child Chunk 2(小块,如 300 字)← 用于检索"]
P --> C3["Child Chunk 3(小块,如 300 字)← 用于检索"]
C1 --> R["检索命中 Child 1"]
R --> Q["返回其 Parent Chunk"]
Q --> L["Parent 送入 LLM"]
检索流程:用 Child Chunk 的向量做检索(小块→精准)→命中后返回其 Parent Chunk(大块→完整上下文)→Parent Chunk 送入 LLM。
方法 3:基于文档结构的切分
对于有明确结构的文档(Markdown、HTML、代码),按标题层级切分(#→##→###),保留元数据(标题路径、来源、页码)。
Chunk 大小选择经验:
| 场景 | 推荐 chunk 大小 | 推荐 overlap |
|---|---|---|
| 精确问答(FAQ) | 200-500 字 | 50-100 字 |
| 知识库检索 | 500-1000 字 | 100-200 字 |
| 长文档分析 | 1000-2000 字 | 200-400 字 |
| 代码检索 | 按函数/类切分 | 不重叠 |
核心原则:先看数据→先做评估→用数据说话。
Embedding 模型选型
Embedding = 把文本映射到高维空间中的一个向量。核心性质:语义相似的文本→空间中距离近的向量。
Embedding 算法演进:
| 代际 | 代表方法 | 核心思路 | 局限 |
|---|---|---|---|
| 第一代 | Word2Vec / GloVe | 静态词向量,每个词只有一个固定向量 | 无法处理多义词(“苹果”=水果还是公司?) |
| 第二代 | Sentence-BERT | 用 BERT 编码句子,对比学习训练 | 主要面向英文,中文效果一般 |
| 第三代 | BGE-M3 / E5 / GTE | 多语言、多任务、对比学习训练 | 模型较大,推理成本高 |
Word2Vec 的两种训练方式:
- CBOW(Continuous Bag of Words):用上下文预测中心词。输入"我 _ 大模型”,预测空格是"爱"
- Skip-gram:用中心词预测上下文。输入"爱",预测周围可能出现"我"、“大模型”
从词向量到句向量的演进:早期方法简单平均词向量(信息稀释)→BERT 用 [CLS] token 的输出作为句向量→现代方法用对比学习训练(正样本对拉近,负样本对推远),直接优化句向量的语义区分度。
Bi-Encoder 架构(几乎所有 Embedding 模型都是):查询和文档分别独立编码,查询时只需编码查询 + 向量检索。优点是文档向量可以预计算存储,缺点是查询和文档独立编码,缺乏交互信息(Reranker 补这个短板)。
主流 Embedding 模型对比(2026):
| 模型 | 维度 | 中文 | 英文 | 适用场景 |
|---|---|---|---|---|
| BGE-M3(BAAI) | 1024 | 优秀 | 良好 | 中文首选,支持混合检索 |
| GTE-Qwen2(阿里) | 1024 | 优秀 | 良好 | 中文优秀,可微调 |
| E5-Mistral(微软) | 4096 | 一般 | 优秀 | 英文最佳之一 |
| Voyage-3(Voyage) | 1024 | 一般 | 优秀 | 代码检索特别好 |
| text-embedding-3(OpenAI) | 3072 | 良好 | 优秀 | 快速原型,支持维度缩减 |
Matryoshka Embedding(俄罗斯套娃 Embedding):训练时让前 $d$ 个维度也能独立工作,同一模型支持 256/512/1024/3072 等多种维度。数值示例:存储 1 亿向量×3072 维×float32 = 1.2 TB;存储 1 亿向量×256 维×float32 = 100 GB——12 倍的存储差异。
何时需要微调 Embedding:用户用行业术语查询(如"PD"在医学中是帕金森,在金融中是产品设计)、检索召回率始终低于预期、领域词汇的语义和通用含义差异大。
查询改写进阶
HyDE(Hypothetical Document Embeddings):先让 LLM"幻想"一个回答,用这个回答去检索。
flowchart LR
Q["用户问:什么是 LoRA?"] --> LLM["LLM 生成假设回答<br/>(可能不完全准确)"]
LLM --> V["用假设回答做向量检索"]
V --> D["检索到的真实文档片段"]
D --> G["LLM 基于真实文档生成最终回答"]
为什么有效?假设回答的向量表示更接近知识库中的文档向量,比短问题的向量表示更丰富。
Step-back Prompting:从具体问题退后到通用问题。例如原始查询"LLaMA 3.1 70B 用 LoRA 微调时 rank 设多少?"→退后为"LoRA 微调中 rank 参数的选择策略是什么?"→通用查询能检索到更全面的文档。
Reranker 重排模型
向量检索是"双编码器"架构(查询和文档分别编码,只做点积),快但不够精确。Reranker 是"交叉编码器"架构(将查询和文档拼接在一起送入模型),更精确但更慢。
| 维度 | Bi-Encoder(Embedding 检索) | Cross-Encoder(Reranker) |
|---|---|---|
| 架构 | 查询和文档独立编码 | 查询和文档拼接编码 |
| 速度 | 快(可预计算) | 慢(每次都要重新计算) |
| 精度 | 较低 | 较高 |
| 适用 | 全库检索(百万级) | 精排(top-50→top-5) |
工作流程:向量检索 top-50→Reranker 重排→取 top-5→送入 LLM。
RRF(Reciprocal Rank Fusion)融合公式:
$$ \text{RRF\_score}(d) = \sum_{i=1}^{n} \frac{1}{k + \text{rank}_i(d)} $$其中 $k$ 是常数(通常 = 60),$\text{rank}_i(d)$ 是文档 $d$ 在第 $i$ 个检索器中的排名。
手算示例:假设 3 个检索器对 5 个文档的排名如下:
| 文档 | 稠密检索排名 | BM25 排名 | Reranker 排名 |
|---|---|---|---|
| 文档 A | 1 | 3 | 2 |
| 文档 B | 2 | 1 | 1 |
| 文档 C | 3 | 2 | 4 |
| 文档 D | 4 | 5 | 3 |
| 文档 E | 5 | 4 | 5 |
取 $k=60$,RRF 计算:
- 文档 A:$\frac{1}{61} + \frac{1}{63} + \frac{1}{62} = 0.01639 + 0.01587 + 0.01613 = 0.04839$
- 文档 B:$\frac{1}{62} + \frac{1}{61} + \frac{1}{61} = 0.01613 + 0.01639 + 0.01639 = 0.04891$
文档 B 的 RRF 分数更高(在多个检索器中排名都靠前),最终排序 B>A。
代码示例:
from FlagEmbedding import FlagReranker
reranker = FlagReranker('BAAI/bge-reranker-v2-m3', use_fp16=True)
pairs = [
["什么是 LoRA 微调?", "LoRA 是一种参数高效微调方法..."],
["什么是 LoRA 微调?", "今天天气很好适合出去玩..."],
]
scores = reranker.compute_score(pairs)
# scores: [0.95, 0.02] → 第一个文档相关性远高于第二个
Self-RAG 与 Corrective RAG
传统 RAG 不管检索到的文档是否相关,都盲目地塞进 prompt。Self-RAG 让模型自己决定什么时候检索、检索结果是否有用。
Self-RAG 的反思 token 机制:
- 模型生成一段文字
- 判断:需要检索吗?→
[Retrieve]/[No Retrieve] - 如果需要检索→执行检索
- 判断:检索结果是否相关?→
[Relevant]/[Irrelevant] - 判断:生成的回答是否被检索结果支持?→
[Supported]/[Not Supported] - 如果不支持→重新生成
Corrective RAG 的思路:检索后加一层"纠错"。相关性高→直接用;相关性低→用 LLM 对文档做知识提炼,去掉不相关部分,或回退到网络搜索获取更相关的信息。
文档解析
实际项目中,RAG 最痛的不是检索不准,而是文档解析不好。
| 难点 | 说明 |
|---|---|
| 扫描版 PDF | 图片,没有文字层 |
| 包含表格的 PDF | 行列对应关系容易乱 |
| 包含公式的学术论文 | 公式无法直接提取 |
| 多列排版的文档 | 顺序容易错乱 |
解析方案分级:
| 级别 | 方案 | 适用场景 |
|---|---|---|
| 基础 | PyPDF / pdfplumber | 简单纯文字 PDF |
| 中级 | Unstructured.io | 企业文档、混合格式 |
| 高级 | 多模态大模型(GPT-4V、Qwen-VL) | 复杂布局、扫描件 |
| 表格 | Camelot / Tabula / 多模态 LLM | PDF 表格提取 |
模块小结: Advanced RAG 工程实践
| 你学到了什么 | 为什么重要 |
|---|---|
| 查询改写与扩展 | 提升检索精准度 |
| 生成优化策略 | 提升回答质量 |
| RAG 评估体系 | 衡量系统效果 |
| Agent 评估 | 评估智能体表现 |
| RAG 三代演进 | 理解从 Naive 到 Agentic 的演进路径 |
| Chunk 策略进阶 | RAG 效果的第一大决定因素 |
| Embedding 选型 | 选择合适的向量化模型 |
| Reranker 重排 | 两阶段检索提升精度 |
| Self-RAG | 让模型自判断检索质量 |
| 文档解析 | 生产环境最痛的环节 |
RAG 实际落地最难的地方
- 文档解析:扫描件 PDF、复杂表格、多列排版、公式等,解析质量直接决定 RAG 效果(详见上文)
- 评估标准:没有统一的"好"标准——Faithfulness 高不代表用户满意,需要结合人工评估
- 长尾 case:80% 的查询效果很好,但 20% 的长尾查询(如跨文档推理、否定查询、多条件筛选)效果很差
- 知识时效性:文档更新后,旧的向量还在数据库中,新旧信息冲突
- 成本控制:Embedding 编码 + 向量检索 + Reranker + LLM 生成,每一步都有成本,大规模系统需要精细优化
Lost in the Middle
当上下文很长时,LLM 倾向于关注开头和结尾的信息,忽略中间的内容。这就是"Lost in the Middle"现象。
影响:如果相关的文档 chunk 恰好被放在上下文的中间位置,LLM 可能"看不到"它,导致回答质量下降。
缓解策略:
- 重排(Rerank):将最相关的 chunk 放在上下文的最前面
- 减少上下文长度:只放最相关的 Top-K 个 chunk,减少中间噪声
- 分段处理:将长上下文分成多段,分别处理后合并结果
- 选择对 Lost in the Middle 鲁棒的模型:部分模型(如 Claude)对此问题的抵抗力更强
推荐补充资源
| 知识点 | 推荐资源 | 说明 |
|---|---|---|
| LLaMA | Meta LLaMA 论文 | 原始论文 |
| 提示词工程 | OpenAI Prompt Engineering Guide | 官方指南 |
| LangChain | LangChain 官方文档 | 最权威的参考 |
| RAG | LangChain RAG 教程 | 实战教程 |
| 向量数据库 | Milvus 官方文档 | 生产级向量数据库 |
| GraphRAG | Microsoft GraphRAG 仓库 | 官方实现 |
| RAG 优化 | Lilian Weng《Prompt Engineering》 | Prompt 工程综述 |
| Self-RAG | Self-RAG 论文(ICLR 2024) | 反思 token 机制 |
| Corrective RAG | Corrective RAG 论文 | 检索后纠错 |
| HyDE | HyDE 论文 | 假设文档嵌入 |
| Reranker | BGE Reranker | 开源重排模型 |
| 文档解析 | Unstructured.io | 多格式文档解析 |
| RAG 综述 | RAG 综述论文 | 全面了解 RAG 发展 |
下一篇预告:本文深入掌握了 RAG 检索增强生成。在速通 AI(八):Agent 开发与生产部署中,你将学习如何定制模型(PEFT 微调、量化)并构建能自主决策的智能体(Function Calling、MCP、LangGraph)。