【RAG】从理论到LlamaIndex实现过程

2024年02月20日 由 alex 发表 602 0

c


最近关于检索增强生成(RAG)的三种范式:


  • 原始 RAG、高级 RAG 和模块化 RAG。


高级 RAG 范式由一系列技术组成,旨在解决天真 RAG 的已知局限性。本文首先讨论这些技术,它们可分为检索前优化、检索优化和检索后优化。


然后你将学习如何使用 Python 中的 Llamaindex 实现一个简单的 RAG 管道。


什么是高级 RAG

随着 RAG 领域的最新进展,高级 RAG 已经发展成为一种新的范式,它针对幼稚 RAG 范式的一些局限性进行了有针对性的改进。


d


检索前优化

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


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


e


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


检索优化

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


  • 对嵌入模型进行微调可根据特定领域的语境定制嵌入模型,尤其是对于术语不断演变或罕见的领域。例如,BAAI/bge-small-en 是一种可微调的高性能嵌入模型。
  • 动态嵌入与静态嵌入不同,静态嵌入是对每个词使用一个单一的向量,而动态嵌入则是根据词语使用的上下文进行调整。例如,OpenAI 的 embeddings-ada-02 就是一个复杂的动态嵌入模型,可以捕捉语境理解。


除了向量搜索,还有其他检索技术,例如混合搜索,它通常指的是将向量搜索与基于关键字的搜索相结合的概念。


检索后优化

对检索到的上下文进行额外处理有助于解决一些问题,如超出上下文窗口限制或引入噪音,从而影响对关键信息的关注。RAG 调查中总结的检索后优化技术包括:


  • 提示压缩通过删除无关内容和突出重要内容来减少提示的总长度。
  • 重新排序使用机器学习模型重新计算检索语境的相关性分数。


f


先决条件


所需软件包

在这里将指导你使用 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。这样做意味着你不必再在代码中明确指定模型。


  • 嵌入模型:用于为文档块和查询生成向量嵌入。
  • 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 有两个功能:


  • 它将文档分成单个句子,这些句子将被嵌入到文档中。
  • 它会为每个句子创建一个上下文窗口。如果指定 window_size = 3,生成的窗口将有三个句子长,从嵌入句子的前一个句子开始,横跨后一个句子。窗口将作为元数据存储。


在检索过程中,将返回与查询最匹配的句子。检索后,你需要定义一个 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 管道中添加重排序器只需三个简单步骤:


  • 首先,定义一个 reeranker 模型。在这里,我们使用的是来自 Hugging Face 的 BAAI/bge-ranker-base。
  • 在查询引擎中,将 reranker 模型添加到节点后处理器列表中。
  • 在查询引擎中增加 similarity_top_k,以检索更多的上下文段落。


# !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 管道。

文章来源:https://medium.com/towards-data-science/advanced-retrieval-augmented-generation-from-theory-to-llamaindex-implementation-4de1464a9930
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消