AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


高级检索增强生成(RAG):从理论到 LlamaIndex 实现
发布日期:2024-07-09 19:05:55 浏览次数: 2266


如何通过在 Python 中实现高级 RAG 技术解决简单 RAG 管道的局限性

Naive 和 Advanced RAG 的区别(作者原创图片,灵感来自[1])

一项关于检索增强生成(RAG)的最新调查[1]总结了最近发展的三种范式:

简单 RAG,高级 RAG,模块化 RAG。

高级 RAG 范式包含一组针对简单 RAG 已知局限性的技术。本文首先讨论这些技术,这些技术可以分为检索前、检索中和检索后的优化。

在本文的第二部分,您将学习如何使用 Llamaindex [1]在 Python 中实现一个简单的 RAG 管道,然后使用以下高级 RAG 技术中的一些来将其增强为高级 RAG 管道:

检索前优化:句子窗口检索检索优化:混合搜索检索后优化:重新排名

本文重点介绍高级 RAG 范式及其实现。如果您不熟悉 RAG 的基础知识,可以在此处了解:

Retrieval-Augmented Generation (RAG): From Theory to LangChain Implementation[2]
从原始学术论文的理论到与 OpenAI、Weaviate 和 LangChain 的 Python 实现


什么是高级 RAG

随着 RAG 领域的最新进展,高级 RAG 已发展为一种新的范式,通过有针对性的增强来解决简单 RAG 范式的一些局限性。正如最近的一项调查[1]所总结,高级 RAG 技术可以分为检索前、检索中和检索后的优化。

Naive 和 Advanced RAG 的区别(作者原创图片,灵感来自[1])

检索前优化

检索前优化侧重于数据索引优化以及查询优化。数据索引优化技术旨在以有助于提高检索效率的方式存储数据,例如[1]:

滑动窗口使用块之间的重叠,是最简单的技术之一。增强数据粒度应用数据清理技术,如删除无关信息、确认事实准确性、更新过时信息等。添加元数据,如日期、用途或章节,用于过滤目的。优化索引结构涉及不同的策略来索引数据,如调整块大小或使用多索引策略。本文将实现的一种技术是句子窗口检索,在检索中嵌入单个句子,并在推理时用更大的文本窗口替换它们。

句子窗口检索

此外,检索前技术不仅限于数据索引,还可以涵盖推理时的技术,例如查询路由、查询重写和查询扩展。

检索优化

检索阶段旨在识别最相关的上下文。通常,检索基于向量搜索,它计算查询与索引数据之间的语义相似性。因此,大多数检索优化技术围绕嵌入模型展开[1]:

微调嵌入模型将嵌入模型定制到特定领域的上下文,特别是对于具有不断变化或罕见术语的领域。例如,BAAI/bge-small-en 是一个高性能嵌入模型,可以进行微调(参见微调指南)。动态嵌入与静态嵌入不同,动态嵌入会根据词语的使用上下文进行适应,而静态嵌入对每个词语使用单一向量。例如,OpenAI 的 embeddings-ada-02 是一个复杂的动态嵌入模型,能够捕捉上下文理解[1]。

除了向量搜索,还有其他检索技术,例如混合搜索,通常指将向量搜索与基于关键字的搜索相结合的概念。如果您的检索需要精确的关键字匹配,这种检索技术非常有益。

提升 RAG 管道中的检索性能与混合搜索

如何通过结合传统的基于关键字的搜索与现代向量搜索找到更相关的搜索结果

towardsdatascience.com[3]

检索后优化

对检索到的上下文进行额外处理可以帮助解决超过上下文窗口限制或引入噪音等问题,从而阻碍对关键信息的关注。RAG 调查[1]总结的检索后优化技术包括:

提示压缩通过删除无关信息和突出重要上下文来减少整体提示长度。重新排名使用机器学习模型重新计算检索上下文的相关性得分。

重新排名在检索之后基于查询重新计算相关性得分,并且仅将前 n 个发送到 LLM 的上下文窗口中。

重新排名

如果您想了解更多关于如何提高 RAG 管道性能的方法,使其达到生产就绪状态,请继续阅读:

生产就绪 RAG 应用的 12 种调优策略指南[4]

如何通过这些“超参数”提高您的检索增强生成(RAG)管道性能

前提条件

本节讨论本文中所需的包和 API 密钥。

所需包

本文将指导您使用 LlamaIndex 在 Python 中实现一个简单和高级的 RAG 管道。

pip install llama-index

在本文中,我们将使用 LlamaIndex v0.10。如果您从旧版本的 LlamaIndex 升级,您需要运行以下命令来正确安装和运行 LlamaIndex:

pip uninstall llama-indexpip install llama-index --upgrade --no-cache-dir --force-reinstall

LlamaIndex 提供了将向量嵌入本地存储在 JSON 文件中的选项,用于持久存储,这对于快速原型设计非常有用。然而,由于高级 RAG 技术旨在实现生产就绪的应用,我们将使用向量数据库进行持久存储。由于我们需要元数据存储和混合搜索功能以及存储向量嵌入,因此我们将使用支持这些功能的开源向量数据库 Weaviate[5]v3.26.2)。

pip install weaviate-client llama-index-vector-stores-weaviate

API 密钥

我们将使用 Weaviate 嵌入版,您可以免费使用而无需注册 API 密钥。然而,本教程使用的嵌入模型和 LLM 来自 OpenAI,您需要一个 OpenAI API 密钥。要获取密钥,您需要一个 OpenAI 账户,然后在 API 密钥[6]下“创建新密钥”。

接下来,在根目录中创建一个本地 .env 文件,并在其中定义您的 API 密钥:

OPENAI_API_KEY="<YOUR_OPENAI_API_KEY>"

之后,您可以使用以下代码加载您的 API 密钥:

# !pip install python-dotenvimport osfrom dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())

使用 LlamaIndex 实现简单 RAG

本节讨论如何使用 LlamaIndex 实现一个简单的 RAG 管道。您可以在此 Jupyter Notebook [7]中找到完整的简单 RAG 管道。关于使用 LangChain 的实现,请继续阅读本文(使用 LangChain 的简单 RAG 管道)。

步骤 1:定义嵌入模型和 LLM

首先,您可以在全局设置对象中定义嵌入模型和 LLM。这样,您就不需要在代码中再次显式指定模型。

嵌入模型:用于为文档块和查询生成向量嵌入。LLM:用于根据用户查询和相关上下文生成答案。

from llama_index.embeddings.openai import OpenAIEmbeddingfrom llama_index.llms.openai import OpenAIfrom llama_index.core.settings import Settings
Settings.llm = OpenAI(model="gpt-3.5-turbo", temperature=0.1)Settings.embed_model = OpenAIEmbedding()

步骤 2:加载数据

接下来,您将在根目录中创建一个名为 data 的本地目录,并从 LlamaIndex GitHub 仓库[8](MIT 许可证)下载一些示例数据。

!mkdir -p 'data'!wget '<https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/paul_graham/paul_graham_essay.txt>' -O 'data/paul_graham_essay.txt'

之后,您可以加载数据以供进一步处理:

from llama_index.core import SimpleDirectoryReader
# 加载数据documents = SimpleDirectoryReader(input_files=["./data/paul_graham_essay.txt"]).load_data()

步骤 3:将文档分块为节点

由于整个文档太大,无法放入 LLM 的上下文窗口中,因此您需要将其划分为较小的文本块,这些块在 LlamaIndex 中称为节点。您可以使用 SimpleNodeParser 解析加载的文档,并定义块大小为 1024。

from llama_index.core.node_parser import SimpleNodeParser
node_parser = SimpleNodeParser.from_defaults(chunk_size=1024)
# 从文档中提取节点nodes = node_parser.get_nodes_from_documents(documents)

步骤 4:构建索引

接下来,您将构建一个索引,用于将所有外部知识存储在 Weaviate[9] 中,这是一个开源向量数据库。

首先,您需要连接到一个 Weaviate 实例。在本例中,我们使用 Weaviate Embedded[10],这允许您在 Notebook 中免费试验而无需 API 密钥。对于生产就绪的解决方案,推荐通过 Docker [11]自行部署 Weaviate 或使用托管服务。

import weaviate
# 连接到您的 Weaviate 实例client = weaviate.Client(embedded_options=weaviate.embedded.EmbeddedOptions(), )

接下来,您将从 Weaviate 客户端构建一个 VectorStoreIndex,用于存储和交互数据。

from llama_index.core import VectorStoreIndex, StorageContextfrom llama_index.vector_stores.weaviate import WeaviateVectorStore
index_name = "MyExternalContext"
# 构建向量存储vector_store = WeaviateVectorStore(weaviate_client = client, index_name = index_name)
# 设置嵌入存储storage_context = StorageContext.from_defaults(vector_store=vector_store)
# 设置索引# 构建 VectorStoreIndex 负责分块文档并将块编码为嵌入以供将来检索index = VectorStoreIndex(nodes,storage_context = storage_context,)

步骤 5:设置查询引擎

最后,您将索引设置为查询引擎。

# QueryEngine 类配备了生成器并促进检索和生成步骤query_engine = index.as_query_engine()

步骤 6:在您的数据上运行一个简单的 RAG 查询

现在,您可以在您的数据上运行一个简单的 RAG 查询,如下所示:

# 运行您的简单 RAG 查询response = query_engine.query("What happened at Interleaf?")

使用 LlamaIndex 实现高级 RAG

在本节中,我们将介绍一些简单的调整,以将上述简单的 RAG 管道转变为高级管道。本教程将涵盖以下高级 RAG 技术的选择:

检索前优化:句子窗口检索检索优化:混合搜索检索后优化:重新排名

由于这里只介绍修改内容,您可以在此 Jupyter Notebook[12] 中找到完整的端到端高级 RAG 管道[13]

索引优化示例:句子窗口检索

对于句子窗口检索技术[14],您需要进行两处调整:首先,您必须调整数据的存储和后处理方式。我们将使用 SentenceWindowNodeParser 代替 SimpleNodeParser

from llama_index.core.node_parser import SentenceWindowNodeParser
# 创建句子窗口节点解析器,使用默认设置node_parser = SentenceWindowNodeParser.from_defaults(window_size=3,window_metadata_key="window",original_text_metadata_key="original_text",)

SentenceWindowNodeParser 做了两件事:

1.它将文档分离成单个句子,这些句子将被嵌入。2.对于每个句子,它创建一个上下文窗口。如果您指定 window_size=3,则生成的窗口将包含三个句子,从嵌入句子的前一句开始,跨越嵌入句子之后的句子。该窗口将作为元数据存储。

在检索过程中,最符合查询的句子将被返回。在检索之后,您需要通过定义一个 MetadataReplacementPostProcessor 并在 node_postprocessors 列表中使用它,将句子替换为元数据中的整个窗口。

from llama_index.core.postprocessor import MetadataReplacementPostProcessor
# 目标键默认为 `window` 以匹配 node_parser 的默认值postproc = MetadataReplacementPostProcessor(target_metadata_key="window")

query_engine = index.as_query_engine( node_postprocessors = [postproc],)

检索优化示例:混合搜索

如果底层向量数据库支持混合搜索查询,在 LlamaIndex 中实现混合搜索非常简单,只需对 query_engine 进行两个参数更改。alpha 参数指定向量搜索与基于关键字的搜索之间的权重,其中 alpha=0 表示基于关键字的搜索,alpha=1 表示纯向量搜索。

query_engine = index.as_query_engine(...,vector_store_query_mode="hybrid", alpha=0.5,...)

检索后优化示例:重新排名

将重新排名器添加到高级 RAG 管道中只需三个简单步骤:

1.首先,定义一个重新排名模型。这里,我们使用 Hugging Face[15] 的 BAAI/bge-reranker-base2.在查询引擎中,将重新排名模型添加到 node_postprocessors 列表中。3.增加查询引擎中的 similarity_top_k 以检索更多的上下文片段,然后在重新排名后将其减少到 top_n

# !pip install torch sentence-transformersfrom llama_index.core.postprocessor import SentenceTransformerRerank
# 定义重新排名模型rerank = SentenceTransformerRerank(top_n = 2, model = "BAAI/bge-reranker-base")
...
# 将重新排名器添加到查询引擎query_engine = index.as_query_engine(similarity_top_k = 6,...,node_postprocessors = [rerank],...,)

在高级 RAG 范式中还有许多不同的技术。如果您对进一步的实现感兴趣,推荐以下两个资源:

构建和评估高级 RAG 应用 学习诸如句子窗口检索和自动合并检索等方法,提高您的 RAG 管道性能...

高级 RAG 01:小到大的检索 使用 LlamaIndex 的子父递归检索和句子窗口检索...

总结

本文介绍了高级 RAG 的概念,这是一组解决简单 RAG 范式局限性的技术。本文概述了高级 RAG 技术,这些技术可以分为检索前、检索中和检索后的技术,并使用 LlamaIndex 实现了简单和高级的 RAG 管道。

RAG 管道的组件包括 OpenAI 的语言模型,Hugging Face[16] 上的 BAAI[17] 重新排名模型,以及 Weaviate[18] 向量数据库。

我们使用 Python 中的 LlamaIndex 实现了以下选择的技术:

检索前优化:句子窗口检索检索优化:混合搜索检索后优化:重新排名

您可以在此处找到包含完整端到端管道的 Jupyter Notebooks:

LlamaIndex 中的简单 RAG[19]LlamaIndex 中的高级 RAG[20]



53AI,企业落地应用大模型首选服务商

产品:大模型应用平台+智能体定制开发+落地咨询服务

承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

与创始人交个朋友

回到顶部

 
扫码咨询