LlamaIndex的多租户
如果您正在构建一个为许多独立用户提供向量服务的服务,并且希望隔离他们的数据,那么最佳实践是使用单个集合并进行基于负载的分区。这种方法称为多租户。我们的单独分区指南描述了如何进行一般设置,但是如果您使用 LlamaIndex 作为后端,您可能更喜欢阅读更具体的说明。那么,就是这样!
先决条件
本教程假设您已经安装了 Qdrant 和 LlamaIndex。如果您还没有安装,请运行以下命令:
pip install llama-index llama-index-vector-stores-qdrant
我们将使用本地的基于 Docker 的 Qdrant 实例。如果您想使用远程实例,请相应地调整代码。以下是我们如何启动本地实例:
docker run -d --name qdrant -p 6333:6333 -p 6334:6334 qdrant/qdrant:latest
设置 LlamaIndex 管道
我们将使用 LlamaIndex 实现一个多租户应用程序的端到端示例。我们将索引不同 Python 库的文档,我们绝对不希望任何用户看到他们不感兴趣的库的结果。在真实的场景中,这甚至更危险,因为文档可能包含敏感信息。
创建向量存储
QdrantVectorStore 是 Qdrant 的一个包装器,它提供了在 LlamaIndex 中使用您的向量数据库所需的所有方法。让我们为我们的集合创建一个向量存储。它需要设置集合名称并传递一个 QdrantClient 实例。
from qdrant_client import QdrantClient
from llama_index.vector_stores.qdrant import QdrantVectorStore
client = QdrantClient("https://:6333")
vector_store = QdrantVectorStore(
collection_name="my_collection",
client=client,
)
定义分块策略和嵌入模型
任何语义搜索应用程序都需要一种将文本查询转换为向量的方法——一个嵌入模型。ServiceContext 是在任何 LlamaIndex 应用程序的索引和查询阶段常用的资源包。我们也可以用它来设置一个嵌入模型——在我们的例子中,是一个本地的 BAAI/bge-small-en-v1.5。设置
from llama_index.core import ServiceContext
service_context = ServiceContext.from_defaults(
embed_model="local:BAAI/bge-small-en-v1.5",
)
注意,如果您使用的不是 OpenAI 的 ChatGPT 的大型语言模型,则应为 ServiceContext 指定 llm 参数。
我们还可以控制文档如何使用 LLamaIndex 的术语拆分为块或节点。SimpleNodeParser 将文档拆分为具有重叠的固定长度块。默认值是合理的,但我们也可以根据需要调整它们。这两个值都以 token 为单位定义。
from llama_index.core.node_parser import SimpleNodeParser
node_parser = SimpleNodeParser.from_defaults(chunk_size=512, chunk_overlap=32)
现在我们还需要将我们的选择告知 ServiceContext
service_context = ServiceContext.from_defaults(
embed_model="local:BAAI/bge-large-en-v1.5",
node_parser=node_parser,
)
嵌入模型和选定的节点解析器都将在索引和查询期间隐式使用。
将所有内容组合在一起
在我们开始索引之前,最后缺少的部分是 VectorStoreIndex。它是 VectorStore 的一个包装器,提供了一个方便的索引和查询接口。它还需要初始化一个 ServiceContext。
from llama_index.core import VectorStoreIndex
index = VectorStoreIndex.from_vector_store(
vector_store=vector_store, service_context=service_context
)
索引文档
无论我们的文档是如何生成的,LlamaIndex 都会自动将它们拆分为节点(如果需要),使用选定的嵌入模型进行编码,然后存储在向量存储中。让我们手动定义一些文档并将它们插入到 Qdrant 集合中。我们的文档将只有一个元数据属性——它们所属的库名称。
from llama_index.core.schema import Document
documents = [
Document(
text="LlamaIndex is a simple, flexible data framework for connecting custom data sources to large language models.",
metadata={
"library": "llama-index",
},
),
Document(
text="Qdrant is a vector database & vector similarity search engine.",
metadata={
"library": "qdrant",
},
),
]
现在我们可以使用我们的 VectorStoreIndex 对它们进行索引
for document in documents:
index.insert(document)
性能考虑
我们的文档已拆分为节点,使用嵌入模型进行编码,并存储在向量存储中。但是,我们不希望允许用户搜索集合中的所有文档,而只搜索他们感兴趣的库所属的文档。为此,我们需要设置 Qdrant 有效载荷索引,以便搜索更高效。
from qdrant_client import models
client.create_payload_index(
collection_name="my_collection",
field_name="metadata.library",
field_type=models.PayloadSchemaType.KEYWORD,
)
有效载荷索引并不是我们唯一要更改的东西。由于任何搜索查询都不会在整个集合上执行,我们还可以更改其配置,以便不全局构建 HNSW 图。这也是由于性能原因而进行的。如果您知道集合上将执行一些全局搜索操作,则不应更改这些参数。
client.update_collection(
collection_name="my_collection",
hnsw_config=models.HnswConfigDiff(payload_m=16, m=0),
)
一旦这两个操作完成,我们就可以开始搜索我们的文档了。
使用约束查询文档
假设我们正在搜索有关大型语言模型的一些信息,但只允许使用 Qdrant 文档。LlamaIndex 有一个检索器概念,负责为给定查询查找最相关的节点。我们的 VectorStoreIndex 可以用作检索器,并带有一些额外的约束——在我们的例子中是 library 元数据属性的值。
from llama_index.core.vector_stores.types import MetadataFilters, ExactMatchFilter
qdrant_retriever = index.as_retriever(
filters=MetadataFilters(
filters=[
ExactMatchFilter(
key="library",
value="qdrant",
)
]
)
)
nodes_with_scores = qdrant_retriever.retrieve("large language models")
for node in nodes_with_scores:
print(node.text, node.score)
# Output: Qdrant is a vector database & vector similarity search engine. 0.60551536
Qdrant 的描述是最佳匹配,尽管它根本没有提及大型语言模型。但是,它是唯一属于 qdrant 库的文档,所以没有其他选择。让我们尝试搜索集合中不存在的内容。
让我们定义另一个检索器,这次用于 llama-index 库
llama_index_retriever = index.as_retriever(
filters=MetadataFilters(
filters=[
ExactMatchFilter(
key="library",
value="llama-index",
)
]
)
)
nodes_with_scores = llama_index_retriever.retrieve("large language models")
for node in nodes_with_scores:
print(node.text, node.score)
# Output: LlamaIndex is a simple, flexible data framework for connecting custom data sources to large language models. 0.63576734
由于不同的约束,两个检索器返回的结果是不同的,因此我们实现了一个真正的多租户搜索应用程序!