RAG 面试 6 连问,从原理到优化全部覆盖

大语言模型的知识来源于训练数据,存在固有的时效性限制和领域知识盲区。RAG (Retrieval-Augmented Generation, 检索增强生成) 在生成前检索外部知识,将事实信息注入模型上下文,是解决 LLM 知识边界问题的主流方案。本章覆盖 RAG 的核心原理、完整流程、关键优化策略以及在 Agent 系统中的角色。

5.1 RAG 核心原理:解决 LLM 的知识边界问题

LLM 训练完成后参数固定,无法主动获取新知识。「参数化知识」有四个核心缺陷:知识时效性差(训练数据有截止日期);领域知识不足(企业私有知识、专业文献覆盖有限);事实幻觉(不确定时生成看似合理但错误的内容);缺乏可追溯性(无法说明回答的知识来源)。

RAG 的做法直截了当:生成回答前,先从外部知识库检索相关文档片段,注入 Prompt,由 LLM 基于检索结果生成回答。知识存储从模型参数转移到外部数据库,实现了动态更新和精确引用。

从信息论角度看,RAG 是一种「非参数化知识注入」机制:模型参数存储通用的语言理解和推理能力,事实知识存储在外部检索系统中。两者解耦带来明显的工程优势------知识更新只需更新数据库,多个模型可以共享同一知识库,知识来源可以审计和追溯。

与另外两种知识增强方式相比,各有取舍。Fine-tuning 将领域知识编码到模型参数中,适合学习领域风格和任务模式,但不适合频繁更新的知识。Context Stuffing 直接将所有相关知识放入 Prompt,简单但受限于上下文窗口大小。RAG 在两者之间取得平衡,既能接入大规模知识库,又保持知识的动态更新能力。

知识增强方式 知识规模 更新成本 时效性 可追溯性 适用场景
Fine-tuning 中等 高(需重训) 差 无 领域风格适配、任务模式学习
Context Stuffing 小 低 好 有 小规模知识、快速原型
RAG 大 低 好 有 企业知识库、大规模文档问答

RAG 并非万能方案。任务需要深度推理而非事实检索时(如数学证明、代码生成),RAG 帮助有限。知识需要被「理解」而非「查找」时(如学习新编程语言语法),Fine-tuning 可能更有效。工程实践中,RAG 常与 Fine-tuning 结合:Fine-tuning 让模型学会如何使用检索到的知识,RAG 提供具体的知识内容。

把握一个关键区分:RAG 解决的是「模型不知道」的问题,而非「模型不会推理」的问题。前者靠检索外部知识弥补,后者需要改进推理能力。在 Agent 系统中,RAG 更像是给 Agent 配了一本随时可查的「参考手册」,而不是提升它的「思考能力」。

5.2 完整 RAG 流程:离线索引与在线查询

RAG 系统的完整生命周期分为两个阶段:离线索引构建可检索的知识库,在线查询处理用户请求并生成回答。两个阶段的设计质量共同决定 RAG 系统的整体效果。

离线索引是 RAG 的基础建设。首先是文档加载,需要处理 PDF、Word、HTML、Markdown 等多种格式。不同格式需要不同的解析器------PDF 要处理版面布局和表格结构,HTML 要去除导航栏和广告等噪声。

接下来是文档分块,将长文档切分为语义完整的片段。分块策略直接影响检索精度:块太大包含过多无关信息,稀释语义信号;块太小丢失上下文,检索到的片段无法独立理解。分块后,每个块通过 Embedding 模型编码为高维向量,连同原始文本存入向量数据库。

在线查询是 RAG 的服务路径。用户问题先经过查询理解和改写,然后通过向量检索和关键词检索的混合方式召回候选文档,再经重排序模型精排,最终将 Top-K 结果组装为上下文,交由 LLM 生成回答。

bash 复制代码
离线索引阶段                          在线查询阶段
┌─────────────┐                    ┌─────────────┐
│  原始文档库  │                    │  用户查询    │
└──────┬──────┘                    └──────┬──────┘
       │                                  │
       v                                  v
┌─────────────┐                    ┌─────────────┐
│  文档解析    │                    │  查询改写    │
└──────┬──────┘                    └──────┬──────┘
       │                                  │
       v                                  v
┌─────────────┐                    ┌─────────────┐
│  文档分块    │                    │  混合检索    │
└──────┬──────┘                    │ (向量+关键词) │
       │                           └──────┬──────┘
       v                                  │
┌─────────────┐                           v
│  向量化编码  │                    ┌─────────────┐
└──────┬──────┘                    │  重排序      │
       │                           └──────┬──────┘
       v                                  │
┌─────────────┐                           v
│  向量数据库  │ <───── 向量检索 ──────┌─────────────┐
│ + 元数据存储 │                       │  上下文组装  │
└─────────────┘                       └──────┬──────┘
                                            │
                                            v
                                      ┌─────────────┐
                                      │  LLM 生成    │
                                      └──────┬──────┘
                                            │
                                            v
                                      ┌─────────────┐
                                      │  回答+来源   │
                                      └─────────────┘

以下是一个使用 LangChain 构建离线索引的代码示例:

python 复制代码
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma

# 文档加载与分块
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,
    chunk_overlap=50,
    separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""]
)
chunks = text_splitter.split_documents(documents)

# 向量化与存储
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    collection_metadata={"hnsw:space": "cosine"}
)

在线查询阶段的代码示例:

python 复制代码
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder

# 混合检索:向量 + 关键词
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 50})
keyword_retriever = BM25Retriever.from_documents(chunks)
keyword_retriever.k = 50

# 重排序
reranker_model = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-v2-m3")
compressor = CrossEncoderReranker(
    model=reranker_model,
    top_n=5
)
hybrid_retriever = EnsembleRetriever(
    retrievers=[vector_retriever, keyword_retriever],
    weights=[0.6, 0.4]
)
final_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=hybrid_retriever
)

# 生成回答
docs = final_retriever.get_relevant_documents(query)
context = "\n\n".join([doc.page_content for doc in docs])
answer = llm.invoke(f"基于以下资料回答问题:\n{context}\n\n问题:{query}")

整个流程中一个容易被忽视的环节:元数据管理。每个文档块都应记录来源信息(文件名、页码、章节标题等),在后续的来源标注、权限过滤和检索优化中至关重要。企业场景中,不同部门的知识库可能需要权限隔离,通过元数据过滤可以在检索阶段实现访问控制。

另一个关键设计决策是 Embedding 模型选择。不同模型在语言支持、向量维度、检索精度上差异显著。中文场景推荐支持中英双语的模型,如 BGE (BAAI General Embedding)、M3E 或 OpenAI 的 text-embedding-3-small。模型确定后不宜频繁更换------更换 Embedding 意味着重建整个向量索引。

5.3 文档分块策略:从固定长度到语义分块

文档分块是 RAG 中最具工程挑战的环节之一,直接决定检索的召回率和精确率,进而影响生成质量。理想的分块应满足三个条件:语义完整(每个块可独立理解)、边界合理(不割裂紧密相关的信息)、大小适中(不太大稀释检索信号,不太小丢失上下文)。

最基础的策略是固定长度分块:按固定 Token 数量切分文本,实现简单,但可能在句子中间切断,破坏语义完整性,也无法适应文档结构变化。工程中通常配合滑动窗口,设置重叠区域缓解边界问题。

python 复制代码
# 固定长度分块(带重叠)
from langchain.text_splitter import CharacterTextSplitter

splitter = CharacterTextSplitter(
    separator="\n\n",
    chunk_size=300,
    chunk_overlap=50,
    length_function=len
)
chunks = splitter.split_text(long_text)

按句子和段落分块是固定长度的改进版:以自然语言边界(句号、换行)作为切分点,保证语义完整。段落超过预设大小时再按句子细分。适合结构良好的文章类文档,对无结构文本效果有限。

递归字符分块(Recursive Character Text Splitter)是 LangChain 的默认策略,按分隔符优先级递归切分:先尝试最高优先级分隔符(如双换行),块仍然太大再用次优先级(如单换行、句号),直到满足大小要求。

python 复制代码
# 递归字符分块
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,
    chunk_overlap=50,
    separators=[
        "\n#{1,6} ",   # Markdown 标题
        "\n\n",         # 段落
        "\n",           # 行
        "。", "!", "?",  # 中文句子结束
        ".", "!", "?",   # 英文句子结束
        ",", ",", " ", ""  # 最后回退到字符
    ]
)

语义分块(Semantic Chunking)更进一层:计算相邻句子的 Embedding 相似度,相似度低于阈值时认为话题切换,在此处切分。语义连贯性更好,但需要对每个句子向量化,计算成本更高。

python 复制代码
# 语义分块示例
from langchain_experimental.text_splitter import SemanticChunker
from langchain.embeddings import OpenAIEmbeddings

embeddings = OpenAIEmbeddings()
splitter = SemanticChunker(
    embeddings,
    breakpoint_threshold_type="percentile",  # 用百分位数确定阈值
    breakpoint_threshold=95,                  # 相似度差异超过95百分位时切分
)
chunks = splitter.split_text(long_text)

不同文档类型应选不同分块策略。Markdown 利用标题层级作为边界;代码以函数和类为单位;PDF 需要单独处理表格和图片------表格转结构化文本,图片描述信息(OCR 或视觉模型提取)作为独立块。

文档类型 推荐分块策略 推荐块大小 注意事项
文章/报告 递归字符分块 400-600 Token 保持段落完整性
Markdown 按标题层级分块 300-500 Token 保留标题路径作为元数据
代码文件 按函数/类分块 函数级别 保留导入语句上下文
PDF 版面感知分块 400-600 Token 单独处理表格和图片
对话记录 按对话轮次分块 单轮或多轮 保留时间戳和参与者信息
技术文档 按章节分块 500-800 Token 保留章节编号和标题

分块大小需要平衡检索精度和上下文完整性。较小的块(128-256 Token)语义信号集中,检索精度高,但可能缺少上下文。较大的块(512-1024 Token)上下文完整,但检索信号被稀释。先在测试集上评估不同块大小,选最适合数据集的配置。

进阶技巧是「父子分块」:检索时用较小的子块保证精度,生成时将子块所属的父块(更大的上下文)注入 Prompt,兼顾检索精确性和上下文完整性。LangChain 中通过 ParentDocumentRetriever 实现。

检索是 RAG 的核心环节,检索质量直接决定生成质量的上限。单一检索方式各有局限:纯向量检索擅长语义匹配但可能遗漏精确匹配的关键词;纯关键词检索(BM25, Best Matching 25)擅长精确匹配但无法理解语义。Hybrid Search (混合检索) 将两者结合,是工业级 RAG 的标准配置。

向量检索将查询和文档都编码为高维向量,计算相似度(通常用余弦相似度或内积)。它使用双塔模型(Bi-Encoder),查询和文档独立编码,因此可以离线预计算文档向量,在线检索时只需计算查询向量与预存向量的相似度,速度快。

向量检索的优势在于语义理解:查询「如何提高系统性能」可以检索到「优化程序运行效率」的文档,即使没有共同关键词。弱点是对精确匹配不敏感------查特定产品编号、人名、代码片段时,向量检索可能无法精确命中。

关键词检索(BM25)基于词频统计:一个词在文档中出现越多、在整个语料库中出现越少,该文档与该词的相关性越高。BM25 对精确匹配非常敏感,适合检索特定 ID、专有名词、技术术语。

Hybrid Search 的实现是并行执行两种检索,然后通过融合算法合并结果。最常用的是 RRF (Reciprocal Rank Fusion, 倒数排名融合):对每个文档,计算它在各检索结果列表中排名的倒数之和,按融合分数排序。公式简单但有效。

python 复制代码
# Hybrid Search 实现
from langchain.retrievers import EnsembleRetriever, BM25Retriever

# 向量检索
vector_retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 50}
)

# 关键词检索
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 50

# 混合检索(加权融合)
hybrid_retriever = EnsembleRetriever(
    retrievers=[vector_retriever, bm25_retriever],
    weights=[0.6, 0.4]  # 向量检索权重更高
)

Hybrid Search 之后通常接 Reranking (重排序) 模块。必要性在于:向量检索的双塔模型为了效率牺牲了精度------查询和文档独立编码,无法捕获深层交互特征。重排序使用 Cross-Encoder,将查询和文档拼接输入模型,更精确地评估相关性。

Cross-Encoder 的计算成本远高于 Bi-Encoder。Bi-Encoder 只需计算一次查询向量,然后与所有文档向量做点积;Cross-Encoder 需要对每个 (query, document) 对单独前向传播。因此工程中采用两阶段架构:先用 Bi-Encoder 召回 Top-50 到 Top-100 候选,再用 Cross-Encoder 精排取 Top-5 到 Top-10。

bash 复制代码
检索两阶段架构:

用户查询
    │
    v
┌──────────────────┐
│ 第一阶段:召回     │
│ Hybrid Search    │──→ 50-100 候选文档
│ (Bi-Encoder+BM25)│
└──────────────────┘
    │
    v
┌──────────────────┐
│ 第二阶段:精排     │
│ Cross-Encoder    │──→ 5-10 最终文档
│ Reranker         │
└──────────────────┘
    │
    v
┌──────────────────┐
│ 上下文组装 + 生成  │
└──────────────────┘
python 复制代码
# 使用 BGE Reranker 进行重排序
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder

# 加载 Cross-Encoder 模型
reranker = HuggingFaceCrossEncoder(
    model_name="BAAI/bge-reranker-v2-m3"
)

# 构建重排序压缩器
compressor = CrossEncoderReranker(
    model=reranker,
    top_n=5  # 保留 Top-5
)

# 将重排序器与检索器组合
from langchain.retrievers import ContextualCompressionRetriever
final_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=hybrid_retriever
)

常用 Reranker 模型:Cohere Rerank(商业 API,效果好但付费)、BGE-Reranker 系列(开源,支持中英文,推荐 bge-reranker-v2-m3)、cross-encoder/ms-marco-MiniLM(开源,英文为主,模型小速度快)。选择时平衡精度、延迟和成本。

一个容易被忽视的优化点是查询向量的编码方式。使用相同 Embedding 模型编码查询和文档是基本要求,进一步可以为查询添加指令前缀。例如 E5 模型中,查询编码为 "query: 如何部署应用",文档编码为 "passage: 部署文档内容...",让模型更好区分查询和文档的编码方式,提升检索精度。

另一个进阶技巧是动态调整 Hybrid Search 权重。包含特定 ID 或专有名词的查询,提高关键词检索权重;开放式语义查询,提高向量检索权重。可以通过规则判断查询类型,或用 LLM 分类后动态调整。

5.5 GraphRAG:知识图谱增强的检索范式

传统 RAG 基于文本块的向量检索,处理需要跨文档推理的问题时表现不佳。例如「公司A的CEO和公司B的CEO是否毕业于同一所大学」,传统 RAG 需要检索两个公司的CEO信息,再分别检索两人的教育背景,最后由 LLM 串联推理。任何一环检索失败都会导致答案错误。

GraphRAG (Knowledge Graph-enhanced RAG, 知识图谱增强的检索增强生成) 通过构建结构化的实体-关系图谱解决多跳推理问题。离线索引阶段,GraphRAG 不仅分块和向量化文档,还使用 LLM 或专用 NLP 模型抽取实体和关系,构建知识图谱。

知识图谱中节点代表实体(人、组织、地点等),边代表实体间的关系,每个节点和边可关联原始文本片段作为证据。检索时,GraphRAG 不仅做向量检索找相关文本块,还在图谱上做图遍历,沿实体间的关系边找到多跳关联信息。

GraphRAG 的索引构建比传统 RAG 复杂得多。实体抽取识别命名实体,关系抽取判断实体对之间的关系类型,实体消歧将指代同一实体的不同表述合并,最后用图算法(如 Leiden 算法)将紧密关联的实体聚类为社区并生成摘要。

bash 复制代码
GraphRAG 索引构建流程:

文档块 ──→ 实体抽取 ──→ 关系抽取
                              │
                              v
                         实体消歧/合并
                              │
                              v
                    ┌──── 知识图谱 ────┐
                    │                  │
                    v                  v
              社区检测            向量索引(节点+边)
                    │
                    v
              社区摘要生成

在线查询阶段,GraphRAG 支持两种检索模式。局部检索:从查询中提取实体,在图谱中找到对应节点,遍历相邻节点和边获取相关子图信息,适合具体实体查询如「张三在哪个公司工作」。

全局检索:利用社区摘要回答全局性问题。将所有社区摘要汇总,让 LLM 基于摘要生成回答,适合总结性问题如「这个领域的主要技术趋势是什么」。

python 复制代码
# GraphRAG 概念示例(基于 Microsoft GraphRAG 思路)
from graphrag import GraphRAG

# 索引构建
graphrag = GraphRAG(
    llm=llm,
    embedding_model=embeddings,
    entity_extraction_prompt=ENTITY_EXTRACTION_TEMPLATE,
    community_detection_algorithm="leiden"
)

# 从文档构建知识图谱
graphrag.index(documents)

# 查询:支持局部检索和全局检索
local_result = graphrag.query(
    "张三和李四的关系是什么",
    mode="local"   # 局部图遍历
)
global_result = graphrag.query(
    "这个文档集的主要主题有哪些",
    mode="global"  # 全局社区摘要
)
维度 传统 RAG GraphRAG
知识结构 扁平的文本块向量 结构化的实体-关系图
检索方式 语义相似度匹配 图遍历 + 语义检索
多跳推理 弱(依赖 LLM 串联) 强(沿图谱边遍历)
全局视角 缺失 支持(社区摘要)
构建成本 低 高(需实体抽取和关系建模)
查询延迟 低 中等
适用场景 事实查询、段落检索 关系推理、全局总结

GraphRAG 的主要瓶颈在构建成本。实体抽取和关系建模需要大量 LLM 推理,大规模文档库的索引成本可能比传统 RAG 高一个数量级。因此 GraphRAG 通常用于对推理深度要求较高的场景,而非所有 RAG 应用的默认选择。

工程中更务实的是混合架构:对核心知识构建知识图谱用于深度推理,对全量文档保持传统向量索引用于广度检索。查询时根据问题类型选路径------实体关系类走图谱检索,事实查询类走向量检索,在成本和效果之间取得平衡。

另一个值得关注的方向是 LightRAG,它简化了 GraphRAG 的索引流程,通过轻量级实体抽取和双层检索(实体级和文档级)降低构建成本,同时保留图结构的推理优势。资源有限的团队值得评估。

5.6 查询改写与多轮对话 RAG

用户提问时往往不会给出一个完美的检索查询。问题可能包含指代词(「他的公司」)、过于简短(「价格呢」)、包含多个子问题、或使用与文档库不一致的术语。查询改写 (Query Rewriting) 的目标是将原始问题转化为更适合检索的查询,提升召回率和精确率。

查询改写有多种策略,针对不同的问题类型。

指代消解是最基础的策略,用于多轮对话。用户说「他的公司在哪里」时,「他」指代前文提到的人物,通过 LLM 将指代词替换为具体实体,生成独立查询:「张三的公司在哪里」。

查询扩展将一个问题拆分为多个子问题分别检索。例如「比较 React 和 Vue 的性能」拆分为「React 的性能特点」和「Vue 的性能特点」,分别检索后合并结果,适合对比类和综合类问题。

HyDE (Hypothetical Document Embeddings, 假设文档嵌入) 是一种巧妙的改写策略。核心洞察:与查询语义相似的文档,和与查询假设答案语义相似的文档,往往不是同一批文档。HyDE 先让 LLM 生成假设性回答,再用假设回答(而非原始查询)做向量检索。假设回答在文本风格和内容上更接近目标文档,检索效果往往更好。

python 复制代码
# HyDE 实现
from langchain.retrievers import HypotheticalDocumentEmbedder

hyde_retriever = HypotheticalDocumentEmbedder.from_llm(
    llm=llm,
    base_embeddings=embeddings,
    prompt_template="请回答以下问题:\n{question}\n\n回答:"
)

# HyDE 内部流程:
# 1. LLM 生成假设回答
# 2. 对假设回答做向量化
# 3. 用假设回答的向量检索文档
docs = hyde_retriever.get_relevant_documents("什么是 RAG?")

多查询生成 (Multi-Query Generation) 让 LLM 从不同角度生成多个查询,分别检索后合并去重,通过增加查询多样性提升召回率,适合模糊查询或宽泛主题的检索。

python 复制代码
# 多查询生成
from langchain.retrievers import MultiQueryRetriever

multi_query_retriever = MultiQueryRetriever.from_llm(
    retriever=vectorstore.as_retriever(),
    llm=llm,
    include_original=True  # 包含原始查询
)
# LLM 会生成如:
# 原始查询:"RAG 评估方法"
# 改写1:"RAG 系统的评估指标有哪些"
# 改写2:"如何衡量 RAG 的检索质量"
# 改写3:"RAG 生成质量的评估框架"

多轮对话 RAG 的核心挑战是上下文依赖。用户在后续问题中经常省略前文信息或使用指代词,直接用原始问题做检索效果很差。标准做法是「对话压缩与查询改写」:将对话历史和当前问题输入 LLM,生成包含完整上下文的独立查询。

python 复制代码
# 多轮对话查询改写
from langchain.chains import create_history_aware_retriever
from langchain_core.prompts import ChatPromptTemplate

condense_prompt = ChatPromptTemplate.from_messages([
    ("system", "基于对话历史,将用户最后的问题改写为独立的检索查询。"
               "不要回答问题,只改写。"),
    ("human", "对话历史:{chat_history}\n\n当前问题:{input}")
])

history_aware_retriever = create_history_aware_retriever(
    llm=llm,
    retriever=final_retriever,
    prompt=condense_prompt
)

除了查询改写,多轮对话 RAG 还需要考虑对话级的状态管理。进阶方案是维护「对话知识状态」:每轮对话后提取已建立的事实和待确认的信息,作为后续检索的补充上下文,类似于人类在多轮讨论中逐步积累共识。

工程经验:查询改写的质量取决于 LLM 能力和改写 Prompt 设计。改写 Prompt 应明确指示 LLM 不要尝试回答问题,只专注生成好的检索查询。同时保留原始查询作为兜底------改写查询的检索结果不理想时,回退到原始查询。

5.7 RAG 系统评估:从检索质量到生成质量

RAG 评估是一个多维度的工程问题。与纯生成模型不同,RAG 的输出质量同时取决于检索质量和生成质量,需要分别评估和联合评估。业界较成熟的评估框架有 RAGAS (RAG Assessment, RAG 评估框架) 和 TruLens。

RAG 评估分为三个维度:检索质量、生成质量和端到端效果。

检索质量评估关注系统能否找到正确的文档。核心指标包括 Recall@K(Top-K 结果中包含相关文档的比例)、Precision@K(Top-K 中相关文档的占比)、MRR (Mean Reciprocal Rank, 平均倒数排名)。Recall@K 是最关键的指标------检索阶段就遗漏了正确文档,生成阶段无法弥补。

生成质量评估关注 LLM 是否正确使用了检索到的信息。核心指标:Faithfulness (忠实度,回答中的陈述是否都能从检索上下文找到支持,用于检测幻觉)、Answer Relevance (回答相关性,回答是否真正回应了用户问题)、Context Relevance (上下文相关性,检索到的上下文是否与问题相关)。

端到端评估关注最终用户体验。核心指标:Answer Correctness (回答正确性,回答是否事实正确)、Citation Accuracy (引用准确性,标注的来源是否正确指向支持文档)。通常需要人工标注的 ground truth 作为参照。

评估维度 指标 含义 评估方法
检索质量 Recall@K Top-K 中相关文档的召回率 人工标注相关文档
检索质量 Precision@K Top-K 中相关文档的精确率 人工标注相关文档
检索质量 MRR 相关文档的排名倒数均值 人工标注相关文档
生成质量 Faithfulness 回答是否忠实于检索内容 LLM 辅助评估
生成质量 Answer Relevance 回答与问题的相关性 LLM 辅助评估
生成质量 Context Relevance 上下文与问题的相关性 LLM 辅助评估
端到端 Answer Correctness 回答是否正确 人工评估或 LLM 辅助
端到端 Citation Accuracy 引用来源是否准确 人工评估
python 复制代码
# 使用 RAGAS 评估 RAG 系统
from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall,
)
from datasets import Dataset

# 准备评估数据
eval_data = {
    "question": ["什么是RAG?", "GraphRAG有什么优势?"],
    "answer": ["RAG是检索增强生成技术...", "GraphRAG支持多跳推理..."],
    "contexts": [["RAG通过检索外部知识..."], ["GraphRAG构建知识图谱..."]],
    "ground_truth": ["RAG结合检索和生成...", "GraphRAG通过图遍历支持多跳推理..."]
}
dataset = Dataset.from_dict(eval_data)

# 执行评估
results = evaluate(
    dataset,
    metrics=[
        context_precision,    # 上下文精确率
        context_recall,       # 上下文召回率
        faithfulness,         # 忠实度
        answer_relevancy,     # 回答相关性
    ]
)
print(results)
# 输出示例:
# {'context_precision': 0.85, 'context_recall': 0.92,
#  'faithfulness': 0.88, 'answer_relevancy': 0.91}

Faithfulness 指标的评估方法值得深入理解。它将回答拆分为一系列原子陈述(atomic statements),逐一检查每个陈述是否能在检索上下文中找到支持。例如「RAG 由 Facebook 在 2020 年提出」会被拆分为「RAG 是一种技术」「RAG 由 Facebook 提出」「RAG 在 2020 年提出」三个陈述,分别验证。能有效检测部分幻觉------回答中大部分正确但混入少量编造信息的情况。

构建高质量评估数据集是 RAG 评估的关键挑战。人工标注成本高,难以大规模进行。实用方案是「LLM 生成 + 人工审核」:用 LLM 从文档中生成问答对,人工审核质量后作为评估数据。可以快速生成大量样本,但 LLM 生成的问答对可能偏向简单问题,覆盖面有限。

分层评估是定位问题的最佳实践:先评估检索质量,确保检索阶段没问题;再评估生成质量,验证 LLM 正确使用了检索结果;最后做端到端评估。检索质量好但端到端效果差,说明问题出在生成阶段------可能是 Prompt 设计不当或 LLM 能力不足。

持续监控也是生产级 RAG 系统的必要环节。记录每个查询的检索结果、生成回答和用户反馈(如点赞/点踩),持续追踪系统性能,发现退化趋势。LangSmith、Phoenix 等工具提供 RAG 可观测性方案,支持查询级别的 trace 分析和聚合指标监控。

5.8 RAG 在 Agent 中的角色:知识工具与记忆增强

在 Agent 系统中,RAG 的角色从「独立的问答系统」转变为「Agent 可调用的知识工具」。Agent 在规划阶段主动决定何时检索知识、检索什么内容、如何使用检索结果。RAG 成为 Agent 感知世界的重要通道之一。

Agent 调用 RAG 的典型场景:回答事实性问题前检索相关知识确保准确性;执行复杂任务时检索操作手册或 API 文档;需要引用权威来源时检索知识库获取支撑材料;遇到领域特定问题时检索企业内部知识库。

将 RAG 作为 Agent 工具实现,需要定义清晰的工具接口。工具接收查询字符串,返回检索到的文档片段,Agent 通过 function calling 机制决定何时调用。

python 复制代码
# 将 RAG 定义为 Agent 工具
from langchain.tools import Tool

def knowledge_search(query: str) -> str:
    """检索企业知识库,返回相关知识片段。
    
    Args:
        query: 检索查询,应该是一个清晰的问题或关键词。
    
    Returns:
        检索到的知识片段,包含来源信息。
    """
    docs = final_retriever.get_relevant_documents(query)
    results = []
    for i, doc in enumerate(docs, 1):
        source = doc.metadata.get("source", "未知来源")
        results.append(f"[{i}] 来源: {source}\n{doc.page_content}")
    return "\n\n".join(results)

knowledge_tool = Tool(
    name="knowledge_search",
    description="当需要查找企业内部知识、产品文档、技术规范时使用此工具。",
    func=knowledge_search
)

# 将工具注册到 Agent
agent = create_react_agent(
    llm=llm,
    tools=[knowledge_tool, search_tool, calculator_tool],
    prompt=agent_prompt
)

RAG 与 Agent 记忆系统的关系是一个重要的设计话题。Agent 的记忆系统通常分为短期记忆(当前对话上下文窗口)和长期记忆(跨会话的持久化存储)。RAG 在其中扮演「外部知识」角色------提供客观事实和领域知识,而非 Agent 的个人经历。

更高级的设计是将 Agent 的记忆本身也用 RAG 方式存储和检索。交互历史(对话记录、执行的操作、用户反馈)被分块、向量化存入向量数据库,需要时通过检索调用。Agent 可以回忆起数天甚至数周前的交互细节,突破上下文窗口限制。

python 复制代码
# Agent 记忆的 RAG 化存储
class AgentMemoryStore:
    def __init__(self, vectorstore, llm):
        self.vectorstore = vectorstore
        self.llm = llm
    
    def store_interaction(self, user_input, agent_response, context):
        """存储一次交互到记忆库"""
        memory_text = (
            f"用户: {user_input}\n"
            f"Agent: {agent_response}\n"
            f"时间: {context.get('timestamp', '')}\n"
            f"会话: {context.get('session_id', '')}"
        )
        self.vectorstore.add_texts(
            texts=[memory_text],
            metadatas=[context]
        )
    
    def recall(self, query, k=5):
        """从记忆库中检索相关历史交互"""
        docs = self.vectorstore.similarity_search(query, k=k)
        return [doc.page_content for doc in docs]

在这种设计中,Agent 拥有两种检索通道:知识检索(RAG 工具,检索外部知识库)和记忆检索(检索自身交互历史)。两者协同工作------知识检索提供事实信息,记忆检索提供上下文和偏好。例如用户说「像上次那样帮我生成报告」时,Agent 先检索记忆找到「上次」的交互记录,再检索知识库获取报告模板和规范。

Agent 场景下的 RAG 还有一个特殊需求:主动检索。传统 RAG 是被动触发的------用户提问后检索。Agent 可以在任务执行过程中主动判断是否需要检索,例如编写代码时遇到不熟悉的 API,主动暂停当前任务,调用知识检索查找 API 文档,然后继续编码。

实现主动检索的关键是让 Agent 具备「知识边界感知」能力------知道自己不知道什么。可以通过系统 Prompt 设置规则:遇到不确定的事实性问题先检索再回答;操作特定领域工具时先查阅文档;用户引用了之前的交互时先检索记忆。更高级的方案是让 Agent 自我评估对某个问题的确信度,低于阈值时触发检索。

一个值得关注的架构模式是「RAG-as-Tool」与「Always-RAG」的结合。核心领域知识设置为「Always-RAG」------每次回答自动检索,不需要 Agent 决定。辅助性知识设置为「RAG-as-Tool」------Agent 根据需要决定是否检索。分层设计在保证核心知识准确性的同时,减少不必要的检索开销。

本章知识点总结

知识点 核心内容 关键细节
RAG 核心原理 检索外部知识注入 Prompt 后生成 解决知识时效性、幻觉、领域不足、不可追溯问题
离线索引 文档加载-解析-分块-向量化-存储 元数据管理不可忽视,Embedding 模型不宜频繁更换
在线查询 查询改写-混合检索-重排序-生成 两阶段架构:先召回再精排
固定长度分块 按 Token 数切分,配合滑动窗口 简单但可能切断语义,需重叠区域缓解
递归字符分块 按分隔符优先级递归切分 适合结构化文档,LangChain 默认策略
语义分块 按语义相似度切分 计算成本高但语义保持好
父子分块 小块检索,大块生成 兼顾检索精度和上下文完整性
Hybrid Search 向量检索 + 关键词检索融合 RRF 融合算法,权重可动态调整
Reranking Cross-Encoder 精排 Bi-Encoder 召回后精排,两阶段架构
GraphRAG 知识图谱增强的检索 支持多跳推理和全局视角,构建成本高
查询改写 指代消解、查询扩展、HyDE、多查询 多轮对话必须做查询改写
HyDE 生成假设文档做检索 假设答案比原始查询更接近目标文档
多轮对话 RAG 对话压缩为独立查询后检索 维护对话级知识状态可进一步提升效果
检索质量评估 Recall@K, Precision@K, MRR Recall@K 是最关键指标
生成质量评估 Faithfulness, Answer Relevance Faithfulness 通过原子陈述逐条验证
RAGAS 评估框架 自动化 RAG 评估工具 支持 LLM 辅助评估,需评估数据集
RAG as Agent Tool RAG 作为 Agent 可调用工具 Agent 主动决定何时检索
记忆 RAG 化 Agent 交互历史向量化存储 突破上下文窗口限制,支持跨会话记忆
主动检索 Agent 自主判断是否需要检索 需要知识边界感知能力
分层 RAG 架构 Always-RAG + RAG-as-Tool 核心知识自动检索,辅助知识按需检索
相关推荐
高洁011 小时前
具身智能中的世界模型训练
人工智能·python·深度学习·机器学习·transformer
无线通信科研笔记1 小时前
IEEE TVT 2026 论文精读与完整复现|相位误差如何重塑近场 RIS 的幅相响应
论文阅读·人工智能·python·算法·论文笔记
朝朝辞暮i2 小时前
VLA 系统学习第 1 课:VLA 到底在干什么?
人工智能·python·计算机视觉·vla
2601_962885722 小时前
如何用 Python 自动识别股票的支撑位与压力位?
开发语言·python
mmmmath_32 小时前
二叉树の递归遍历
算法
All for pursuit.3 小时前
【位运算-1】338.比特位计数
数据结构·c++·算法·leetcode
北冥有鱼被烹3 小时前
VCSEL全景解析:与光模块NPO CPO的关系、市场量化分析与产业链影响
python
caoerzhong3 小时前
中小企业上 WMS 该先上哪几块:JeeWMS 开源 Java 仓库管理系统的分批上线清单
java·python·开源
正在走向自律3 小时前
AI数据分析与可视化:从基础到应用实践
服务器·人工智能·python·机器学习·数据分析·pandas