使用 LlamaIndex 构建 RAG 电子书“图书管理员”
作者:Jonathan Jin
介绍
此笔记本演示了如何快速为本地电子书库构建基于 RAG 的“图书管理员”。
想想您上次去图书馆并利用那里知识渊博的工作人员的专业知识来帮助您从图书馆的无数教科书、小说和其他资源中找到您需要的内容。我们的 RAG“图书管理员”将为我们做同样的事情,只是针对我们自己的本地电子书收藏。
要求
我们希望我们的图书管理员轻量级并且尽可能在本地运行,依赖项最少。这意味着我们将尽可能利用开源,以及偏向可以在典型硬件上本地执行的模型,例如 M1 Macbooks。
组件
我们的解决方案将包含以下组件
- LlamaIndex,一个用于基于 LLM 的应用程序的数据框架,与 LangChain 不同,它是专门为 RAG 设计的;
- Ollama,一个用户友好的解决方案,用于在本地运行LLM,例如 Llama 2;
- The
BAAI/bge-base-en-v1.5
嵌入模型,其性能 相当不错,并且尺寸也相当轻量级; - Llama 2,我们将通过 Ollama 运行它。
依赖项
首先让我们安装我们的依赖项。
%pip install -q \ llama-index \ EbookLib \ html2text \ llama-index-embeddings-huggingface \ llama-index-llms-ollama
!brew install ollama
测试库设置
接下来,让我们创建我们的测试“库”。
为简单起见,假设我们的“库”仅仅是**一个包含.epub
文件的嵌套目录**。我们可以很容易地看到这个解决方案可以推广到,比如,一个带有metadata.db
数据库文件的Calibre库。我们将把这个扩展留作读者的练习。😇
让我们从 古腾堡计划中获取两个.epub
文件作为我们的库。
!mkdir -p "./test/library/jane-austen"
!mkdir -p "./test/library/victor-hugo"
!wget https://www.gutenberg.org/ebooks/1342.epub.noimages -O "./test/library/jane-austen/pride-and-prejudice.epub"
!wget https://www.gutenberg.org/ebooks/135.epub.noimages -O "./test/library/victor-hugo/les-miserables.epub"
使用 LlamaIndex 的 RAG
使用 LlamaIndex 的 RAG,其核心包含以下几个主要阶段
- 加载,您告诉 LlamaIndex 您的数据存储在哪里以及如何加载它;
- 索引,您通过向量嵌入等方法来增强已加载的数据,以便于查询;
- 查询,您配置一个 LLM 作为您索引数据的查询界面。
这个解释只是略微触及了 LlamaIndex 可能性的表面。要了解更多深入的细节,我强烈建议您阅读 LlamaIndex 文档的“高级概念”页面。
加载
当然,让我们从**加载**阶段开始。
之前我提到过 LlamaIndex 是专门为 RAG 设计的。从它的 SimpleDirectoryReader
结构中可以立即显而易见,它✨神奇地✨免费支持大量多模型文件类型。方便我们的是,.epub
就在支持的集合中。
from llama_index.core import SimpleDirectoryReader
loader = SimpleDirectoryReader(
input_dir="./test/",
recursive=True,
required_exts=[".epub"],
)
documents = loader.load_data()
SimpleDirectoryReader.load_data()
将我们的电子书转换为一组 LlamaIndex 可以使用的 Document
。
这里需要注意的一点是,**文档在此阶段尚未被分块**——这将在索引过程中发生。请继续阅读…
索引
在**加载**数据之后,下一步是将其**索引**。这将允许我们的 RAG 管道查找与我们的查询相关的上下文,并将其传递给我们的 LLM 以**增强**它们生成的响应。这也是文档分块发生的地方。
VectorStoreIndex
是 LlamaIndex 中用于索引的“默认”入口点。默认情况下,VectorStoreIndex
使用一个简单的内存字典来存储索引,但 LlamaIndex 也支持 各种各样的向量存储解决方案,供您在扩展时使用。
如前所述,我们将使用 BAAI/bge-small-en-v1.5
来生成我们的嵌入。默认情况下,LlamaIndex 使用 OpenAI(具体来说是gpt-3.5-turbo
),考虑到我们希望拥有一个轻量级、本地可运行的端到端解决方案,我们希望避免使用它。
值得庆幸的是,LlamaIndex 支持通过方便的HuggingFaceEmbedding
类从 Hugging Face 检索嵌入模型,因此我们将在本文中使用它。
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
embedding_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")
我们将将其传递给VectorStoreIndex
作为我们的嵌入模型,以绕过 OpenAI 的默认行为。
from llama_index.core import VectorStoreIndex
index = VectorStoreIndex.from_documents(
documents,
embed_model=embedding_model,
)
查询
现在是 RAG 拼图的最后一块——连接查询层。
我们将使用 Llama 2 来进行此演示,但我鼓励读者尝试不同的模型,看看哪个模型在此处生成“最佳”响应。
首先,让我们启动 Ollama 服务器。不幸的是,Ollama Python 客户端 不支持实际启动和停止服务器,因此我们必须退出 Python 环境来进行此操作。
在另一个终端中,运行:ollama serve
。请记住,在我们完成操作后终止它!
现在,让我们将 Llama 2 连接到 LlamaIndex 并将其用作我们查询引擎的基础。
from llama_index.llms.ollama import Ollama
llama = Ollama(
model="llama2",
request_timeout=40.0,
)
query_engine = index.as_query_engine(llm=llama)
最终结果
这样一来,我们的基本 RAG 图书管理员就设置好了,我们可以开始询问关于我们图书馆的问题。例如
>>> print(
... query_engine.query(
... "What are the titles of all the books available? Show me the context used to derive your answer."
... )
... )
Based on the context provided, there are two books available: 1. "Pride and Prejudice" by Jane Austen 2. "Les Misérables" by Victor Hugo The context used to derive this answer includes: * The file path for each book, which provides information about the location of the book files on the computer. * The titles of the books, which are mentioned in the context as being available for reading. * A list of words associated with each book, such as "epub" and "notebooks", which provide additional information about the format and storage location of each book.
>>> print(query_engine.query("Who is the main character of 'Pride and Prejudice'?"))
The main character of 'Pride and Prejudice' is Elizabeth Bennet.
结论和未来改进
我们演示了如何构建一个基本的基于 RAG 的“图书管理员”,它完全在本地运行,甚至可以在 Apple 硅芯片 Mac 上运行。在此过程中,我们还对 LlamaIndex 及其如何简化基于 RAG 的应用程序设置流程进行了“全面介绍”。
话虽如此,我们实际上只是触及了这里可能性的表面。以下是一些关于如何完善和建立在这个基础上的想法。
强制引用
为了防止我们的图书管理员出现幻觉,我们如何要求它为它所说的一切提供引用?
使用扩展元数据
像 Calibre 这样的电子书库管理解决方案为电子书库创建了额外的元数据。这可以提供诸如出版商或版本之类的信息,这些信息可能在书籍本身的文本中不可立即获得。我们如何扩展我们的 RAG 管道以考虑不是 .epub
文件的额外信息源?
高效索引
如果我们要将我们在这里构建的所有内容收集到一个脚本/可执行文件中,生成的脚本将在每次调用时重新索引我们的库。对于我们只有两个文件的微型测试库来说,这“还可以”,但对于任何非平凡规模的库来说,这将很快变得让用户感到厌烦。我们如何持久化嵌入索引,并且仅在库的内容发生重大更改时才更新它们,例如添加新书籍?
< > 更新 在 GitHub 上