最近关于检索增强生成(RAG)的三种范式:
高级 RAG 范式由一系列技术组成,旨在解决天真 RAG 的已知局限性。本文首先讨论这些技术,它们可分为检索前优化、检索优化和检索后优化。
然后你将学习如何使用 Python 中的 Llamaindex 实现一个简单的 RAG 管道。
什么是高级 RAG
随着 RAG 领域的最新进展,高级 RAG 已经发展成为一种新的范式,它针对幼稚 RAG 范式的一些局限性进行了有针对性的改进。
检索前优化
检索前优化侧重于数据索引优化和查询优化。数据索引优化技术旨在以有助于提高检索效率的方式存储数据,例如 :
此外,检索前优化技术并不局限于数据索引,还可以涵盖推理时的技术,如查询路由、查询重写和查询扩展。
检索优化
检索阶段旨在确定最相关的上下文。检索通常基于向量搜索,计算查询和索引数据之间的语义相似性。因此,大多数检索优化技术都围绕着嵌入模型展开:
除了向量搜索,还有其他检索技术,例如混合搜索,它通常指的是将向量搜索与基于关键字的搜索相结合的概念。
检索后优化
对检索到的上下文进行额外处理有助于解决一些问题,如超出上下文窗口限制或引入噪音,从而影响对关键信息的关注。RAG 调查中总结的检索后优化技术包括:
先决条件
所需软件包
在这里将指导你使用 Python 中的 LlamaIndex 实现初级和高级 RAG 管道。
pip install llama-index
在本文中,我们将使用 LlamaIndex v0.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_dotenv
load_dotenv(find_dotenv())
使用 LlamaIndex 实施Naive RAG
步骤 1:定义嵌入模型和 LLM
首先,可以在全局设置对象中定义嵌入模型和 LLM。这样做意味着你不必再在代码中明确指定模型。
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
from 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 代码库(MIT 许可)中下载一些示例数据。
!mkdir -p 'data'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 的上下文窗口,因此需要将其分割成较小的文本块,在 LlamaIndex 中称为节点。你可以使用简单节点解析器(SimpleNodeParser)将加载的文档解析为节点,定义的块大小为 1024。
from llama_index.core.node_parser import SimpleNodeParser
node_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 密钥的情况下免费在笔记本中进行实验。对于生产就绪的解决方案,建议通过 Docker 或托管服务等方式自行部署 Weaviate。
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 WeaviateVectorStore
index_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:在数据上运行Naive RAG查询
现在,你可以在数据上运行一RAG查询,如下所示:
# Run your naive RAG query
response = query_engine.query(
"What happened at Interleaf?"
)
使用 LlamaIndex 实现高级 RAG
在本节中,我们将介绍一些简单的调整,以便将上述简单的 RAG 管道转变为高级管道。
索引优化示例: 句子窗口检索
对于句子窗口检索技术,你需要进行两项调整: 首先,必须调整存储和后处理数据的方式。我们将使用 SentenceWindowNodeParser 代替 SimpleNodeParser。
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 有两个功能:
在检索过程中,将返回与查询最匹配的句子。检索后,你需要定义一个 MetadataReplacementPostProcessor(元数据替换后处理器),并将其用于节点后处理器列表中,从而用元数据中的整个窗口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],
)
检索优化示例: 混合搜索
如果底层矢量数据库支持混合搜索查询,那么在 LlamaIndex 中实施混合搜索只需更改 query_engine 的两个参数即可。alpha 参数指定了矢量搜索和基于关键字的搜索之间的权重,其中 alpha=0 表示基于关键字的搜索,alpha=1 表示纯矢量搜索。
query_engine = index.as_query_engine(
...,
vector_store_query_mode="hybrid", "hybrid",
alpha=0.5,
...
)
检索后优化示例 重新排序
在高级 RAG 管道中添加重排序器只需三个简单步骤:
# !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 技术(可分为检索前技术、检索技术和检索后技术)之后,本文使用 LlamaIndex 实现了一个用于协调的原始和高级 RAG 管道。