LlamaIndex(六)——LlamaIndex Storing

一、简介

LlamaIndex 提供了一个高级接口,用于提取、索引和查询您的外部数据。在底层,LlamaIndex 还支持可互换的存储组件,允许您自定义:

  • Document stores: 存储提取的文档(即,Node 对象)的地方
  • Index stores: 存储索引元数据的地方
  • Vector stores: 存储向量的地方
  • Graph stores: 存储知识图谱的地方(即 KnowledgeGraphIndex)
  • Chat Stores: 存储和组织聊天记录的地方

文档/索引存储依赖于一个共同的键值存储抽象。LlamaIndex 支持将数据持久化到 fsspec 支持的任何存储后端。已确认支持以下存储后端:

  • 本地文件系统
  • AWS S3
  • Cloudflare R2

1.1 使用示例

许多向量存储(除了 FAISS)会同时存储数据和索引(embeddings)。这意味着不需要使用单独的文档存储或索引存储。这也意味着不需要显式地持久化这些数据 - 这会自动发生。构建新索引/重新加载现有索引的用法可能如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
## build a new index
from llama_index.core import VectorStoreIndex, StorageContext
from llama_index.vector_stores.deeplake import DeepLakeVectorStore

# construct vector store and customize storage context
vector_store = DeepLakeVectorStore(dataset_path="<dataset_path>")
storage_context = StorageContext.from_defaults(vector_store=vector_store)
# Load documents and build index
index = VectorStoreIndex.from_documents(
documents, storage_context=storage_context
)


## reload an existing one
index = VectorStoreIndex.from_vector_store(vector_store=vector_store)

通常要使用存储抽象,需要定义一个 StorageContext 对象:

1
2
3
4
5
6
7
8
9
10
11
from llama_index.core.storage.docstore import SimpleDocumentStore
from llama_index.core.storage.index_store import SimpleIndexStore
from llama_index.core.vector_stores import SimpleVectorStore
from llama_index.core import StorageContext

# create storage context using default stores
storage_context = StorageContext.from_defaults(
docstore=SimpleDocumentStore(),
vector_store=SimpleVectorStore(),
index_store=SimpleIndexStore(),
)

二、Vector Stores

向量存储包含被提取文档块的向量(有时也包含文档块本身)。

2.1 简单向量存储

默认情况下,LlamaIndex 使用一个简单的内存向量存储,非常适合快速实验。
它们可以通过调用 vector_store.persist()(以及 SimpleVectorStore.from_persist_path(...) )保存到磁盘上(并从磁盘加载)。

LlamaIndex 支持超过 20 种不同的向量存储选项。具体可查看:https://docs.llamaindex.ai/en/stable/community/integrations/vector_stores/

三、Document Stores

文档存储包含了已提取的文档块,称之为节点对象(Node objects)API Reference

3.1 简单文档存储

默认情况下,SimpleDocumentStore 将节点对象存储在内存中。可以通过调用 docstore.persist()(以及 SimpleDocumentStore.from_persist_path(...))将它们持久化到磁盘(并从磁盘加载)。

完整例子

3.2 MongoDB 文档存储

LlamaIndex支持 MongoDB 作为另一种文档存储后端,它在提取节点对象时持久化数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from llama_index.storage.docstore.mongodb import MongoDocumentStore
from llama_index.core.node_parser import SentenceSplitter

# create parser and parse document into nodes
parser = SentenceSplitter()
nodes = parser.get_nodes_from_documents(documents)

# create (or load) docstore and add nodes
docstore = MongoDocumentStore.from_uri(uri="<mongodb+srv://...>")
docstore.add_documents(nodes)

# create storage context
storage_context = StorageContext.from_defaults(docstore=docstore)

# build index
index = VectorStoreIndex(nodes, storage_context=storage_context)

在底层,MongoDocumentStore 连接到一个固定的 MongoDB 数据库,并将节点初始化新集合(或加载现有集合)。在实例化 MongoDocumentStore 时,可以配置 db_namenamespace,否则它们默认为 db_name="db_docstore"namespace="docstore"。当使用 MongoDocumentStore 时,不必调用 storage_context.persist()(或 docstore.persist()),因为数据默认情况下会被持久化。

可以通过使用现有的 db_namecollection_name 重新初始化 MongoDocumentStore,轻松地重新连接到 MongoDB 集合并重新加载索引。

完整的示例

3.3 Redis 文档存储

LlamaIndex支持 Redis 作为另一种文档存储后端,它在提取节点对象时持久化数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from llama_index.storage.docstore.redis import RedisDocumentStore
from llama_index.core.node_parser import SentenceSplitter

# create parser and parse document into nodes
parser = SentenceSplitter()
nodes = parser.get_nodes_from_documents(documents)

# create (or load) docstore and add nodes
docstore = RedisDocumentStore.from_host_and_port(
host="127.0.0.1", port="6379", namespace="llama_index"
)
docstore.add_documents(nodes)

# create storage context
storage_context = StorageContext.from_defaults(docstore=docstore)

# build index
index = VectorStoreIndex(nodes, storage_context=storage_context)

在底层,RedisDocumentStore 连接到一个 redis 数据库,并将您的节点添加到一个存储在 {namespace}/docs 下的命名空间中。在实例化 RedisDocumentStore 时,可以配置命名空间,否则它默认为 namespace="docstore"。可以通过使用现有的 host、portnamespace 重新初始化 RedisDocumentStore,轻松地重新连接到 Redis 客户端并重新加载索引。

完整的示例

3.4 Firestore 文档存储

LlamaIndex支持 Firestore 作为另一种文档存储后端,它在提取节点对象时持久化数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from llama_index.storage.docstore.firestore import FirestoreDocumentStore
from llama_index.core.node_parser import SentenceSplitter

# create parser and parse document into nodes
parser = SentenceSplitter()
nodes = parser.get_nodes_from_documents(documents)

# create (or load) docstore and add nodes
docstore = FirestoreDocumentStore.from_database(
project="project-id",
database="(default)",
)
docstore.add_documents(nodes)

# create storage context
storage_context = StorageContext.from_defaults(docstore=docstore)

# build index
index = VectorStoreIndex(nodes, storage_context=storage_context)

在底层,FirestoreDocumentStore 连接到 Google Cloud 中的 firestore 数据库,并将节点添加到存储在 {namespace}/docs 下的命名空间中。在实例化 FirestoreDocumentStore 时,您可以配置命名空间,否则它默认为 namespace="docstore"。可以通过使用现有的 project、databasenamespace 重新初始化FirestoreDocumentStore,轻松地重新连接到 Firestore 数据库并重新加载索引。

完整的示例

四、Index Stores

索引存储包含轻量级索引元数据(即构建索引时创建的附加状态信息)。API Reference

4.1 简单索引存储

默认情况下,LlamaIndex 使用一个简单的索引存储,该存储由内存中的键值存储支持。它们可以通过调用 index_store.persist()(以及 SimpleIndexStore.from_persist_path(...))持久化到磁盘并从磁盘加载。

4.2 MongoDB 索引存储

类似于文档存储,LlamaIndex也可以将 MongoDB 用作索引存储的存储后端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from llama_index.storage.index_store.mongodb import MongoIndexStore
from llama_index.core import VectorStoreIndex

# create (or load) index store
index_store = MongoIndexStore.from_uri(uri="<mongodb+srv://...>")

# create storage context
storage_context = StorageContext.from_defaults(index_store=index_store)

# build index
index = VectorStoreIndex(nodes, storage_context=storage_context)

# or alternatively, load index
from llama_index.core import load_index_from_storage

index = load_index_from_storage(storage_context)

在底层,MongoIndexStore 连接到一个固定的 MongoDB 数据库,并为索引元数据初始化新集合(或加载现有集合)。可以在实例化 MongoIndexStore 时配置 db_namenamespace,否则它们默认为 db_name="db_docstore"namespace="docstore"。当使用 MongoIndexStore 时,没有必要调用 storage_context.persist()(或 index_store.persist()),因为数据默认情况下会被持久化。可以通过使用现有的 db_namecollection_name 重新初始化 MongoIndexStore,轻松地重新连接到 MongoDB 集合并重新加载索引。

完整的示例

4.3 Redis 索引存储

LlamaIndex支持 Redis 作为另一种文档存储后端,它在提取节点对象时持久化数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from llama_index.storage.index_store.redis import RedisIndexStore
from llama_index.core import VectorStoreIndex

# create (or load) docstore and add nodes
index_store = RedisIndexStore.from_host_and_port(
host="127.0.0.1", port="6379", namespace="llama_index"
)

# create storage context
storage_context = StorageContext.from_defaults(index_store=index_store)

# build index
index = VectorStoreIndex(nodes, storage_context=storage_context)

# or alternatively, load index
from llama_index.core import load_index_from_storage

index = load_index_from_storage(storage_context)

在底层,RedisIndexStore 连接到一个 redis 数据库,并将节点添加到一个存储在 {namespace}/index 下的命名空间中。可以在实例化 RedisIndexStore 时配置命名空间,否则它默认为 namespace="index_store"。可以通过使用现有的host,portnamespace重新初始化 RedisIndexStore,轻松地重新连接到 Redis 客户端并重新加载索引。

完整的示例

五、Chat Stores

聊天存储充当一个集中的接口来存储聊天记录。与其他存储格式相比,聊天记录是独特的,因为消息的顺序对于维持整个对话至关重要。
聊天存储可以通过键(如user_ids或其他唯一可识别的字符串)组织聊天消息序列,并处理删除、插入和获取操作。

5.1 SimpleChatStore

最基本的聊天存储是SimpleChatStore,它将消息存储在内存中,并且可以保存到/从磁盘,或者可以序列化并存储在其他地方。
通常,将实例化一个聊天存储并将其提供给一个记忆模块。使用聊天存储的记忆模块将默认使用SimpleChatStore

1
2
3
4
5
6
7
8
9
10
from llama_index.core.storage.chat_store import SimpleChatStore
from llama_index.core.memory import ChatMemoryBuffer

chat_store = SimpleChatStore()

chat_memory = ChatMemoryBuffer.from_defaults(
token_limit=3000,
chat_store=chat_store,
chat_store_key="user1",
)

一旦创建了记忆,可以将其包含在代理或聊天引擎中:

1
2
3
agent = OpenAIAgent.from_tools(tools, memory=memory)
# OR
chat_engine = index.as_chat_engine(memory=memory)

为了稍后保存聊天存储,可以通过磁盘保存/加载

1
2
3
4
chat_store.persist(persist_path="chat_store.json")
loaded_chat_store = SimpleChatStore.from_persist_path(
persist_path="chat_store.json"
)

或者可以将聊天存储转换为字符串,然后将其保存在其他地方

1
2
chat_store_string = chat_store.json()
loaded_chat_store = SimpleChatStore.parse_raw(chat_store_string)

5.2 RedisChatStore

使用RedisChatStore,可以远程存储聊天记录,而无需担心手动持久化和加载聊天记录。

1
2
3
4
5
6
7
8
9
10
from llama_index.storage.chat_store.redis import RedisChatStore
from llama_index.core.memory import ChatMemoryBuffer

chat_store = RedisChatStore(redis_url="redis://localhost:6379", ttl=300)

chat_memory = ChatMemoryBuffer.from_defaults(
token_limit=3000,
chat_store=chat_store,
chat_store_key="user1",
)

六、Key-Value Stores

键值存储是Document StoresIndex Stores背后的基础存储抽象。

提供以下键值存储:

  • Simple Key-Value Store:一个内存中的 KV 存储。用户可以选择调用persist进行持久化操作,将数据持久化到磁盘。

  • MongoDB Key-Value Store:一个 MongoDB KV 存储。

API Reference

七、Persisting & Loading Data

7.1 持久化数据

默认情况下,LlamaIndex 将数据存储在内存中,如果需要,可以显式地将数据持久化到磁盘:

1
storage_context.persist(persist_dir="<persist_dir>")

这将把数据持久化到磁盘,在指定的 persist_dir(或默认的 ./storage)下。
可以在同一目录下持久化并从该目录加载多个索引,假设你在加载时能跟踪索引 ID。
用户还可以配置其他存储后端(例如 MongoDB),这些后端默认会持久化数据。在这种情况下,调用 storage_context.persist() 将不执行任何操作。

7.2 加载数据

要加载数据,用户只需使用相同的配置重新创建存储上下文(例如,传入相同的 persist_dir 或向量存储客户端)。

1
2
3
4
5
6
7
storage_context = StorageContext.from_defaults(
docstore=SimpleDocumentStore.from_persist_dir(persist_dir="<persist_dir>"),
vector_store=SimpleVectorStore.from_persist_dir(
persist_dir="<persist_dir>"
),
index_store=SimpleIndexStore.from_persist_dir(persist_dir="<persist_dir>"),
)

然后,可以通过以下便捷函数从 StorageContext 加载特定的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from llama_index.core import (
load_index_from_storage,
load_indices_from_storage,
load_graph_from_storage,
)

# load a single index
# need to specify index_id if multiple indexes are persisted to the same directory
index = load_index_from_storage(storage_context, index_id="<index_id>")

# don't need to specify index_id if there's only one index in storage context
index = load_index_from_storage(storage_context)

# load multiple indices
indices = load_indices_from_storage(storage_context) # loads all indices
indices = load_indices_from_storage(
storage_context, index_ids=[index_id1, ...]
) # loads specific indices

# load composable graph
graph = load_graph_from_storage(
storage_context, root_id="<root_id>"
) # loads graph with the specified root_id

7.3 使用远程后端

默认情况下,LlamaIndex 使用本地文件系统加载和保存文件。但是,可以通过传递一个 fsspec.AbstractFileSystem 对象来覆盖这个行为。

以下是一个简单的例子,实例化一个向量存储:

1
2
3
4
5
6
7
8
9
10
11
12
import dotenv
import s3fs
import os

dotenv.load_dotenv("../../../.env")

# load documents
documents = SimpleDirectoryReader(
"../../../examples/paul_graham_essay/data/"
).load_data()
print(len(documents))
index = VectorStoreIndex.from_documents(documents)

实例化一个 S3 文件系统,并从那里保存 / 加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# set up s3fs
AWS_KEY = os.environ["AWS_ACCESS_KEY_ID"]
AWS_SECRET = os.environ["AWS_SECRET_ACCESS_KEY"]
R2_ACCOUNT_ID = os.environ["R2_ACCOUNT_ID"]

assert AWS_KEY is not None and AWS_KEY != ""

s3 = s3fs.S3FileSystem(
key=AWS_KEY,
secret=AWS_SECRET,
endpoint_url=f"https://{R2_ACCOUNT_ID}.r2.cloudflarestorage.com",
s3_additional_kwargs={"ACL": "public-read"},
)

# If you're using 2+ indexes with the same StorageContext,
# run this to save the index to remote blob storage
index.set_index_id("vector_index")

# persist index to s3
s3_bucket_name = "llama-index/storage_demo" # {bucket_name}/{index_name}
index.storage_context.persist(persist_dir=s3_bucket_name, fs=s3)

# load index from s3
index_from_s3 = load_index_from_storage(
StorageContext.from_defaults(persist_dir=s3_bucket_name, fs=s3),
index_id="vector_index",
)

默认情况下,如果没有传递一个文件系统,将使用本地文件系统。

八、Customizing Storage

LlamaIndex 隐藏了复杂性,可以在不到五行代码的情况下查询您的数据:

1
2
3
4
5
6
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("Summarize the documents.")

在底层,LlamaIndex 还支持可互换的存储层,允许自定义存储提取的文档(即 Node 对象)、embedding vectors和索引元数据的位置。

8.1 Low-Level API

high-level API

1
index = VectorStoreIndex.from_documents(documents)

Low-Level API提供了更细粒度的控制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from llama_index.core.storage.docstore import SimpleDocumentStore
from llama_index.core.storage.index_store import SimpleIndexStore
from llama_index.core.vector_stores import SimpleVectorStore
from llama_index.core.node_parser import SentenceSplitter

# create parser and parse document into nodes
parser = SentenceSplitter()
nodes = parser.get_nodes_from_documents(documents)

# create storage context using default stores
storage_context = StorageContext.from_defaults(
docstore=SimpleDocumentStore(),
vector_store=SimpleVectorStore(),
index_store=SimpleIndexStore(),
)

# create (or load) docstore and add nodes
storage_context.docstore.add_documents(nodes)

# build index
index = VectorStoreIndex(nodes, storage_context=storage_context)

# save index
index.storage_context.persist(persist_dir="<persist_dir>")

# can also set index_id to save multiple indexes to the same folder
index.set_index_id("<index_id>")
index.storage_context.persist(persist_dir="<persist_dir>")

# to load index later, make sure you setup the storage context
# this will loaded the persisted stores from persist_dir
storage_context = StorageContext.from_defaults(persist_dir="<persist_dir>")

# then load the index object
from llama_index.core import load_index_from_storage

loaded_index = load_index_from_storage(storage_context)

# if loading an index from a persist_dir containing multiple indexes
loaded_index = load_index_from_storage(storage_context, index_id="<index_id>")

# if loading multiple indexes from a persist dir
loaded_indicies = load_index_from_storage(
storage_context, index_ids=["<index_id>", ...]
)

可以通过一行代码更改实例化不同的文档存储索引存储向量存储,来自定义底层存储。

8.2 向量存储集成和存储

LlamaIndex的大多数向量存储集成将整个索引(向量 + 文本)存储在向量存储本身中。这带来了一个主要的好处,即不必像上面所示明确地持久化索引,因为向量存储已经托管并持久化了索引中的数据。

支持这种做法的向量存储有:

  • AzureAISearchVectorStore
  • ChatGPTRetrievalPluginClient
  • CassandraVectorStore
  • ChromaVectorStore
  • EpsillaVectorStore
  • DocArrayHnswVectorStore
  • DocArrayInMemoryVectorStore
  • JaguarVectorStore
  • LanceDBVectorStore
  • MetalVectorStore
  • MilvusVectorStore
  • MyScaleVectorStore
  • OpensearchVectorStore
  • PineconeVectorStore
  • QdrantVectorStore
  • RedisVectorStore
  • UpstashVectorStore
  • WeaviateVectorStore

使用 Pinecone 的一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pinecone
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.vector_stores.pinecone import PineconeVectorStore

# Creating a Pinecone index
api_key = "api_key"
pinecone.init(api_key=api_key, environment="us-west1-gcp")
pinecone.create_index(
"quickstart", dimension=1536, metric="euclidean", pod_type="p1"
)
index = pinecone.Index("quickstart")

# construct vector store
vector_store = PineconeVectorStore(pinecone_index=index)

# create storage context
storage_context = StorageContext.from_defaults(vector_store=vector_store)

# load documents
documents = SimpleDirectoryReader("./data").load_data()

# create index, which will insert documents/vectors to pinecone
index = VectorStoreIndex.from_documents(
documents, storage_context=storage_context
)

如果有一个已经加载了数据的现有向量存储,可以连接到它并直接创建 VectorStoreIndex,如下所示:

1
2
3
index = pinecone.Index("quickstart")
vector_store = PineconeVectorStore(pinecone_index=index)
loaded_index = VectorStoreIndex.from_vector_store(vector_store=vector_store)

官方资源


LlamaIndex(六)——LlamaIndex Storing
https://mztchaoqun.com.cn/posts/D19_LlamaIndex_Storing/
作者
mztchaoqun
发布于
2024年5月1日
许可协议