使用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("http://localhost: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将文档分割成固定长度的块,并带有重叠部分。默认值是合理的,但如果需要,我们也可以调整它们。这两个值都是以标记为单位定义的。
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
由于不同的约束条件,两个检索器返回的结果不同,因此我们实现了一个真正的多租户搜索应用程序!
