RAG架构设计
1. RAG概述
检索增强生成(Retrieval-Augmented Generation, RAG)通过在生成之前检索相关文档,为LLM提供上下文信息,解决知识截断、幻觉和领域知识不足的问题。
1.1 为什么需要RAG
| 问题 | 纯LLM | RAG |
|---|---|---|
| 知识时效性 | 限于训练数据截止日期 | 可访问最新信息 |
| 幻觉 | 容易编造事实 | 基于检索到的事实回答 |
| 领域知识 | 泛化但不深入 | 可接入专业知识库 |
| 可追溯性 | 无法追溯信息来源 | 可引用具体文档 |
| 成本 | 微调成本高 | 无需训练,更新知识库即可 |
1.2 RAG vs 微调
- RAG: 适合知识密集型任务、需要最新信息、知识频繁更新
- 微调: 适合调整模型行为/风格、特定领域的专业表达、需要深度理解的任务
- RAG + 微调: 最佳实践是结合两者
2. RAG演进
2.1 Naive RAG
最基础的RAG实现:
用户查询 → 向量检索 → 拼接上下文 → LLM生成
局限性:
- 检索质量依赖查询质量
- 简单的向量相似度可能不准确
- 无法处理需要多跳推理的问题
- 上下文窗口有限
2.2 Advanced RAG
在Naive RAG基础上添加了预处理和后处理:
用户查询 → 查询改写 → 向量检索 → 重排序 → 上下文压缩 → LLM生成
改进:
- 查询优化(改写、扩展、分解)
- 检索优化(混合搜索、重排序)
- 生成优化(上下文压缩、自我反思)
2.3 Modular RAG
将RAG拆分为可组合的模块,灵活编排:
[路由模块] → 决定处理策略
[检索模块] → 多源检索 + 融合
[重排序模块] → 相关性排序
[压缩模块] → 上下文精炼
[生成模块] → 基于优化上下文生成
[验证模块] → 检查生成质量
3. RAG Pipeline详解
3.1 文档加载 (Load)
from langchain.document_loaders import (
PyPDFLoader, TextLoader, UnstructuredHTMLLoader,
CSVLoader, DirectoryLoader
)
# 加载不同格式的文档
pdf_loader = PyPDFLoader("document.pdf")
html_loader = UnstructuredHTMLLoader("page.html")
csv_loader = CSVLoader("data.csv")
# 批量加载目录
dir_loader = DirectoryLoader(
"./docs/",
glob="**/*.md",
show_progress=True
)
documents = dir_loader.load()
支持的数据源: PDF、HTML、Markdown、CSV、JSON、数据库、API、网页爬取
3.2 文档分块 (Chunk)
固定大小分块
from langchain.text_splitter import CharacterTextSplitter
splitter = CharacterTextSplitter(
chunk_size=1000, # 每块大小
chunk_overlap=200, # 重叠区域
separator="\n\n" # 分隔符
)
chunks = splitter.split_documents(documents)
语义分块
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", "。", ".", " ", ""] # 递归分隔
)
分块策略对比
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 固定大小 | 简单、快速 | 可能截断语义 | 通用场景 |
| 语义分块 | 保持语义完整性 | 计算开销较大 | 文档结构不规则 |
| 递归分块 | 灵活、效果好 | 需要调参 | 大多数场景(推荐) |
| 文档结构分块 | 利用文档结构 | 依赖文档格式 | 结构化文档(HTML、Markdown) |
| 句子级分块 | 语义最完整 | 块太小,检索效率低 | 精确匹配场景 |
分块大小的选择:
- 太小: 丢失上下文信息
- 太大: 检索精度降低,可能超出上下文窗口
- 经验值: 256-1024 tokens
- 建议: 通过实验确定最优大小
3.3 嵌入 (Embed)
from langchain.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
vectors = embeddings.embed_documents([chunk.page_content for chunk in chunks])
详见 向量数据库实战 中的嵌入模型对比。
3.4 索引 (Index)
from langchain.vectorstores import Chroma
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
3.5 检索 (Retrieve)
# 基本语义检索
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 5}
)
# MMR检索(平衡相关性和多样性)
retriever = vectorstore.as_retriever(
search_type="mmr",
search_kwargs={"k": 5, "fetch_k": 20, "lambda_mult": 0.5}
)
docs = retriever.get_relevant_documents("什么是Transformer?")
3.6 重排序 (Rerank)
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
# 对检索结果重排序
pairs = [(query, doc.page_content) for doc in retrieved_docs]
scores = reranker.predict(pairs)
# 按分数排序
reranked = sorted(zip(retrieved_docs, scores), key=lambda x: x[1], reverse=True)
3.7 生成 (Generate)
from langchain.chains import RetrievalQA
qa_chain = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-4"),
chain_type="stuff", # stuff / map_reduce / refine
retriever=retriever,
return_source_documents=True
)
result = qa_chain({"query": "什么是Transformer的自注意力机制?"})
3.8 RAG Pipeline全景
graph TD
A[文档加载] --> B[文档分块]
B --> C[向量嵌入]
C --> D[向量索引]
E[用户查询] --> F[查询改写]
F --> G[向量检索]
D --> G
G --> H[重排序]
H --> I[上下文构建]
I --> J[LLM生成]
J --> K[输出]
F --> |HyDE| F1[生成假设文档]
F1 --> G
F --> |Multi-Query| F2[生成多个查询]
F2 --> G
style A fill:#e1f5fe
style B fill:#e1f5fe
style C fill:#e1f5fe
style D fill:#e1f5fe
style E fill:#fff3e0
style J fill:#e8f5e9
4. 查询转换 (Query Transformation)
4.1 HyDE (Hypothetical Document Embeddings)
先让LLM生成一个假设性的答案文档,再用该文档的嵌入进行检索:
def hyde_retrieval(query, llm, embeddings, vectorstore):
# 1. 生成假设文档
hypothesis_prompt = f"请写一段可能回答以下问题的文字:\n{query}"
hypothesis_doc = llm.predict(hypothesis_prompt)
# 2. 用假设文档的嵌入检索
hypothesis_embedding = embeddings.embed_query(hypothesis_doc)
docs = vectorstore.similarity_search_by_vector(hypothesis_embedding, k=5)
return docs
原理: 假设文档与真实答案的嵌入空间更接近,比直接用问题检索效果更好。
4.2 Multi-Query
将原始查询改写为多个不同角度的查询,合并检索结果:
def multi_query_retrieval(query, llm, retriever):
# 生成多个查询变体
prompt = f"""请从不同角度改写以下问题,生成3个相关但不同的查询:
原始问题:{query}
查询1:
查询2:
查询3:"""
queries = llm.predict(prompt).split("\n")
# 合并所有查询的检索结果
all_docs = set()
for q in queries:
docs = retriever.get_relevant_documents(q)
all_docs.update(docs)
return list(all_docs)
4.3 查询分解
将复杂问题分解为子问题,分别检索后合并:
原始问题:Transformer和RNN在处理长序列时有什么区别?
子问题1:Transformer如何处理长序列?
子问题2:RNN如何处理长序列?
子问题3:Transformer和RNN的主要区别是什么?
5. 高级RAG模式
5.1 Self-RAG
在生成过程中动态决定是否需要检索:
1. 接收查询
2. 判断:是否需要检索?
- 是 → 检索 → 评估检索结果相关性 → 生成 → 验证
- 否 → 直接生成
3. 对生成结果进行自我验证
5.2 CRAG (Corrective RAG)
对检索结果进行评估和修正:
1. 检索文档
2. 评估文档相关性
- 相关 → 使用
- 不相关 → 丢弃 + 网络搜索补充
- 部分相关 → 提取相关部分
3. 基于修正后的上下文生成
5.3 Graph RAG
结合知识图谱增强检索:
1. 构建知识图谱(实体+关系)
2. 向量检索 + 图谱遍历
3. 获取多跳关系信息
4. 合并向量检索和图谱结果
5. LLM生成
6. 总结
RAG架构的选择取决于具体需求:
- 简单问答: Naive RAG即可
- 企业级应用: Advanced RAG + 重排序 + 混合搜索
- 复杂推理: Modular RAG + Self-RAG / CRAG
- 知识图谱场景: Graph RAG
关键原则:
- 评估驱动: 建立评估基线再优化
- 渐进增强: 从简单开始,按需增加复杂度
- 端到端优化: 各环节联合优化而非孤立调整
参考资料
- Lewis et al., "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks", 2020
- Gao et al., "Retrieval-Augmented Generation for Large Language Models: A Survey", 2024
- RAG增强记忆 — Agent中的RAG应用
- 向量数据库实战 — 嵌入模型和向量数据库选择
- RAG评估与优化 — RAG系统评估方法