1.索引 #
加载完数据后,您现在拥有一个Document对象列表(或Node列表)。是时候构建一个 Index 了,通过这些对象,您就能开始查询它们了。
2.什么是Index? #
在LlamaIndex术语中,一个 Index 是一种由 Document 数据结构组成的对象,旨在支持LLM查询。您的Index设计旨在与查询策略相辅相成。
LlamaIndex提供多种不同的索引类型,这里我们将介绍两种最常见的类型。
3.向量存储索引 #
一个 VectorStoreIndex 是目前最常见的索引类型。Vector Store Index会将您的Documents分割成Nodes,随后创建 vector embeddings,每个节点的文本内容已准备就绪,可供LLM查询。
3.1 什么是embedding? #
Vector embeddings 是LLM应用运行的核心所在。
一个 vector embedding,通常简称为embedding,是一种文本语义或意义的数值化表示。两段含义相似的文本,即使实际内容差异很大,其数学上的嵌入向量也会非常接近。
这一数学关系使得语义搜索成为可能:用户提供查询词,LlamaIndex就能找到相关的文本,基于查询词条的含义而非简单的关键词匹配。这是检索增强生成(Retrieval-Augmented Generation)工作原理的重要组成部分,也是大语言模型(LLMs)普遍运作的核心机制。
有多种类型的嵌入,它们在效率、效果和计算成本上各不相同。默认情况下,LlamaIndex使用 text-embedding-ada-002,这是OpenAI默认使用的嵌入模型。如果您使用不同的LLM,通常需要搭配不同的嵌入模型。
3.2 Vector Store Index 为您的文档生成嵌入向量 #
Vector Store Index 通过调用您LLM的API将所有文本转化为嵌入向量,这就是我们所说的"嵌入您的文本"。如果您有大量文本,生成嵌入向量可能会耗费很长时间,因为这涉及多次往返API调用。
当您需要搜索嵌入向量时,查询本身会被转换为向量嵌入,然后由VectorStoreIndex执行数学运算,根据所有嵌入向量与查询的语义相似度进行排序。
3.3 Top K 检索 #
当排名完成后,VectorStoreIndex会返回最相似的嵌入向量及其对应的文本片段。返回的嵌入向量数量被称为 k,控制返回多少嵌入向量的参数被称为 top_k,因此,这类搜索常被称为"top-k语义检索"。
Top-k检索是查询向量索引的最简单形式;阅读后续内容时,您将了解到更复杂精妙的策略。详见查询部分。
3.4 使用Vector Store Index #
要使用Vector Store Index,只需将加载阶段创建的Documents列表传递给它即可。
# 导入VectorStoreIndex和Document类,用于向量索引和文档对象的创建
from llama_index.core import VectorStoreIndex, Document
# 定义一个包含示例文本的列表
sample_texts = [
"LlamaIndex是一个强大的数据框架,用于构建LLM应用。",
"向量索引是LlamaIndex中最常用的索引类型。",
"嵌入向量是文本语义的数值化表示。",
]
# 将每个文本字符串转换为Document对象,构建文档列表
documents = [Document(text=text) for text in sample_texts]
# 通过from_documents方法从文档列表创建向量存储索引
index = VectorStoreIndex.from_documents(documents)
# 通过索引对象创建查询引擎,用于后续的语义检索
query_engine = index.as_query_engine()
# 使用查询引擎进行语义搜索,查询“什么是向量索引?”
response = query_engine.query("什么是向量索引?")
# 打印查询结果
print(response)提示:
from_documents方法还接受一个可选参数show_progress,将其设置为True可在索引构建过程中显示进度条。
您也可以选择直接基于一组Node对象构建索引:
# 导入必要的库
from llama_index.core import VectorStoreIndex, Document
from llama_index.core.node_parser import SentenceSplitter
# 创建示例文档
sample_texts = [
"LlamaIndex支持多种索引类型,包括向量索引和摘要索引。",
"向量索引适合语义搜索,摘要索引适合文档摘要。"
]
# 将文本转换为Document对象
documents = [Document(text=text) for text in sample_texts]
# 创建节点解析器
# SentenceSplitter会将文档分割成句子级别的节点
node_parser = SentenceSplitter(chunk_size=1024, chunk_overlap=20)
# 将文档分割成节点
# 节点是索引的基本构建块,每个节点包含一段文本内容
nodes = node_parser.get_nodes_from_documents(documents)
# 直接从节点创建向量索引
# 这种方式允许您对节点分割过程进行更精细的控制
index = VectorStoreIndex(nodes)
# 创建查询引擎并执行查询
query_engine = index.as_query_engine()
response = query_engine.query("LlamaIndex有哪些索引类型?")
print(response)您的文本已建立索引,技术上现已准备就绪进行查询。然而,将所有文本进行嵌入处理可能非常耗时,而且如果您使用的是托管式LLM服务,成本也可能相当高昂。为了节省时间和费用,您会希望先存储您的嵌入向量。
4.摘要索引 #
Summary Index是一种较为简单的索引形式,最适合用于生成文档内容摘要的查询场景。正如其名称所示,它会存储所有文档内容,并在查询时将全部文档返回给查询引擎进行处理。
4.1 使用摘要索引 #
# 导入SummaryIndex和Document类,用于后续的索引和文档操作
from llama_index.core import SummaryIndex, Document
# 构建一个包含三段文本的示例文档列表
sample_texts = [
# 第一段文本,介绍LlamaIndex及其三种索引类型
"LlamaIndex是一个数据框架,专门用于构建LLM应用。它提供了多种索引类型,包括向量索引、摘要索引和知识图谱索引。向量索引适合语义搜索,摘要索引适合文档摘要,知识图谱索引适合结构化数据。",
# 第二段文本,解释向量索引的原理和优势
"向量索引通过将文本转换为嵌入向量来实现语义搜索。嵌入向量是文本语义的数值化表示,相似的文本会有相似的向量表示。这使得系统能够基于语义相似性而非关键词匹配来检索相关内容。",
# 第三段文本,说明摘要索引的特点和适用场景
"摘要索引是一种简单的索引形式,它存储完整的文档内容并在查询时返回所有相关文档。这种索引类型特别适合需要生成文档摘要或需要完整上下文的场景。",
]
# 将每一段文本包装成Document对象,便于后续索引处理
documents = [Document(text=text) for text in sample_texts]
# 通过SummaryIndex的from_documents方法,将文档列表构建为摘要索引
index = SummaryIndex.from_documents(documents)
# 通过摘要索引创建一个查询引擎,用于后续的查询操作
query_engine = index.as_query_engine()
# 使用查询引擎执行一次查询,问题为“请总结LlamaIndex的主要特点”
response = query_engine.query("请总结LlamaIndex的主要特点")
# 输出查询结果(即生成的摘要或答案)
print(response)
4.2 摘要索引的特点 #
- 简单直接:不需要复杂的向量计算
- 适合摘要:特别适合生成文档摘要的场景
- 完整上下文:在查询时提供完整的文档内容
- 计算效率:相比向量索引,计算开销更小
5.索引类型对比 #
| 索引类型 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| VectorStoreIndex | 语义搜索、相似性查询 | 支持语义搜索、高精度 | 计算成本高、需要嵌入模型 |
| SummaryIndex | 文档摘要、完整内容查询 | 简单易用、完整上下文 | 不支持语义搜索、内存占用大 |
6.索引构建的最佳实践 #
6.1 选择合适的索引类型 #
- 如果需要语义搜索,选择
VectorStoreIndex - 如果需要文档摘要,选择
SummaryIndex - 如果数据是知识图谱,考虑使用知识图谱索引
6.2 优化嵌入向量生成 #
# 导入VectorStoreIndex、Document和Settings类
from llama_index.core import VectorStoreIndex, Document, Settings
# 导入OpenAIEmbedding嵌入模型
from llama_index.embeddings.openai import OpenAIEmbedding
# 设置嵌入模型为OpenAIEmbedding(默认使用text-embedding-ada-002)
Settings.embed_model = OpenAIEmbedding()
# 构建示例文档内容列表
sample_texts = [
"LlamaIndex提供了多种索引类型来满足不同的应用需求。",
"向量索引通过嵌入向量实现语义搜索功能。",
"摘要索引适合需要完整上下文的文档处理任务。",
]
# 将每段文本包装为Document对象
documents = [Document(text=text) for text in sample_texts]
# 创建向量索引,并在构建过程中显示进度条(show_progress=True)
index = VectorStoreIndex.from_documents(documents, show_progress=True)
# 通过向量索引创建查询引擎
query_engine = index.as_query_engine()
# 执行查询,问题为“LlamaIndex有哪些索引类型?”
response = query_engine.query("LlamaIndex有哪些索引类型?")
# 输出查询结果
print(response)
6.3 处理大量数据 #
对于大量数据,考虑:
# 导入VectorStoreIndex和Document类,用于向量索引和文档对象的创建
from llama_index.core import VectorStoreIndex, Document
# 导入SentenceSplitter类,用于将文档分割为节点
from llama_index.core.node_parser import SentenceSplitter
# 创建大量示例文档,这里生成100个包含LlamaIndex信息的文档
# 实际应用中,文档内容可能来自文件、数据库或API
large_sample_texts = [
f"这是第{i}个文档,包含关于LlamaIndex的信息。"
+ "LlamaIndex是一个强大的数据框架,用于构建LLM应用。"
+ "它支持多种索引类型和查询策略。"
for i in range(100) # 创建100个文档
]
# 设置每批处理的文档数量为20
batch_size = 20
# 用于存储所有Document对象的列表
all_documents = []
# 按批次处理large_sample_texts,避免一次性处理大量数据导致内存问题
for i in range(0, len(large_sample_texts), batch_size):
# 获取当前批次的文档文本
batch_texts = large_sample_texts[i : i + batch_size]
# 将当前批次的文本转换为Document对象
batch_documents = [Document(text=text) for text in batch_texts]
# 将当前批次的Document对象添加到总文档列表中
all_documents.extend(batch_documents)
# 打印当前批次处理进度
print(f"处理了第 {i//batch_size + 1} 批文档,共 {len(batch_documents)} 个")
# 创建节点解析器,设置每个节点的最大字符数和重叠字符数以优化分割效果
node_parser = SentenceSplitter(
chunk_size=1024, # 每个节点的最大字符数
chunk_overlap=20, # 相邻节点之间的重叠字符数
)
# 打印提示信息,开始将文档分割成节点
print("正在将文档分割成节点...")
# 使用节点解析器将所有文档分割为节点
nodes = node_parser.get_nodes_from_documents(all_documents)
# 打印分割后节点的总数
print(f"共创建了 {len(nodes)} 个节点")
# 打印提示信息,开始创建向量索引
print("正在创建向量索引...")
# 基于节点创建向量索引,并显示进度条
index = VectorStoreIndex(nodes, show_progress=True)
# 创建查询引擎,用于后续的查询操作
query_engine = index.as_query_engine()
# 执行一次查询,问题为“LlamaIndex的主要功能是什么?”
response = query_engine.query("LlamaIndex的主要功能是什么?")
# 打印查询结果
print(response)
6.4 存储和重用嵌入向量 #
# 导入VectorStoreIndex、Document和StorageContext类,用于向量索引、文档对象和存储上下文管理
from llama_index.core import VectorStoreIndex, Document, StorageContext
# 导入SimpleVectorStore类,作为简单的内存向量存储实现
from llama_index.core.vector_stores import SimpleVectorStore
# 构建示例文档内容列表
sample_texts = [
"LlamaIndex支持持久化存储,可以保存和重用嵌入向量。",
"通过持久化存储,可以避免重复计算嵌入向量,节省时间和成本。",
"SimpleVectorStore是LlamaIndex提供的一个简单的向量存储实现。",
]
# 将每段文本包装为Document对象,便于后续索引处理
documents = [Document(text=text) for text in sample_texts]
# 创建一个SimpleVectorStore实例,作为内存中的向量存储
vector_store = SimpleVectorStore()
# 通过StorageContext.from_defaults方法,指定vector_store,创建存储上下文
storage_context = StorageContext.from_defaults(vector_store=vector_store)
# 基于文档和存储上下文创建向量索引,并显示进度条
index = VectorStoreIndex.from_documents(
documents, storage_context=storage_context, show_progress=True
)
# 通过向量索引创建查询引擎
query_engine = index.as_query_engine()
# 执行一次查询,问题为“如何避免重复计算嵌入向量?”
response = query_engine.query("如何避免重复计算嵌入向量?")
# 打印查询结果
print(response)
# 打印提示信息,说明索引已创建并可使用
print("索引已创建并可以使用")
SimpleVectorStore 是 LlamaIndex 提供的一个内存中的向量存储实现,它的主要作用包括:
- 向量存储
- 存储文档的嵌入向量(embeddings)
- 存储向量与原始文本的映射关系
- 提供向量检索功能
- 内存管理
- 所有向量数据存储在内存中
- 程序运行期间数据保持可用
- 程序结束后数据会丢失(除非手动保存)
- 性能优化
- 内存访问速度快
- 适合中小规模的数据集
- 避免重复计算嵌入向量
7.延伸阅读 #
如果您的数据是一组相互关联的概念(用计算机科学的术语来说,就是一个"graph"),那么您可能会对我们的知识图谱索引感兴趣。
8.总结 #
索引是RAG应用的核心组件,它决定了如何存储和检索您的数据:
- VectorStoreIndex:最常用的索引类型,支持语义搜索
- SummaryIndex:适合文档摘要的简单索引
- 知识图谱索引:适合结构化关系数据
选择合适的索引类型对于构建高效的RAG应用至关重要。根据您的具体需求和数据特点,选择最适合的索引类型将显著提升应用性能。