引言
RAG(Retrieval-Augmented Generation)已成为构建LLM应用的主流架构范式。它将信息检索与文本生成有机结合,让AI系统既能利用大模型的推理能力,又能访问实时、私有的外部知识。
然而,RAG并非单一技术,而是一套包含多个组件的系统工程。从文档摄入到最终答案生成,每一步都涉及关键的设计决策。理解整体架构,是构建可靠RAG系统的第一步。
本文将系统梳理RAG的完整技术链路,涵盖离线和在线两大流水线、核心组件深度解析、常见架构模式以及关键权衡。
1. RAG系统总体架构
下图展示了一个生产级RAG系统的完整架构:
┌─────────────────────────────────────────────────────────────────────┐
│ RAG 系统总体架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────── 离线流水线(Indexing Pipeline)──────────────────┐ │
│ │ │ │
│ │ 原始文档 ──► 文档解析 ──► 文本分块 ──► 向量化 ──► 索引存储 │ │
│ │ (PDF/HTML/ │ │ │ │ │ │
│ │ Markdown) │ │ │ │ │ │
│ │ ▼ ▼ ▼ ▼ │ │
│ │ 文档加载器 文本分割器 Embedding 向量数据库 │ │
│ │ 模型 + 元数据 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────── 在线流水线(Query Pipeline)────────────────────┐ │
│ │ │ │
│ │ 用户查询 ──► 查询理解 ──► 向量检索 ──► 重排序 ──► 上下文构建 │ │
│ │ │ │ │ │ │ │ │
│ │ ▼ ▼ ▼ ▼ ▼ │ │
│ │ 原始问题 查询改写/ 相似度搜索 Reranker Prompt │ │
│ │ HyDE 模型 组装 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ LLM 生成 ──► 最终回答 │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
整个系统分为两条核心流水线:离线流水线(Indexing Pipeline)负责将知识文档处理为可检索的索引;在线流水线(Query Pipeline)负责将用户查询转化为最终答案。
2. 离线流水线:知识索引
离线流水线的任务是将原始文档转化为结构化的可检索索引。这是RAG系统的基础设施,通常在系统上线前一次性完成,并在知识更新时增量执行。
2.1 文档加载与解析
from llama_index.core import SimpleDirectoryReader
from langchain.document_loaders import PyPDFLoader, TextLoader
# LlamaIndex 方式
documents = SimpleDirectoryReader("./data").load_data()
# LangChain 方式
loader = PyPDFLoader("document.pdf")
docs = loader.load()
不同的文档类型对应不同的解析策略:
| 文档类型 | 推荐加载器 | 注意事项 |
|---|---|---|
| PyPDFLoader / PyMuPDFLoader | 注意表格和图片的提取 | |
| HTML | SeleniumURLLoader / BeautifulSoup | 需要清理HTML标签和脚本 |
| Markdown | MarkdownTextSplitter | 保留层级结构信息 |
| Word | Docx2txtLoader | 处理图片和表格需额外工具 |
| 代码 | TextLoader | 注意语法结构和注释的处理 |
2.2 文本分块(Chunking)
分块是影响检索质量的关键步骤。分块太小会丢失语义,太大则引入噪声。
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=512, # 每块字符数
chunk_overlap=128, # 块间重叠字符数
separators=["\n\n", "\n", "。", ". ", " ", ""],
length_function=len,
)
chunks = text_splitter.split_documents(docs)
分块策略对比:
| 策略 | 粒度 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 固定大小 | 统一 | 通用场景 | 实现简单 | 可能切断语义单元 |
| 递归分割 | 自适应 | 大多数场景 | 保持段落完整性 | 块大小不均 |
| 语义分割 | 语义块 | 长文档 | 语义完整 | 依赖模型推理 |
| 文档结构 | 章节/段落 | 结构化文档 | 保留原文结构 | 对非结构化文档无效 |
2.3 向量化与索引
from langchain_openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
# 初始化嵌入模型
embeddings = OpenAIEmbeddings(
model="text-embedding-3-small", # 1536维
)
# 创建向量索引并持久化
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db",
collection_metadata={"hnsw:space": "cosine"}, # 使用余弦距离
)
# 保存元数据索引(用于过滤)
chunk_metadatas = [
{"source": doc.metadata.get("source"),
"page": doc.metadata.get("page"),
"section": doc.metadata.get("section")}
for doc in chunks
]
向量索引的核心选型要点:
- 向量维度:高维(1536/3072)精度高但计算慢,低维(384/768)速度快但可能损失精度
- 索引算法:HNSW(准确率高但内存大)、IVF(速度快但不保证精确)、Flat(暴力搜索,小数据集适用)
- 距离度量:余弦相似度(最常用)、点积(适用于归一化向量)、L2距离
3. 在线流水线:查询到答案
在线流水线是RAG系统的运行时路径,每次用户查询都会流经这条链路。
3.1 查询理解与改写
原始用户问题往往不适合直接用于向量检索。查询理解模块负责将用户输入转化为更有效的检索查询。
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 查询改写 Prompt
query_rewrite_prompt = ChatPromptTemplate.from_template("""
你是一个查询改写专家。将用户的问题改写成3个独立的关键词查询,
每个查询针对不同的语义维度,用于向量数据库检索。
原始问题:{question}
请用逗号分隔输出3个改写后的查询:
""")
def rewrite_query(question: str) -> list[str]:
response = llm.invoke(
query_rewrite_prompt.format(question=question)
)
queries = response.content.strip().split(",")
return [q.strip() for q in queries[:3]]
常见查询理解策略:
- 查询改写 - 将口语化问题转为适合检索的关键词形式
- HyDE(假设文档嵌入) - 先生成一个假设回答,再用该回答进行检索
- 多查询扩展 - 从不同角度生成多个子查询
- 查询分解 - 将复杂问题拆解为多个简单子问题
3.2 向量检索
from langchain.retrievers import EnsembleRetriever
from langchain.retrievers import BM25Retriever
# 向量检索器
vector_retriever = vectorstore.as_retriever(
search_type="similarity", # 或 "mmr" (最大边际相关性)
search_kwargs={"k": 10}, # 返回 top-k 结果
)
# BM25 关键词检索器(混合检索用)
bm25_retriever = BM25Retriever.from_documents(
documents=chunks, k=5
)
# 集成检索器(混合检索)
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.3, 0.7], # 可调节权重
)
检索方式对比:
| 方式 | 原理 | 适用场景 | 召回率 | 精度 |
|---|---|---|---|---|
| 纯向量检索 | 语义相似度 | 语义匹配 | 高 | 中 |
| 关键词检索 | BM25/稀疏向量 | 精确匹配 | 低 | 高 |
| 混合检索 | 向量+关键词融合 | 通用最优 | 最高 | 高 |
| 结构化检索 | 元数据过滤+向量 | 有明确过滤条件 | 依赖过滤条件 | 高 |
3.3 重排序(Reranking)
检索出的 top-k 结果中,真实相关的文档可能排在后面。重排序模块使用更精确的模型对结果重新打分。
from llama_index.core.postprocessor import SentenceTransformerRerank
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainFilter
# LlamaIndex 重排序
reranker = SentenceTransformerRerank(
model="BAAI/bge-reranker-v2-m3",
top_n=3, # 只保留前3个最相关的
)
# LangChain 上下文压缩(使用 LLM 过滤)
compressor = LLMChainFilter.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vector_retriever,
)
主流 Reranker 模型对比:
| 模型 | 参数量 | 语言 | 特点 |
|---|---|---|---|
| BAAI/bge-reranker-v2-m3 | 567M | 多语言 | 性价比优秀 |
| Cohere Rerank | API | 多语言 | 托管的专有服务 |
| cross-encoder/ms-marco-MiniLM-L-6-v2 | 22M | 英文 | 推理速度快 |
| jina-reranker-v2 | 278M | 多语言 | 支持长上下文 |
3.4 LLM 生成
将检索到的相关文档与用户查询组装成提示词,送入 LLM 生成最终答案。
from langchain.prompts import ChatPromptTemplate
from langchain.chains import RetrievalQA
# 上下文组装提示模板
qa_prompt = ChatPromptTemplate.from_messages([
("system", """你是一个基于知识的问答助手。请严格根据提供的上下文信息回答问题。
如果上下文中没有相关信息,请明确说"找不到相关信息"。
请在回答末尾标注引用的文档来源。
上下文:
{context}"""),
("human", "{question}"),
])
# 构建完整的 RAG 问答链
qa_chain = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-4o", temperature=0.1),
chain_type="stuff", # 将所有文档一次性放入上下文
retriever=ensemble_retriever,
chain_type_kwargs={
"prompt": qa_prompt,
"document_separator": "\n---\n",
},
return_source_documents=True,
)
# 执行查询
result = qa_chain.invoke({"query": "RAG系统的核心组件有哪些?"})
print(result["result"])
chain_type 选择:
| 类型 | 原理 | 适用 | 限制 |
|---|---|---|---|
| stuff | 所有文档放入一个 prompt | 文档少且小 | 受限于上下文窗口 |
| map_reduce | 分别处理再汇总 | 文档多 | 多次 LLM 调用 |
| refine | 逐个文档迭代优化 | 信息逐步累积 | 串行、速度慢 |
| map_rerank | 分别处理并评分 | 需要选最佳 | 需要评分机制 |
4. 核心组件深度解析
4.1 Retriever(检索器)
Retriever 是 RAG 的信息入口,其质量直接决定系统上限。核心设计维度包括:
- 召回策略:向量检索 vs 关键词检索 vs 混合检索
- 索引结构:HNSW、IVF、Flat 的选择影响性能
- 分块粒度:决定检索到的是段落还是句子
- 元数据过滤:通过时间、来源、类别等字段精确定位
# 高级检索器配置示例
retriever = vectorstore.as_retriever(
search_type="mmr", # 最大边际相关性,兼顾相关性和多样性
search_kwargs={
"k": 10, # 检索数量
"fetch_k": 30, # MMR 候选池大小
"lambda_mult": 0.25, # 多样性权重(0=高多样,1=高相关)
"filter": {"date": {"$gte": "2024-01-01"}}, # 时间过滤
},
)
4.2 Generator(生成器)
Generator 负责基于检索结果生成最终答案。关键考量包括:
- 模型选择:GPT-4o、Claude 3.5、Llama 3 等的基础能力
- 上下文窗口:窗口大小决定能携带多少检索结果
- 温度参数:事实性问答设 0-0.3,创意任务可适当调高
- 功能调用:是否支持结构化输出、引用标注
# 结构化输出示例
from pydantic import BaseModel
from langchain.output_parsers import PydanticOutputParser
class AnswerWithCitations(BaseModel):
answer: str
citations: list[str]
confidence: float
parser = PydanticOutputParser(pydantic_object=AnswerWithCitations)
structured_prompt = ChatPromptTemplate.from_messages([
("system", "根据上下文回答问题。\\n{format_instructions}"),
("human", "问题:{question}\\n上下文:{context}"),
]).partial(format_instructions=parser.get_format_instructions())
4.3 Reranker(重排序器)
Reranker 是检索质量的关键保障,它在粗排之后对结果进行精排。
| 方面 | 双编码器(Bi-encoder) | 交叉编码器(Cross-encoder) |
|---|---|---|
| 计算方式 | 查询和文档分别编码 | 查询和文档一起编码 |
| 推理速度 | 快(可预计算向量) | 慢(需要逐对计算) |
| 精度 | 中等 | 高(全交互注意力) |
| 使用位置 | 向量检索(粗排) | 重排序(精排) |
| 典型模型 | text-embedding-3 | bge-reranker-v2 |
重排序的典型流程:
- 粗排阶段:向量检索返回 top-20 到 top-50 结果
- 精排阶段:Reranker 在 top-3 到 top-5 结果上重排序
- 截断阶段:只保留精排后的 top-3 结果送入 LLM
5. 常见架构模式
5.1 Naive RAG(基础RAG)
最简单的实现方式,适用于入门和小型项目。
用户查询 → 向量检索 → LLM生成 → 最终回答
# Naive RAG - 最小实现
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(),
)
优点:实现简单、快速验证 缺点:无查询优化、无重排序、检索质量依赖单一策略
5.2 Advanced RAG(进阶RAG)
在基础之上增加预处理和后处理环节,提升检索质量。
用户查询 → 查询改写 → 混合检索 → 重排序 → LLM生成 → 引用标注 → 答案
↓
HyDE / 多查询
进阶优化手段:
- 预检索优化:查询改写、HyDE、查询分解
- 检索增强:混合检索(向量+BM25)、多路召回、元数据过滤
- 后检索优化:重排序、上下文压缩、动态截断
5.3 Modular RAG(模块化RAG)
将 RAG 拆分为可插拔模块,灵活组合不同策略。
from typing import Protocol
class Retriever(Protocol):
def retrieve(self, query: str, top_k: int) -> list[Document]:
...
class QueryProcessor(Protocol):
def process(self, query: str) -> str:
...
class Reranker(Protocol):
def rerank(self, query: str, docs: list[Document]) -> list[Document]:
...
class Generator(Protocol):
def generate(self, query: str, context: list[Document]) -> str:
...
# 模块化组装
class ModularRAGPipeline:
def __init__(self,
query_processor: QueryProcessor,
retriever: Retriever,
reranker: Reranker,
generator: Generator):
self.query_processor = query_processor
self.retriever = retriever
self.reranker = reranker
self.generator = generator
def run(self, query: str) -> str:
processed = self.query_processor.process(query)
docs = self.retriever.retrieve(processed, top_k=10)
reranked = self.reranker.rerank(query, docs)
return self.generator.generate(query, reranked)
架构模式对比:
| 维度 | Naive RAG | Advanced RAG | Modular RAG |
|---|---|---|---|
| 组件数量 | 3 | 5-7 | 5+(可扩展) |
| 检索质量 | 低 | 中-高 | 高 |
| 延迟 | 低 | 中 | 中-高 |
| 可定制性 | 低 | 中 | 高 |
| 维护成本 | 低 | 中 | 中-高 |
| 适用场景 | demo/原型 | 生产环境 | 复杂生产系统 |
6. 关键权衡
设计 RAG 系统时,需要在多个维度间权衡。
6.1 检索质量 vs 延迟
| 优化方向 | 质量提升 | 延迟影响 | 方案 |
|---|---|---|---|
| 更多检索结果 | ↑ | ↑↑ | k 从 5 增加到 20 |
| 重排序 | ↑↑ | ↑ | 增加 Reranker 调用 |
| 混合检索 | ↑↑ | ↑ | 两路检索 + 融合 |
| 缓存命中 | — | ↓↓ | 复用相似查询结果 |
6.2 上下文窗口 vs 成本
| 策略 | 上下文占用 | Token 成本 | 信息完整性 |
|---|---|---|---|
| 少量文档 | 低 | 低 | 可能缺失关键信息 |
| 大量文档 | 高 | 高 | 可能引入噪声 |
| 自适应窗口 | 中 | 中 | 动态平衡 |
| 分层摘要 | 中 | 中(+摘要成本) | 兼顾全局 |
6.3 精确度 vs 多样性
MMR(最大边际相关性)参数调优:
lambda_mult = 0 → 完全多样性(结果覆盖面广)
lambda_mult = 1 → 完全精确度(结果高度相关)
推荐值:0.25-0.5 → 在相关性和多样性间取得平衡
6.4 通用建议
- 数据量 < 1000 文档:Naive RAG + 单路向量检索足够
- 数据量 1000-100000 文档:Advanced RAG + 混合检索 + 重排序
- 数据量 > 100000 文档:Modular RAG + 分层索引 + 多级检索
- 需要高实时性:减少检索和重排序步骤,使用缓存
- 需要高精度:增加查询改写、多路召回、重排序和结构化输出
7. 总结
RAG 架构从宏观上可以理解为一条从”知识索引”到”知识检索”再到”知识生成”的完整链路。本文所介绍的核心组件和架构模式构成了生产级 RAG 系统的基础框架:
| 环节 | 核心组件 | 关键决策 | 常见陷阱 |
|---|---|---|---|
| 索引 | 文档解析器、文本分割器、Embedding 模型、向量数据库 | 分块策略、嵌入模型选型 | 分块不合理、忽略元数据 |
| 检索 | Retriever、Reranker、查询处理器 | 检索策略、重排序方案 | 检索深度不足、未做过滤 |
| 生成 | LLM、Prompt 模板、输出解析器 | 模型选择、上下文组装 | prompt 太简单、无引用 |
| 编排 | Pipeline / Chain / Graph | 架构模式选择 | 忽略容错和监控 |
推荐后续阅读: