原文地址:Advanced Retrieval-Augmented Generation: From Theory to LlamaIndex Implementation
如何通过在 Python 中实现有针对性的高级 RAG 技术来解决原始 RAG 管道的局限性
2024 年 2 月 19 日
如何通过在 Python 中实现有针对性的高级 RAG 技术来解决 naive RAG 管道的局限性
A最近关于检索增强生成(RAG)的调查[1]总结了三种最近发展的范式:
- Naive RAG
- 高级 RAG
- 模块化 RAG
高级 RAG 范式由一组技术组成,旨在解决原始 RAG 的已知局限性。本文首先讨论这些技术,这些技术可以分为检索前、检索和检索后优化。
在后半部分中,您将学习如何在 Python 中使用Llamaindex实现简单的 RAG 管道,然后通过选择以下高级 RAG 技术将其增强为高级 RAG 管道:
- 检索前优化:句子窗口检索
- 检索优化:混合搜索
- 检索后优化:重新排名
本文重点介绍高级 RAG 范式及其实现。如果您不熟悉 RAG 的基础知识,可以在这里了解:
检索增强生成(RAG):从理论到 LangChain 实现
什么是高级 RAG
随着 RAG 领域的最新进展,高级 RAG 已发展成为一种新范式,并进行了有针对性的增强,以解决原始 RAG 范式的一些限制。正如最近的一项调查[1]所总结的,先进的 RAG 技术可以分为检索前、检索和检索后优化。
Naive 和 Advanced RAG 的区别(图片由作者提供,灵感来自 [1])
检索前优化
预检索优化侧重于数据索引优化和查询优化。数据索引优化技术旨在以有助于提高检索效率的方式存储数据,例如[1]:
- 滑动窗口使用块之间的重叠,是最简单的技术之一。
- 增强数据粒度应用数据清理技术,例如删除不相关信息、确认事实准确性、更新过时信息等。
- 添加元数据,例如日期、目的或章节,用于过滤目的。
- 优化索引结构涉及不同的数据索引策略,例如调整块大小或使用多重索引策略。我们将在本文中实现的一项技术是句子窗口检索,它嵌入单个句子进行检索,并在推理时用更大的文本窗口替换它们。
句子窗口检索
此外,预检索技术不仅限于数据索引,还可以涵盖推理时的技术,例如查询路由、查询重写和查询扩展。
检索优化
检索阶段旨在识别最相关的上下文。通常,检索基于向量搜索,它计算查询和索引数据之间的语义相似度。因此,大多数检索优化技术都围绕嵌入模型[1]:
- 微调嵌入模型可以根据特定领域的上下文定制嵌入模型,特别是对于具有不断发展或罕见术语的领域。例如,
BAAI/bge-small-en
是一个可以微调的高性能嵌入模型(参见微调指南) - 动态嵌入适应单词使用的上下文,这与静态嵌入不同,静态嵌入为每个单词使用单个向量。例如,OpenAI
embeddings-ada-02
是一个复杂的动态嵌入模型,可以捕获上下文理解。[1]
除了矢量搜索之外,还有其他检索技术,例如混合搜索,它通常指的是矢量搜索与基于关键字的搜索相结合的概念。如果您的检索需要精确的关键字匹配,则此检索技术非常有用。
通过混合搜索提高 RAG 管道的检索性能
检索后优化
对检索到的上下文进行额外处理可以帮助解决诸如超出上下文窗口限制或引入噪声等问题,从而阻碍对关键信息的关注。RAG 调查 [1] 中总结的检索后优化技术包括:
- 提示压缩通过删除不相关的内容并突出显示重要的上下文来减少总体提示长度。
- 重新排名使用机器学习模型来重新计算检索到的上下文的相关性分数。
重新排名
有关如何提高 RAG 管道性能以使其做好生产准备的更多想法,请继续阅读此处:
生产就绪 RAG 应用的 12 种调优策略指南
先决条件
本节讨论本文中所需的包和 API 密钥。
所需包
本文将指导您在 Python 中使用LlamaIndex实现简单和高级的 RAG 管道。
pip install llama-index
在本文中,我们将使用LlamaIndexv0.10
。如果您是从较旧的 LlamaIndex 版本升级,则需要运行以下命令才能正确安装和运行 LlamaIndex:
pip uninstall llama-index
pip install llama-index --upgrade --no-cache-dir --force-reinstall
LlamaIndex 提供了一个选项,可以将矢量嵌入本地存储在 JSON 文件中以进行持久存储,这对于快速构建想法原型非常有用。然而,我们将使用矢量数据库进行持久存储,因为先进的 RAG 技术旨在生产就绪的应用程序。
由于除了存储向量嵌入之外,我们还需要元数据存储和混合搜索功能,因此我们将使用支持这些功能的开源向量数据库Weaviate ( v3.26.2
)。
pip install weaviate-client llama-index-vector-stores-weaviate
API 密钥
我们将使用嵌入的 Weaviate,您可以免费使用它,无需注册 API 密钥。但是,本教程使用OpenAI的嵌入模型和 LLM ,为此您将需要 OpenAI API 密钥。要获取它,您需要一个 OpenAI 帐户,然后在API 密钥下“创建新密钥” 。
.env
接下来,在根目录中创建一个本地文件并在其中定义 API 密钥:
OPENAI_API_KEY="<YOUR_OPENAI_API_KEY>"
之后,您可以使用以下代码加载 API 密钥:
# !pip install python-dotenv
import os
from dotenv import load_dotenv,find_dotenvload_dotenv(find_dotenv())
使用 LlamaIndex 实施 Naive RAG
本节讨论如何使用 LlamaIndex 实现简单的 RAG 管道。您可以在此Jupyter Notebook中找到整个原始 RAG 管道。对于使用 LangChain 的实现,您可以继续本文(使用 LangChain 的 naive RAG pipeline)。
第 1 步:定义嵌入模型和 LLM
首先,您可以在全局设置对象中定义嵌入模型和 LLM。这样做意味着您不必再次在代码中显式指定模型。
- 嵌入模型:用于为文档块和查询生成向量嵌入。
- LLM:用于根据用户查询和相关上下文生成答案。
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
from llama_index.core.settings import SettingsSettings.llm = OpenAI(model="gpt-3.5-turbo", temperature=0.1)
Settings.embed_model = OpenAIEmbedding()
第 2 步:加载数据
接下来,您将创建一个data
在根目录中命名的本地目录,并从LlamaIndex GitHub 存储库(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# Load data
documents = SimpleDirectoryReader(input_files=["./data/paul_graham_essay.txt"]
).load_data()
第 3 步:将文档分块到节点中
由于整个文档太大而无法放入 LLM 的上下文窗口,因此您需要将其划分为较小的文本块,这些文本块Nodes
在 LlamaIndex 中调用。SimpleNodeParser
您可以使用定义的块大小 1024 将加载的文档解析为节点。
from llama_index.core.node_parser import SimpleNodeParsernode_parser = SimpleNodeParser.from_defaults(chunk_size=1024)# Extract nodes from documents
nodes = node_parser.get_nodes_from_documents(documents)
第 4 步:建立索引
接下来,您将在开源矢量数据库Weaviate中构建存储所有外部知识的索引。
首先,您需要连接到 Weaviate 实例。在本例中,我们使用Weaviate Embedded,它允许您在笔记本中免费进行实验,无需 API 密钥。对于生产就绪的解决方案,建议自行部署 Weaviate,例如通过 Docker或利用托管服务。
import weaviate# Connect to your Weaviate instance
client = weaviate.Client(embedded_options=weaviate.embedded.EmbeddedOptions(),
)
接下来,您将从 Weaviate 客户端构建一个VectorStoreIndex
来存储数据并与之交互。
from llama_index.core import VectorStoreIndex, StorageContext
from llama_index.vector_stores.weaviate import WeaviateVectorStoreindex_name = "MyExternalContext"# Construct vector store
vector_store = WeaviateVectorStore(weaviate_client = client, index_name = index_name
)# Set up the storage for the embeddings
storage_context = StorageContext.from_defaults(vector_store=vector_store)# Setup the index
# build VectorStoreIndex that takes care of chunking documents
# and encoding chunks to embeddings for future retrieval
index = VectorStoreIndex(nodes,storage_context = storage_context,
)
第 5 步:设置查询引擎
最后,您将设置索引作为查询引擎。
# The QueryEngine class is equipped with the generator
# and facilitates the retrieval and generation steps
query_engine = index.as_query_engine()
第 6 步:对数据运行简单的 RAG 查询
现在,您可以对数据运行简单的 RAG 查询,如下所示:
# Run your naive RAG query
response = query_engine.query("What happened at Interleaf?"
)
使用 LlamaIndex 实施高级 RAG
在本节中,我们将介绍一些简单的调整,您可以进行一些简单的调整,将上述简单的 RAG 管道转变为高级管道。本演练将涵盖以下高级 RAG 技术选择:
- 检索前优化:句子窗口检索
- 检索优化:混合搜索
- 检索后优化:重新排名
由于我们仅在此处介绍修改,因此您可以在此 Jupyter Notebook 中找到完整的端到端高级 RAG 管道。
索引优化示例:句子窗口检索
对于句子窗口检索技术,您需要进行两个调整:首先,您必须调整数据的存储和后处理方式。SimpleNodeParser
我们将使用来代替SentenceWindowNodeParser
。
from llama_index.core.node_parser import SentenceWindowNodeParser# create the sentence window node parser w/ default settings
node_parser = SentenceWindowNodeParser.from_defaults(window_size=3,window_metadata_key="window",original_text_metadata_key="original_text",
)
它SentenceWindowNodeParser
做了两件事:
- 它将文档分成单个句子,这些句子将被嵌入。
- 对于每个句子,它都会创建一个上下文窗口。如果您指定 a
window_size = 3
,则生成的窗口将是三个句子长,从嵌入句子的前一个句子开始并跨越后面的句子。该窗口将存储为元数据。
在检索过程中,返回与查询最匹配的句子。MetadataReplacementPostProcessor
检索后,您需要通过定义 a并在 列表中使用它,将句子替换为元数据中的整个窗口node_postprocessors
。
from llama_index.core.postprocessor import MetadataReplacementPostProcessor# The target key defaults to `window` to match the node_parser's default
postproc = MetadataReplacementPostProcessor(target_metadata_key="window"
)...query_engine = index.as_query_engine( node_postprocessors = [postproc],
)
检索优化示例:混合搜索
query_engine
如果底层矢量数据库支持混合搜索查询,则在 LlamaIndex 中实现混合搜索就像更改两个参数一样简单。该alpha
参数指定矢量搜索和基于关键字搜索之间的权重,其中alpha=0
表示基于关键字搜索,alpha=1
表示纯矢量搜索。
query_engine = index.as_query_engine(...,vector_store_query_mode="hybrid", alpha=0.5,...
)
检索后优化示例:重新排名
将重新排序器添加到高级 RAG 管道只需三个简单步骤:
- 首先,定义一个重排序模型。在这里,我们使用
BAAI/bge-reranker-base
Hugging Face 中的。 - 在查询引擎中,将 reranker 模型添加到 的列表中
node_postprocessors
。 - 增加
similarity_top_k
查询引擎中的 来检索更多上下文段落,可以在重新排序后减少top_n
。
# !pip install torch sentence-transformers
from llama_index.core.postprocessor import SentenceTransformerRerank# Define reranker model
rerank = SentenceTransformerRerank(top_n = 2, model = "BAAI/bge-reranker-base"
)...# Add reranker to query engine
query_engine = index.as_query_engine(similarity_top_k = 6,...,node_postprocessors = [rerank],...,
)
高级 RAG 范例中还有许多不同的技术。如果您对进一步的实施感兴趣,我推荐以下两个资源:
构建和评估高级 RAG 应用程序
高级 RAG 01:从小到大检索
概括
本文介绍了高级 RAG 的概念,其中涵盖了一组解决朴素 RAG 范式局限性的技术。在概述了高级 RAG 技术(可分为预检索、检索和检索后技术)之后,本文使用 LlamaIndex 进行编排实现了一个简单且高级的 RAG 管道。
RAG 管道组件是来自OpenAI的语言模型、来自托管在Hugging Face上的BAAI 的重排序模型以及Weaviate矢量数据库。
我们在 Python 中使用 LlamaIndex 实现了以下技术选择:
- 检索前优化:句子窗口检索
- 检索优化:混合搜索
- 检索后优化:重新排名
您可以在此处找到包含完整端到端管道的 Jupyter Notebook:
- LlamaIndex 中的 Naive RAG
- LlamaIndex 中的高级 RAG
参考
[1] Gao, Y., Xiong, Y., Gao, X., Jia, K., Pan, J., Bi, Y., … & Wang, H. (2023). Retrieval-augmented generation for large language models: A survey.arXiv preprint arXiv:2312.10997。