当前位置: 首页 > news >正文

RAG-检索进阶

在基础的 RAG 流程中,依赖向量相似度从知识库中检索信息。然而,这种方法存在一些固有的局限性,例如最相关的文档不总是在检索结果的顶端,以及语义理解的偏差等。为了构建更强大、更精准的生产级 RAG 应用,需要引入更高级的检索技术。
在这里插入图片描述

一、重排序 (Re-ranking)

1.1 RRF (Reciprocal Rank Fusion)

我们在 混合检索章节 中已经接触过 RRF。它是一种简单而有效的零样本重排方法,不依赖于任何模型训练,而是纯粹基于文档在多个不同检索器(例如,一个稀疏检索器和一个密集检索器)结果列表中的排名来计算最终分数。

一个文档如果在多个检索结果中都排名靠前,那么它很可能更重要。RRF 通过计算排名的倒数来为文档打分,有效融合了不同检索策略的优势。但是如果只考虑排名信息,会忽略原始的相似度分数,可能丢失部分有用信息。

1.2 RankLLM / LLM-based Reranker

以下是一个文档列表,每个文档都有一个编号和摘要。同时提供一个问题。请根据问题,按相关性顺序列出您认为需要查阅的文档编号,并给出相关性分数(1-10分)。请不要包含与问题无关的文档。示例格式:
文档 1: <文档1的摘要>
文档 2: <文档2的摘要>
...
文档 10: <文档10的摘要>问题: <用户的问题>回答:
Doc: 9, Relevance: 7
Doc: 3, Relevance: 4
Doc: 7, Relevance: 3

1.3 Cross-Encoder 重排

Cross-Encoder(交叉编码器)能提供出色的重排精度2。它的工作原理是将查询(Query)和每个候选文档(Document)拼接成一个单一的输入(例如,[CLS] query [SEP] document [SEP]),然后将这个整体输入到一个预训练的 Transformer 模型(如 BERT)中,模型最终会输出一个单一的分数(通常在 0 到 1 之间),这个分数直接代表了文档与查询的相关性。

注:[SEP] 是在 BERT 这类基于 Transformer 架构的模型中,用于分隔不同文本片段(如查询和文档)的特殊标记。
在这里插入图片描述
上图清晰地展示了 Cross-Encoder 的工作流程:

初步检索:搜索引擎首先从知识库中召回一个初始的文档列表(例如,前 50 篇)。
逐一评分:对于列表中的每一篇文档,系统都将其与原始查询配对,然后发送给 Cross-Encoder 模型。
独立推理:模型对每个“查询-文档”对进行一次完整的、独立的推理计算,得出一个精确的相关性分数。
返回重排结果:系统根据这些新的分数对文档列表进行重新排序,并将最终结果返回给用户。
这个流程凸显了其高精度的来源(同时分析查询和文档),也解释了其高延迟的原因(需要N次独立的模型推理)。

常见的 Cross-Encoder 模型包括 ms-marco-MiniLM-L-12-v2、ms-marco-TinyBERT-L-2-v2 等。

1.4 ColBERT 重排

ColBERT(Contextualized Late Interaction over BERT)是一种创新的重排模型,它在 Cross-Encoder 的高精度和双编码器(Bi-Encoder)的高效率之间取得了平衡3。采用了一种“后期交互”机制。

其工作流程如下:

独立编码:ColBERT 分别为查询(Query)和文档(Document)中的每个 Token 生成上下文相关的嵌入向量。这一步是独立完成的,可以预先计算并存储文档的向量,从而加快查询速度。
后期交互:在查询时,模型会计算查询中每个 Token 的向量与文档中每个 Token 向量之间的最大相似度(MaxSim)。
分数聚合:最后,将查询中所有 Token 得到的最大相似度分数相加,得到最终的相关性总分。
通过这种方式,ColBERT 避免了将查询和文档拼接在一起进行昂贵的联合编码,同时又比单纯比较单个 [CLS] 向量的双编码器模型捕捉了更细粒度的词汇级交互信息。

1.5 重排方法对比

为了更直观地理解不同重排方法的特点和适用场景,下表对讨论过的几种主流方法进行了总结:
在这里插入图片描述

二、压缩 (Compression)

“压缩”技术旨在解决一个常见问题:初步检索到的文档块(Chunks)虽然整体上与查询相关,但可能包含大量无关的“噪音”文本。将这些未经处理的、冗长的上下文直接提供给 LLM,不仅会增加 API 调用的成本和延迟,还可能因为信息过载而降低最终生成答案的质量。

压缩的目标就是对检索到的内容进行“压缩”和“提炼”,只保留与用户查询最直接相关的信息。这可以通过两种主要方式实现:

内容提取:从文档中只抽出与查询相关的句子或段落。
文档过滤:完全丢弃那些虽然被初步召回,但经过更精细判断后认为不相关的整个文档。

2.1 LangChain 的 ContextualCompressionRetriever

LangChain 提供了一个强大的组件 ContextualCompressionRetriever 来实现上下文压缩4。它像一个包装器,包裹在基础的检索器(如 FAISS.as_retriever())之上。当基础检索器返回文档后,ContextualCompressionRetriever 会使用一个指定的 DocumentCompressor 对这些文档进行处理,然后再返回给调用者。

LangChain 内置了多种 DocumentCompressor:

LLMChainExtractor: 这是最直接的压缩方式。它会遍历每个文档,并利用一个 LLM Chain 来判断并提取出其中与查询相关的部分。这是一种“内容提取”。
LLMChainFilter: 这种压缩器同样使用 LLM,但它做的是“文档过滤”。它会判断整个文档是否与查询相关,如果相关,则保留整个文档;如果不相关,则直接丢弃。
EmbeddingsFilter: 这是一种更快速、成本更低的过滤方法。它会计算查询和每个文档的嵌入向量之间的相似度,只保留那些相似度超过预设阈值的文档。

代码示例
自定义 ColBERTReranker 的代码实现:

# 初始化配置...(略)# 1. 加载和处理文档
loader = TextLoader("../../data/C4/txt/ai.txt", encoding="utf-8")
documents = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
docs = text_splitter.split_documents(documents)# 2. 创建向量存储和基础检索器
vectorstore = FAISS.from_documents(docs, hf_bge_embeddings)
base_retriever = vectorstore.as_retriever(search_kwargs={"k": 20})# 3. 设置ColBERT重排序器
reranker = ColBERTReranker()# 4. 设置LLM压缩器
compressor = LLMChainExtractor.from_llm(llm)# 5. 使用DocumentCompressorPipeline组装压缩管道
# 流程: ColBERT重排 -> LLM压缩
pipeline_compressor = DocumentCompressorPipeline(transformers=[reranker, compressor]
)# 6. 创建最终的压缩检索器
final_retriever = ContextualCompressionRetriever(base_compressor=pipeline_compressor,base_retriever=base_retriever
)# 7. 执行查询并展示结果
query = "AI还有哪些缺陷需要克服?"
print(f"\n{'='*20} 开始执行查询 {'='*20}")
print(f"查询: {query}\n")# 7.1 基础检索结果
print(f"--- (1) 基础检索结果 (Top 20) ---")
base_results = base_retriever.get_relevant_documents(query)
for i, doc in enumerate(base_results):print(f"  [{i+1}] {doc.page_content[:100]}...\n")# 7.2 使用管道压缩器的最终结果
print(f"\n--- (2) 管道压缩后结果 (ColBERT重排 + LLM压缩) ---")
final_results = final_retriever.get_relevant_documents(query)
for i, doc in enumerate(final_results):print(f"  [{i+1}] {doc.page_content}\n")

这段代码展示了如何将各个组件串联起来,形成一个完整的检索流程:

创建基础组件:首先创建一个标准的 FAISS 向量存储和一个基础检索器 base_retriever,负责从向量库中初步召回20个可能相关的文档。
准备处理单元:实例化两个关键的处理单元:
reranker: 自定义的 ColBERTReranker 实例。
compressor: LangChain 内置的 LLMChainExtractor,用于从文档中提取与查询相关的句子。
构建处理管道 (DocumentCompressorPipeline):这是整个流程的核心。创建一个 DocumentCompressorPipeline 实例,并将 reranker 和 compressor 按顺序放入 transformers 列表中。根据 DocumentCompressorPipeline 的源码,它会依次调用列表中的每个处理器。因此,文档会先经过 ColBERTReranker 重排,重排后的结果再被送入 LLMChainExtractor 进行压缩。
组装最终检索器:最后,用 ContextualCompressionRetriever 将 base_retriever 和我们创建的 pipeline_compressor 包装在一起。当调用 final_retriever 时,它会自动执行“基础检索 -> 管道处理(重排 -> 压缩)”的完整流程。
完整代码

2.3 LlamaIndex 中的检索压缩

LlamaIndex 同样提供了封装好的压缩功能,其代表是 SentenceEmbeddingOptimizer5。它也是一个后处理器(Node Postprocessor),工作在检索之后。

它的工作原理是:对于每个检索到的文档,将其分解成句子,然后计算每个句子与用户查询的嵌入相似度,最后只保留那些相似度最高的句子,从而“优化”文档,去除无关信息。

三、校正 (Correcting)

传统的 RAG 流程有一个隐含的假设:检索到的文档总是与问题相关且包含正确答案。然而在现实世界中,检索系统可能会失败,返回不相关、过时或甚至完全错误的文档。如果将这些“有毒”的上下文直接喂给 LLM,就可能导致幻觉(Hallucination)或产生错误的回答。

校正检索(Corrective-RAG, C-RAG) 正是为解决这一问题而提出的一种策略6。思路是引入一个“自我反思”或“自我修正”的循环,在生成答案之前,对检索到的文档质量进行评估,并根据评估结果采取不同的行动。

C-RAG 的工作流程可以概括为 “检索-评估-行动” 三个阶段:
在这里插入图片描述
1、检索 (Retrieve) :与标准 RAG 一样,首先根据用户查询从知识库中检索一组文档。

2、评估 (Assess) :这是 C-RAG 的关键步骤。如图所示,一个“检索评估器 (Retrieval Evaluator)”会判断每个文档与查询的相关性,并给出“正确 (Correct)”、“不正确 (Incorrect)”或“模糊 (Ambiguous)”的标签。

3、行动 (Act) :根据评估结果,系统会进入不同的知识修正与获取流程:

  • 如果评估为“正确”:系统会进入“知识精炼 (Knowledge Refinement)”环节。如图,它会将原始文档分解成更小的知识片段 (strips),过滤掉无关部分,然后重新组合成更精准、更聚焦的上下文,再送给大模型生成答案。
  • 如果评估为“不正确”:系统认为内部知识库无法回答问题,此时会触发“知识搜索 (Knowledge Searching)”。它会先对原始查询进行“查询重写 (Query Rewriting)”,生成一个更适合搜索引擎的查询,然后进行 Web 搜索,用外部信息来回答问题。
    如果评估为“模糊”:同样会触发“知识搜索”,但通常会直接使用原始查询进行 Web 搜索,以获取额外信息来辅助生成答案。

通过这种方式,C-RAG 极大地增强了 RAG 系统的鲁棒性。不再盲目信任检索结果,而是增加了一个“事实核查”层,能够在检索失败时主动寻求外部帮助,从而有效减少幻觉,提升答案的准确性和可靠性。

在 LangChain 的 langgraph 库中,可以利用其图结构来灵活地构建这种带有条件判断和循环的复杂 RAG 流程7。

http://www.dtcms.com/a/362809.html

相关文章:

  • 【一张图看懂Kafka消息队列架构】
  • 【C++】编写通用模板代码的重要技巧:T()
  • 与后端对话:在React中优雅地请求API数据 (Fetch/Axios)
  • 基于STM32的智能语音浴缸设计
  • 工业视觉光源选色指南:白光通用、蓝光显瑕疵、红光能穿透,看完直接用
  • 推荐一个论文阅读工具ivySCI
  • C++内存管理,模板初阶(泛型编程)
  • 项目组文档标准不一致的原因有哪些
  • 设计模式:命令模式(Command Pattern)
  • 测试覆盖率不够高?这些技巧让你的FastAPI测试无懈可击!
  • java-设计模式-3-创建型模式-原型
  • GPT-5 技术应用指南:从底层能力到工业级落地
  • 零基础Linux操作基础小白快速掌握Shell脚本bash的配置文件
  • PHP操作LibreOffice将替换变量后的word文件转换为PDF文件
  • CICD的持续集成与持续交付和Zabbix
  • Rsync + Rsyncd 从入门到项目实战:自动化备份全攻略
  • 系统配置不是“乐高积木”:制造企业如何通过科学变更管理保障稳定运行
  • 关于ANDROUD APPIUM安装细则
  • 科研绘图(二):R 语言实现小鼠脑图谱 3D 渲染,附完整代码与数据获取指南
  • LoRaWAN®协议,如何为工业制造的数字化转型赋能?
  • 《CrystalDiskInfo》 [9.7.2] [单文件版] 下载
  • CHT共轭传热: 导热系数差异如何影响矩阵系数
  • 从0死磕全栈第2天:Vite + React 配置全解析,让你的开发效率飞起来
  • Element-Plus 入门指南
  • 跳出“中央集权”的泥潭:以Data Mesh重构AI时代的活性数据治理
  • MongoDb(②pymongo)
  • 豪华酒店品牌自营APP差异对比分析到产品重构
  • 腾讯混元世界模型Voyager开源:单图生成3D世界的“核弹级”突破,游戏、VR、自动驾驶迎来新变量
  • C++ 面试高频考点 力扣 852. 山脉数组的峰顶索引 二分查找 题解 每日一题
  • ansible循环