本章将深入探讨 RAG 系统的核心——检索增强技术。我们将从最基础的相似度搜索开始,逐步讲解如何通过各种高级检索策略和优化技巧,确保 RAG 系统能够从海量知识库中精准、高效地找到最相关的上下文信息,从而显著提升生成答案的准确性和质量。
4.1 基础检索:向量相似度搜索
向量相似度搜索是 RAG 检索模块的基石。在第二章和第三章中我们了解到,文本内容被转换为高维向量并存储在向量数据库中。当用户发起查询时,查询本身也被向量化,然后通过计算查询向量与知识库中存储向量的相似度,来找出语义上最接近的文档片段。
向量相似度概念
在向量空间中,两个向量的相似度通常通过它们之间的距离或夹角来衡量。常用的相似度度量包括:
-
余弦相似度 (Cosine Similarity):
-
概念: 衡量两个向量在 N 维空间中夹角的余弦值。余弦相似度越接近 1,表示两个向量方向越一致,语义越相似;接近 0 表示几乎不相关;接近 -1 表示方向相反,语义相反。它只关注方向,不关注向量的长度(模)。
-
公式: 对于两个向量 A 和 B,其余弦相似度 cos(theta) 定义为:
c o s ( θ ) = A ⋅ B ∥ A ∥∥ B ∥ = ∑ i = 1 n A i B i ∑ i = 1 n A i 2 ∑ i = 1 n B i 2 cos(θ)= \frac{A⋅B}{∥A∥∥B∥}=\frac{∑_{ i=1}^nA_iB_i}{\sqrt{∑_{i=1}^nA_{i}^2}\sqrt{∑ _{i=1}^nB_{i}^2}} cos(θ)=∥A∥∥B∥A⋅B=∑i=1nAi2∑i=1nBi2∑i=1nAiBi
-
适用场景: 在文本相似度比较中最为常用,因为文本向量的长度通常不代表其重要性,而方向更能反映语义。
-
-
欧氏距离 (Euclidean Distance):
-
概念: 衡量两个向量在 N 维空间中的直线距离,也称为 L2 范数距离。距离越小,表示两个向量越相似。
-
公式: 对于两个向量 A 和 B,其欧氏距离 d(A,B) 定义为:
d ( A , B ) = ∑ i = 1 n ( A i − B i ) 2 d(A,B)= \sqrt{∑_{i=1}^n(Ai−Bi)^2} d(A,B)=∑i=1n(Ai−Bi)2
-
适用场景: 适用于特征的绝对值有意义的场景。在某些 Embedding 模型中,也会将其作为距离度量。
-
-
点积 (Dot Product) / 内积:
- 概念: 两个向量的对应元素相乘再求和。如果向量已经过归一化(即模长为1),则点积等价于余弦相似度。
- 公式: 对于两个向量 A 和 B,其点积 AcdotB 定义为: A ⋅ B = ∑ i = 1 n A i B i A⋅B=∑_{i=1}^nA_iB_i A⋅B=∑i=1nAiBi
- 适用场景: 计算效率高,常用于优化后的向量数据库索引。
**** 这些距离和相似度度量是线性代数和几何学的基本概念,在机器学习和信息检索领域被广泛应用。
向量数据库查询接口
主流的向量数据库(如 Pinecone, Weaviate, Milvus, Qdrant)都提供了直观的 API 接口来执行 Top-K 相似度搜索。其基本流程如下:
-
连接数据库: 使用对应的 SDK 初始化与向量数据库的连接。
-
准备查询向量: 将用户输入的查询文本通过与知识库构建时相同的 Embedding 模型,转换为查询向量。
-
执行 Top-K 搜索:调用数据库的查询方法,传入查询向量和希望返回的结果数量 k。
- 数据库会根据其内部索引和选择的相似度度量(通常在创建索引时指定),从海量向量中找出与查询向量最相似的
k
个向量。 - 同时,数据库会返回这些相似向量对应的原始文本内容以及在存储时关联的元数据。
- 数据库会根据其内部索引和选择的相似度度量(通常在创建索引时指定),从海量向量中找出与查询向量最相似的
-
接收结果: 结果通常是一个列表,包含每个检索到的文档块(及其得分和元数据)。
示例(以 Python SDK 为例):
# 假设已初始化向量数据库客户端 'vector_db_client'
# 假设已初始化 Embedding 模型 'embedding_model'
user_query = "如何申请加班费?"
# 1. 将用户查询向量化
query_vector = embedding_model.encode(user_query).tolist()
# 2. 执行 Top-K 相似度搜索
# 这里的 'index_name' 是你存储知识库的索引名称
# 'top_k' 表示希望返回最相关的 k 个结果
search_results = vector_db_client.query(
vector=query_vector,
top_k=5,
include_metadata=True, # 包含元数据
include_values=True # 包含原始向量值(可选,通常不需要)
)
# 3. 处理检索结果
retrieved_chunks = []
for match in search_results.matches:
chunk_text = match.metadata['text_content'] # 假设元数据中包含原始文本
document_title = match.metadata.get('title', '未知标题')
score = match.score
retrieved_chunks.append({
"text": chunk_text,
"title": document_title,
"score": score
})
# retrieved_chunks 现在包含了最相关的文档片段,可用于构建 LLM 的 Prompt
检索结果可视化与初步分析
初步的检索结果可视化和分析对于理解 RAG 系统的行为至关重要。
- 直观查看检索结果:
- 将检索到的
top_k
个文档片段及其相似度得分直接打印出来,人工检查它们与用户查询的相关性。 - 如果提供了文档来源(如 URL 或标题),将这些信息一同展示,有助于理解上下文。
- 将检索到的
- 评估初始召回质量:
- 相关性判断: 核心问题是:这些检索到的文档片段是否真的能够帮助 LLM 回答用户的问题?有没有无关的“噪音”?有没有遗漏关键信息?
- 多样性: 检索结果是否过于集中在某个方面,导致其他相关信息被忽略?
- Top-K 效果: 最佳答案是否出现在 Top-1 或 Top-3 中?如果相关文档排位靠后,可能需要优化检索策略。
工具与方法:
- 命令行/Jupyter Notebook 打印: 最简单直接的方式。
- 自定义 Web 界面: 可以构建一个简单的调试界面,展示查询、检索结果、相似度分数和元数据。
- 可视化工具: 对于更深入的分析,可以考虑使用降维技术(如 t-SNE, UMAP)将高维向量投影到二维或三维空间,并可视化查询向量与知识库中文档向量的分布,直观判断相似度关系。但这通常用于模型调试而非日常运维。
通过对基础检索结果的初步分析,我们可以发现潜在的问题,为后续的检索增强策略提供方向。
4.2 检索增强策略
仅仅依靠基础的向量相似度搜索往往不足以应对复杂的查询和多样化的知识库。检索增强策略旨在通过在检索流程的不同阶段(预处理、后处理)引入额外的逻辑或模型,来提升检索的精度和召回率。
预处理阶段的增强
这些策略在用户查询到达向量数据库之前对其进行优化,以提高查询与知识库的匹配度。
-
查询改写 (Query Rewriting):
- 概念: 利用 LLM 对用户的原始查询进行改写或重新表述,使其更清晰、更具体,或包含更多潜在的关键词,从而提高与知识库文档的匹配度。特别适用于模糊、口语化或多意图的查询。
- 应用场景:
- 处理省略上下文的查询: 例如,用户问“那项政策是什么?”(承接上文),LLM 可以改写为“关于上次讨论的健康保险政策是什么?”。
- 处理多意图查询: 用户问“如何请假,年假和病假的区别?” LLM 可以将其拆分为两个独立的查询进行检索。
- 优化关键词: 将口语化的表达改写为更专业的术语。
- 实现方式: 将原始查询和明确的指令(例如“请你改写以下用户问题,使其更适合在文档库中进行检索,补充必要的上下文信息。”)作为 Prompt 输入给 LLM,获取改写后的查询。
- 这种利用 LLM 进行查询增强的思路是 LLM 时代信息检索的新兴方向。
-
查询扩展 (Query Expansion):
-
概念: 在原始查询中添加额外的相关词汇或短语,以扩大检索范围,提高召回率。
-
方法:
-
同义词/近义词扩展: 使用词典或语言模型生成查询词的同义词。
-
相关概念扩展: 识别查询中的核心概念,并添加与其相关的其他概念。
-
假设性文档嵌入 (HyDE, Hypothetical Document Embeddings):这是由 OpenAI 提出的一个强大技术。它利用 LLM 根据用户查询生成一个假设性的、可能的答案(或文档),然后将这个假设性答案进行 Embedding,再用其去检索知识库。由于假设性答案通常比短查询包含更多语义信息,因此能更准确地找到相关文档。
-
-
实现方式: 结合词典、Word Embedding、LLM 调用等技术。
-
-
CoT for Retrieval (CoT-R):
- 概念: CoT-R(Chain-of-Thought for Retrieval)是一种结合思维链(Chain-of-Thought)提示的方法。LLM 不仅直接生成最终答案,还会被引导去生成一系列中间推理步骤,这些步骤可能包含更适合检索的子问题或关键词。
- 作用: LLM 可以通过思考过程,生成比原始查询更具洞察力的、用于检索的查询或指令,从而提升检索质量。例如,用户问“A和B有什么不同?”,CoT-R 可能会让 LLM 先思考“A的特点是什么?”,“B的特点是什么?”,然后用这些子问题去检索。
- 这种方法是 CoT 提示在 RAG 场景的扩展应用。
后处理阶段的增强
这些策略在从向量数据库检索到初步结果之后对其进行优化,以提高结果的精度和 LLM 的输入效率。
- 重排序 (Re-ranking):
- 概念: 向量数据库的 Top-K 检索通常基于纯粹的向量相似度,可能不够精确。重排序是对初步检索到的文档片段进行二次排序,以提升最相关文档的排名。
- 方法:
- 交叉编码器 (Cross-Encoder): 使用一个独立的、更小的 Transformer 模型,该模型同时接收用户查询和检索到的文档片段作为输入,然后输出一个表示两者相关性的分数。交叉编码器由于能同时“看到”查询和文档,通常比独立编码器(用于生成向量嵌入的模型)更准确地判断相关性。
- LLM 重排序: 将用户查询和检索到的多个文档片段一并送入 LLM,要求 LLM 根据其对内容的理解,对这些文档片段进行打分或排序,选出最相关的几个。这通常成本较高,但效果往往很好。
- 优点: 显著提升检索结果的精确率,减少 LLM 处理的噪音。
- 交叉编码器是信息检索和问答系统中常用的技术,例如 Sentence Transformers 库中也包含交叉编码器模型。
- 上下文压缩 (Contextual Compression):
- 概念: 检索到的文档片段可能仍然包含与用户查询不直接相关的信息。上下文压缩旨在从这些片段中提取出最核心、最相关的句子或短语,从而减少输入给 LLM 的 Token 数量。
- 作用: 减少 LLM 的输入长度,降低推理成本,并可能减少 LLM 因无关信息而“迷失”的情况(“Lost in the Middle” 缓解)。
- 实现方式:
- 基于 LLM 的摘要/抽取: 将检索到的文档片段和用户查询一同输入 LLM,要求 LLM 仅抽取其中与查询相关的关键信息或进行简要摘要。
- 基于关键词/TF-IDF 的抽取: 识别查询中的关键词,在检索到的文档片段中抽取包含这些关键词及其周边上下文的句子。
- 这是将文本摘要和信息抽取技术应用于 RAG 流程的优化。
结构化检索与混合检索
纯向量搜索在处理需要精确匹配或结合不同数据类型(如结构化数据)的查询时可能遇到瓶颈。
- 元数据过滤 (Metadata Filtering):
- 概念: 利用存储在向量数据库中的文档元数据(如文档类型、作者、时间范围、权限标签等)对检索结果进行精确过滤。
- 作用: 极大地缩小搜索空间,确保检索结果符合特定的业务规则或用户权限,提高精确度。
- 实现方式: 在向量数据库查询时,除了提供查询向量,还提供元数据过滤条件(例如,
{ "document_type": "policy", "department": "HR" }
)。
- 混合检索 (Hybrid Search):
- 概念: 结合了两种或多种不同的检索方法,以弥补各自的不足,发挥各自的优势。最常见的是结合关键词搜索(Lexical Search,如 BM25)和向量搜索(Vector Search,基于 Embedding)。
- 关键词搜索 (BM25, TF-IDF):
- 原理: 基于文本中关键词的频率和位置来衡量相关性。擅长精确匹配和处理专有名词、代码等,对新词敏感。
- 优点: 对于精确匹配和稀有词汇效果好。
- 缺点: 无法理解语义相似性,对同义词、近义词不敏感。
- BM25(Okapi BM25)是信息检索领域广泛使用的排名函数。
- 结合方式:
- 融合重排序 (RRF, Reciprocal Rank Fusion): 对关键词搜索和向量搜索的结果进行融合,根据它们在各自列表中的排名赋予分数,然后将分数相加得到最终排名。
- 并行检索与融合: 同时执行两种搜索,然后将结果合并或重排序。
- 优点: 兼顾了关键词的精确匹配和语义的理解能力,在各种查询类型下都能获得更鲁棒的检索效果。
4.3 应对 RAG 挑战的高级检索技术
随着 RAG 应用复杂度的提升,一些单次检索难以解决的挑战浮出水面,例如需要多步推理才能得到答案,或者需要处理超长文档。以下是一些应对这些挑战的高级检索技术。
多跳推理 (Multi-Hop Reasoning)
某些复杂问题需要从多个不同的文档片段中提取信息,并将它们串联起来才能得出完整答案。这被称为“多跳推理”。
- 迭代检索 (Iterative Retrieval):
- 概念: LLM 在第一次检索并阅读文档后,可能会意识到需要更多信息才能回答问题。它会根据当前信息提出新的子问题或生成新的查询,然后再次进行检索,如此循环,直到收集到足够的信息。
- 实现方式: 通常通过 Agent 机制(如 LangGraph)来编排这种循环。LLM 扮演决策者,决定是否需要更多信息,以及如何生成下一个检索查询。
- 优点: 能够处理需要逐步推理的问题,模拟人类的查阅过程。
- 图谱检索 (Graph-based Retrieval):
- 概念: 将知识库中的实体(如人物、地点、概念)和它们之间的关系构建成知识图谱。检索时,不仅进行文本相似度搜索,还可以沿着图谱中的关系进行“跳跃”式地查找相关信息。
- 优点: 对于实体关系明确、需要关联多个信息点的问题(如“A和B的共同点是什么?”,“谁发明了X并在哪里工作?”),图谱检索能提供更结构化的、路径明确的答案。
- 实现方式: 需要额外的知识图谱构建和管理模块,例如 Neo4j、TigerGraph 等图数据库。RAG 系统可以首先检索相关文本,然后利用文本中的实体信息在图谱中进行进一步的扩展查询。
- 知识图谱在语义网和信息检索领域有长期研究和应用历史。
长文档处理
传统 RAG 可能难以有效处理非常长的文档(如书籍、年报),因为单纯的切分可能破坏宏观结构,或导致重要信息分散。
- 分层检索 (Hierarchical Retrieval):
- 概念: 建立多层级的知识索引。首先在粗粒度(如整个文档或章节级别)进行检索,找出最相关的文档或章节。然后,再在这些选定的粗粒度单元内部进行细粒度(如段落或块级别)的检索。
- 优点: 模拟人类阅读过程,先了解整体再深入细节,减少无关信息的干扰,提高长文档检索效率。
- 实现方式: 可以为每个章节、文档创建独立的 Embedding 和索引,或者使用 LlamaIndex 等框架提供的多层次索引结构。
- Parent Document Retriever (父文档检索器):
- 概念: 这是 LangChain 提出的一种策略。核心思想是:将原始大文档切分成小块(用于精确检索),但同时保留原始大文档或更大的父块。检索时,先通过小块进行精确相似度搜索,当找到相关的小块后,再返回其对应的更大的父文档或父块作为上下文提供给 LLM。
- 优点: 结合了小块的精确检索能力和大块的完整上下文信息,缓解了“块太小丢失上下文”和“块太大引入噪音”的矛盾。
- LangChain 官方文档和社区中对此有详细的介绍。
Agent-based Retrieval (代理式检索)
将检索能力封装成 LLM 可以自主调用的“工具”,从而让 LLM 在推理过程中动态地决定何时以及如何进行检索。这与我们第二章中提及的 Agent 机制紧密相关。
- 工具使用 (Tool Using):
- 概念: LLM 被赋予使用各种外部工具的能力,检索是其中一种工具。当 LLM 接收到用户查询时,它会首先“思考”(Chain-of-Thought),判断是否需要调用检索工具,如果需要,它会生成调用检索工具的参数(即检索查询)。
- 作用: 赋予 LLM 更大的灵活性和自主性,使其能够根据问题的复杂性,动态地选择合适的工具来获取信息,而不仅仅是依赖预先注入的上下文。
- 实现方式: 通过 LangChain 的 Agent 模块或 LangGraph 来编排。需要定义检索工具的接口(例如,一个
retriever_tool(query: str) -> str
函数),并将其暴露给 LLM。
- RAG as a Tool:
- 概念: 将整个 RAG 流程(包括查询向量化、向量搜索、结果后处理等)封装成一个独立的、可供 Agent 调用的工具。Agent 在需要知识库信息时,不是直接访问向量数据库,而是调用这个“RAG工具”。
- 优点: 实现了模块化和解耦。一个 Agent 可以拥有多个不同的 RAG 工具(例如,一个用于产品知识,一个用于公司政策),或结合其他类型的工具(如代码解释器、计算器、API 调用工具)。
- 这种将复杂系统封装为工具的模式在软件工程和 LLM Agent 设计中越来越常见。
4.4 检索效果评估
无论多么先进的检索策略,其效果都需要量化评估。检索效果评估是 RAG 系统迭代优化的关键环节,它帮助我们理解当前系统的表现,并指导我们进行有针对性的改进。
评估指标
评估检索效果的指标通常借鉴信息检索(Information Retrieval, IR)领域的标准:
- 召回率 (Recall):
- 概念: 在所有相关的文档中,有多少被检索系统成功找回。
- 公式: Recall=fractext相关且被检索到的文档数量text所有相关文档的数量
- 作用: 衡量检索系统的“查全”能力,即找到所有相关信息的比例。
- 准确率 (Precision):
- 概念: 在所有检索到的文档中,有多少是真正与查询相关的。
- 公式: P r e c i s i o n = 相关且被检索到的文档数量 所有检索到的文档数量 Precision=\frac{相关且被检索到的文档数量}{所有检索到的文档数量} Precision=所有检索到的文档数量相关且被检索到的文档数量
- 作用: 衡量检索系统的“查准”能力,即检索结果中噪音的比例。
- MRR (Mean Reciprocal Rank):
- 概念: 衡量检索到的第一个相关文档的排名位置。如果第一个相关文档排在第 k 位,则 Reciprocal Rank 为 1/k。MRR 是对多个查询的 Reciprocal Rank 的平均值。
- 公式: 对于 N 个查询, $MRR=\frac1N∑_{i=1}^N\frac1{rank_i} $(其中 rank_i 是第 i 个查询的第一个相关文档的排名)。
- 作用: 对于“一击即中”的场景(如问答系统),MRR 是一个很好的指标,因为它高度重视相关结果出现在高位。
- NDCG (Normalized Discounted Cumulative Gain):
- 概念: 一个更复杂的指标,它不仅考虑了相关文档的排名位置,还考虑了相关性的级别(例如,文档可能是高度相关、中度相关或低度相关)。排名越靠前,且相关性级别越高,对 NDCG 的贡献越大。
- 作用: 在需要衡量排序质量和多级相关性时非常有用,广泛应用于搜索引擎和推荐系统。
评估方法
- 离线评估:
- 概念: 在没有真实用户参与的情况下,基于预先标注好的数据集进行自动化评估。
- 步骤:
- 构建测试集: 准备一组代表性的用户查询。
- 人工标注相关性: 对于每个查询,人工标注知识库中与之相关的所有文档片段。这是最耗时但最关键的一步。
- 运行检索系统: 使用这些查询去检索知识库。
- 计算指标: 根据检索结果和人工标注的相关性,自动计算召回率、准确率、MRR、NDCG 等指标。
- 优点: 自动化、可重复、成本相对较低(一旦数据集构建完成),适合快速迭代和调试。
- 缺点: 依赖标注数据集的质量和覆盖度;可能无法完全模拟真实用户行为。
- 工具: LangChain 和 LlamaIndex 都提供了评估模块,可以与 Ragas 等专门的 RAG 评估库结合使用。
- 在线 A/B 测试:
- 概念: 在真实用户流量中,将不同版本的 RAG 系统(例如,A 组使用旧的检索策略,B 组使用新的检索策略)进行并行测试,通过收集用户行为数据来评估效果。
- 收集指标: 用户点击率、答案满意度(通过显式评分或隐式行为如再次提问率)、会话时长、问题解决率等。
- 优点: 最能反映真实世界的效果,数据真实可靠。
- 缺点: 实施复杂,需要流量支持,周期较长,结果受多种因素影响。
- 人工评估:
- 概念: 邀请领域专家或志愿者对检索结果和生成答案进行人工打分和评论。
- 方法: 设计一套打分标准(例如,0-5分的相关性、流畅性、事实准确性),让人工评估员对照标准进行打分。
- 优点: 最可靠、最细致的评估方法,能够发现自动化评估难以捕捉的细微问题(如语义理解错误、细微的事实偏差)。
- 缺点: 成本极高,耗时耗力,扩展性差,难以大规模应用。通常作为离线评估的补充或小规模、高质量验证。
评估流程建议:
在 RAG 项目中,通常会采用离线评估为主,结合小规模人工评估和必要的在线 A/B 测试。离线评估用于快速迭代,人工评估用于提供深入反馈和校准,A/B 测试用于最终验证和决策。