RAG 架构全景:从检索增强生成到 Agentic RAG 的生产实践

RAG(检索增强生成)已成为大语言模型接入私有知识库的主流范式,但朴素 RAG 在召回率、相关性排序与评估层面存在系统性短板。本文系统讲解 RAG 架构全景:文档切分与索引策略、查询路由与改写、混合检索(稀疏+密集+BM25)、Cross-Encoder Reranker 重排序、生成阶段上下文组装,以及基于 RAGAS 的评估体系与 Agentic RAG 的生产调优。

检索增强生成(Retrieval-Augmented Generation, RAG)通过把外部知识库接入 LLM 推理,缓解幻觉、补充时效性知识、实现私有数据问答。然而从 Demo 到生产,RAG 的检索质量、排序质量与评估闭环决定了系统的最终可用性。本文给出完整的 RAG 工程视图。

RAG 架构全景与演进路线

RAG 的核心理念是「检索-增强-生成」三段式流水线:先从知识库检索候选片段,再把片段注入 Prompt,最后让 LLM 基于证据生成答案。按工程成熟度,RAG 通常被划分为三代:

Naive RAG(朴素 RAG):对文档做固定切块、批量 Embedding、Top-K 向量检索、拼接 Prompt。实现简单,但对查询改写、相关性排序、上下文冗余都没有显式处理,检索噪声会直接污染生成质量。

Advanced RAG(进阶 RAG):在检索前增加查询改写(Query Rewriting)、查询路由(Query Routing)、混合检索与 Reranker 重排,在生成前增加上下文压缩与引用标注,检索质量与可解释性显著提升。

Modular / Agentic RAG(模块化与智能体 RAG):将检索拆解为可编排的模块,由 LLM 作为控制器决定「何时检索、检索什么、是否多跳检索」,支持反思(Self-RAG)、迭代修正(CRAG)与工具调用。

┌─────────────────────────────────────────────────────────────┐
│  Agentic RAG(控制器编排检索、迭代、反思)                      │
│  ┌─────────┐  ┌──────────┐  ┌──────────┐  ┌───────────┐     │
│  │ 查询路由  │→│ 查询改写   │→│ 混合检索   │→│ Reranker  │     │
│  │ Router  │  │ Rewriter │  │ S+Dense  │  │ CrossEnc │     │
│  └─────────┘  └──────────┘  └──────────┘  └───────────┘     │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ 知识库索引:切分 → Embedding → 向量库 + BM25 + 元数据    │  │
│  └───────────────────────────────────────────────────────┘  │
│  ┌─────────┐  ┌───────────────┐  ┌──────────────────────┐  │
│  │ 上下文组装 │→│ Prompt 注入     │→│ LLM 生成 + 引用标注   │  │
│  └─────────┘  └───────────────┘  └──────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
阶段朴素 RAG进阶 RAGAgentic RAG
查询处理原样检索路由 + 改写 + HyDE控制器按需检索
检索方式仅向量 Top-K混合检索 + Rerank自适应多跳检索
排序向量相似度Cross-Encoder 重排排序 + 反思校验
生成拼接 Top-K压缩去噪 + 引用迭代修正 + 停止条件
评估无RAGAS 离线指标在线 + 反馈闭环

文档切分与索引策略

检索单元的质量决定召回上限。切分(Chunking)的目标是让每个片段自洽、语义完整、便于命中,常见策略如下:

策略做法优点缺点
固定窗口按 256/512 token 硬切简单、稳定切断语义,噪声高
递归字符按分隔符层级递归切结构友好粒度需调参
语义切分按句向量相似度断点语义完整计算成本高
父子分块父块粗切、子块细切命中子块、注入父块存储翻倍

父子分块(Parent-Child Chunking) 是生产环境最常用的模式:子块用于检索匹配,命中后把父块(上下文更完整的段落)注入 Prompt,兼顾检索精度与上下文完整性。LangChain 中可通过 ParentDocumentRetriever 实现。

from langchain.text_splitter import RecursiveCharacterTextSplitter

# 子块:细粒度,用于向量检索
child_splitter = RecursiveCharacterTextSplitter(
    chunk_size=256, chunk_overlap=32,
    separators=["\n\n", "\n", "。", "!", "?", " "]
)
# 父块:粗粒度,用于注入上下文
parent_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1024, chunk_overlap=128
)

切分参数的核心权衡是块大小 vs 语义完整性。块越小,检索粒度越细但上下文越碎片化;块越大,语义越完整但 Top-K 可能被无关内容占用。经验起点是 256-512 token、10-20% 重叠率,再按业务语料评估调整。

索引阶段还需同步构建 稀疏索引(BM25/Elasticsearch) 与元数据过滤字段(时间、来源、文档类型、租户 ID),为混合检索与标量过滤做准备。

检索向量化与 Embedding 选型

Embedding 模型决定向量检索的上限。选型主要看三个维度:语义对齐度(是否匹配领域语料)、向量维度(影响存储与检索成本)、MTEB 基准表现。

模型维度特点适用场景
text-embedding-3-large3072(可截断)OpenAI 官方,支持 MRL通用问答
text-embedding-3-small1536成本低、速度快大规模索引
bge-large-zh1024中文表现强中文知识库
bge-m31024多语言 + 多粒度 + 多检索方式多语言混合
e5-mistral-7b4096超强语义但推理贵离线高质量索引
from sentence_transformers import SentenceTransformer

# 用 bge-m3 构建向量,维度 1024,支持 dense + sparse
model = SentenceTransformer("BAAI/bge-m3", device="cuda")
corpus = ["RAG 通过外部知识库增强 LLM 生成质量", "LoRA 是一种参数高效微调方法"]
embeddings = model.encode(corpus, normalize_embeddings=True)
print(embeddings.shape)  # (2, 1024)

MRL(Matryoshka 表征学习)维度截断与**量化存储(int8/uint8)**是控制成本的两大利器:OpenAI 支持 MRL 将 3072 维截断至 512/1024/1536 维,向量库则普遍支持对 float32 向量做 int8 量化,存储可压缩 4 倍、召回损失通常小于 1%。

实践原则:Embedding 与检索链路的版本必须与文档索引「同源同版」。更换 Embedding 模型意味着必须重建索引,否则向量分布不匹配会导致召回率骤降。

查询路由与改写

用户的原始问题往往不适合直接检索:问句可能是口语化的、包含指代(「那个模型」)、过短或歧义。查询改写(Query Transformation)负责把原始查询改造成更适合检索的形式。

查询路由(Query Routing):由 LLM 判断查询类型(闲聊 / 知识问答 / 需要检索 / 需要工具调用),并分派到对应流水线。典型的分类结果可以是枚举指令。

HyDE(Hypothetical Document Embeddings):先让 LLM 根据查询生成一段「假设答案」,再用该答案的向量去检索,缓解查询与文档之间的词汇鸿沟。

Multi-Query:把一个问题改写成多个视角的子问题,分别检索后合并去重。

from langchain_core.output_parsers import JsonOutputParser

ROUTER_SYSTEM = """
你是查询路由器。请根据用户问题返回 JSON:
{"type": "general_chat" | "rag_query" | "tool_call", "reason": "判断依据"}
若问题需要查阅私有知识库才能回答,则返回 rag_query。
"""

def route_query(user_query: str) -> str:
    response = llm.invoke([
        {"role": "system", "content": ROUTER_SYSTEM},
        {"role": "user", "content": user_query}
    ])
    return JsonOutputParser().parse(response.content)["type"]

查询改写示例:用户问「它对比 LoRA 有什么优势」,改写器应先补充指代(LoRA 指代的对象),再扩展为完整问题「对比 LoRA,QLoRA 在显存占用与训练精度上有什么优势」,从而显著提升检索命中率。

改写技术原理适用场景代价
指代消解把「它」「那个」替换为实体多轮对话低
查询扩展添加同义词与领域词稀疏领域低
Multi-Query生成多个子问题复杂问题中(检索放大)
HyDE生成假设答案再检索词汇鸿沟大高(额外生成)
RAG-Fusion多路结果 RRF 融合全面召回高

混合检索:稀疏 + 密集 + BM25

单一检索器各有盲区:密集向量擅长语义相似但可能丢失精确关键词(如型号、商品号),**稀疏检索(BM25)擅长精确词匹配但对同义改写不敏感。混合检索(Hybrid Search)把两者结合,再用倒数排名融合(Reciprocal Rank Fusion, RRF)**合并结果。

def rrf_fuse(result_lists: list[list[str]], k: int = 60) -> list[str]:
    """RRF:为每个文档在每路结果的排名打分并累加。"""
    scores: dict[str, float] = {}
    for ranked in result_lists:
        for rank, doc_id in enumerate(ranked, start=1):
            scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank)
    return sorted(scores, key=scores.get, reverse=True)

RRF 不需要分数归一化,直接对排名做倒数加权,鲁棒性远好于「加权平均相似度」。融合时的权重调节同样关键:关键词密集场景(电商、代码、合同编号)可给 BM25 更高权重;开放语义问答场景(FAQ、客服)可给密集向量更高权重。

# 以 Qdrant 为例:query_points 同时走 dense 与 sparse 两路
curl -X POST http://localhost:6333/collections/kb/points/search \
  -H "Content-Type: application/json" -d '{
    "prefer_vector": "dense",
    "query": 0.9,
    "query_sparse": [{"index": 12, "value": 0.7}],
    "limit": 20
  }'

混合检索的收益在「专有名词 / 产品型号 / 代码标识符」等精确匹配场景最明显。建议在离线评测集上对比「纯向量」「纯 BM25」「混合 + RRF」三档的命中率,再决定融合权重,而不是拍脑袋定比例。

Reranker 重排序

向量检索是**双塔(Bi-Encoder)**结构:查询与文档分别编码后算余弦相似度,速度快但只捕捉粗粒度语义。Reranker 采用 Cross-Encoder,把「查询 + 文档」拼接后整体过一遍 Transformer,直接输出相关性分数,精度更高但推理代价大,因此只用于对 Top-50/100 的重排。

对比Bi-EncoderCross-Encoder
编码方式查询与文档各自独立编码查询与文档拼接后联合编码
相关性精度中高
延迟毫秒级(可预计算)每对需一次前向
典型角色召回(Recall)精排(Precision)
from FlagEmbedding import FlagReranker

reranker = FlagReranker("BAAI/bge-reranker-v2-m3", use_fp16=True)

def rerank(query: str, candidates: list[str], top_k: int = 5) -> list[str]:
    pairs = [[query, doc] for doc in candidates]
    scores = reranker.compute_score(pairs, normalize=True)
    ordered = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
    return [doc for doc, _ in ordered[:top_k]]

二阶段检索(Two-Stage Retrieval)是生产标准做法:第一阶段用双塔做宽召回(Top-50/100,海选),第二阶段用 Reranker 做精排(Top-5/8,精选)。精排阶段还可以叠加硬规则——命中精确关键词的文档强制置顶,未命中但语义相似的文档作为补充。

def two_stage_retrieve(query: str):
    candidates = vector_db.search(query, top_k=50)          # 召回
    candidates += bm25_search(query, top_k=50)              # 稀疏补充
    candidates = dedupe_and_filter_by_metadata(candidates)  # 去重 + 过滤
    return rerank(query, candidates, top_k=5)               # 精排

生成阶段:上下文组装与提示注入

检索到证据后,生成阶段决定 LLM 如何使用证据。核心工程点包括上下文组装(哪些片段进 Prompt、顺序如何)、去噪(与问题无关的片段剔除)、引用标注(答案附证据来源,提升可信度与可追溯性)。

上下文组装的关键原则:

  1. 按相关性降序排列片段,最相关的内容靠近问题
  2. 控制上下文总量,通常 3-8 段、1-2K token,避免「上下文淹没」
  3. 注入引用元数据,要求 LLM 在答案中标注 [1][2] 对应的证据编号
  4. 设置「未知即未知」约束:证据不足以回答时明确说「知识库中未找到」
from langchain_core.prompts import ChatPromptTemplate

RAG_PROMPT = ChatPromptTemplate.from_messages([
    ("system", """你是一个知识库问答助手。仅依据提供的证据回答,禁止臆测。
规则:
1. 若证据不足以回答,回答「知识库中未找到相关信息」。
2. 回答必须引用证据编号,格式为 [1][2]。
3. 保持回答简洁准确,使用中文。"""),
    ("human", "证据:\n[1]{doc_1}\n[2]{doc_2}\n[3]{doc_3}\n\n问题:{question}"),
])

def build_chain():
    return RAG_PROMPT | llm | CitationParser()

**上下文压缩(Contextual Compression)**解决「Top-K 里有噪声」的问题:对每个片段做相关性打分,低于阈值的片段直接丢弃;对长片段抽取与问题最相关的句子,只注入精华部分。这能显著降低幻觉率,代价是每次生成多一次校验调用。

RAG 评估:RAGAS 与检索命中率

RAG 系统的质量由检索质量与生成质量两个维度组成,前者用检索命中率、MRR 衡量,后者用 RAGAS 框架的无参考指标衡量。

指标维度公式/含义目标
Recall@K检索相关文档在 Top-K 中的比例越高越好
MRR检索首个相关文档排名的倒数越高越好
Faithfulness生成答案中声称可被证据支持的占比越高越好
Answer Relevancy生成答案与问题的相关性越高越好
Context Precision检索+生成证据片段中相关信息占比越高越好
Context Recall检索+生成证据是否覆盖答案所需信息越高越好

**检索命中率(Hit Rate)**是生产中最先看的指标:对一批标准问答对,检查「正确答案是否出现在检索结果 Top-5 中」。召回不足时先别调 Prompt,而应优先排查切分粒度、Embedding 模型与混合检索权重。

from ragas import evaluate
from ragas.metrics import (
    faithfulness, answer_relevancy, context_precision, context_recall
)
from datasets import Dataset

eval_set = Dataset.from_dict({
    "question": ["LoRA 相比全量微调节省多少显存?"],
    "answer": ["LoRA 仅训练低秩增量矩阵,显存占用可降低约 2-3 倍。"],
    "contexts": [["LoRA 通过低秩分解冻结原权重,只训练增量部分。"]] ,
    "ground_truth": ["LoRA 只训练低秩增量矩阵,显存显著下降。"],
})

result = evaluate(
    eval_set,
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
    llm=evaluator_llm,
    embeddings=embedding_model,
)
print(result)

**Golden Set(黄金评测集)**是评估的基石:由人工构造 200-500 条覆盖常见问题形态的问答对,标注标准答案与相关文档 ID。任何检索链路改动(换 Embedding、改切分、调权重)都先在 Golden Set 上回归,避免「改了 A 好了、坏了 B」的隐性退化。

Agentic RAG 与生产调优

Agentic RAG 把检索决策交给 LLM 控制器,适用于查询需要多步推理、需要跨多个知识源汇总、或需要判断「要不要检索」的场景。典型模式包括:

  • Self-RAG:LLM 自我评估「是否需要检索」「检索结果是否足够」,不足则迭代检索
  • CRAG(Corrective RAG):先检索,再对检索结果做质量评估,噪声高则触发查询改写重新检索
  • 多跳检索(Multi-Hop):先检索得到中间实体,再基于中间实体进行第二轮检索
def agentic_retrieve(query: str, max_iterations: int = 3) -> list[str]:
    evidence: list[str] = []
    current_query = query
    for _ in range(max_iterations):
        docs = two_stage_retrieve(current_query)
        verdict = llm.judge("""基于以下证据,判断是否足以回答:
证据: {docs}
请回答: SUFFICIENT / INSUFFICIENT / NEED_EXTRA
如果 NEED_EXTRA,请给出新的检索问题。""", docs)
        if verdict.status == "SUFFICIENT":
            evidence = docs; break
        current_query = verdict.new_query  # 迭代改写
    return evidence

生产调优的杠杆集中在延迟预算与缓存上:

调优项手段收益
检索延迟缩小召回 K(50→20)、向量 int8 量化P95 检索 < 20ms
重排延迟只对 Top-20 做 Rerank,限制片段长度节省 60%+ 重排耗时
LLM 延迟上下文压缩 + 更小的生成模型首 token 延迟下降
成本语义缓存(Semantic Cache)+ 结果缓存命中可省 40-70% Token
稳定性混合检索降级:向量库不可用回退 BM25检索高可用

关键认知:RAG 优化顺序应当是「评估先行、检索优先」。先建 Golden Set 摸清检索命中率与生成 Faithfulness 基线,再逐环节优化;多数生产问题(幻觉、答非所问)根因在检索而非 Prompt。

总结

环节核心决策常用方案
索引切分粒度父子分块 256/1024 token
向量化Embedding 选型bge-m3 / text-embedding-3
查询路由与改写Multi-Query + HyDE
检索召回融合BM25 + 向量 + RRF
排序精排Cross-Encoder Reranker
生成上下文注入压缩去噪 + 引用标注
评估质量闭环RAGAS + Golden Set
编排复杂查询Agentic RAG 多跳检索

RAG 工程的关键不是堆砌组件,而是建立「索引 → 检索 → 排序 → 生成 → 评估」的完整闭环,用可量化的指标驱动每一环的迭代优化。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ai」更多文章

  1. 时序预测实战:从 ARIMA 到时序基础模型
  2. 模型压缩:量化、剪枝、蒸馏与部署优化实战
  3. 量化感知训练(QAT)与量化微调:伪量化、STE 与 QLoRA 实战