使用 LlamaIndex 的多租户

如果您正在构建一个为许多独立用户提供向量服务的服务,并且您希望隔离他们的数据,最佳实践是使用一个具有基于 payload 分区的单一 collection。这种方法称为多租户。我们关于独立分区的指南描述了如何进行通用设置,但如果您使用 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 中使用向量数据库所需的所有方法。让我们为我们的 collection 创建一个向量存储。它需要设置 collection 名称并传递一个 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 将文档分割成固定长度的块并带有重叠。默认值是合理的,但我们也可以根据需要进行调整。这两个值都以 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 collection 中。我们的文档将有一个元数据属性——它们所属的库名称。

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)

性能考量

我们的文档已经被分割成节点,使用嵌入模型编码,并存储在向量存储中。但是,我们不想允许用户搜索 collection 中的所有文档,而只搜索他们感兴趣的库所属的文档。因此,我们需要设置 Qdrant payload 索引,以提高搜索效率。

from qdrant_client import models

client.create_payload_index(
    collection_name="my_collection",
    field_name="metadata.library",
    field_type=models.PayloadSchemaType.KEYWORD,
)

payload 索引不是我们唯一想改变的东西。由于没有搜索查询会在整个 collection 上执行,我们也可以改变其配置,以便 HNSW 图不是全局构建的。这样做也是出于性能原因如果您知道将在 collection 上执行一些全局搜索操作,则不应更改这些参数。

client.update_collection(
    collection_name="my_collection",
    hnsw_config=models.HnswConfigDiff(payload_m=16, m=0),
)

一旦这两个操作完成,我们就可以开始搜索我们的文档了。

带约束条件地查询文档

假设我们正在搜索有关大型语言模型的一些信息,但只允许使用 Qdrant 文档。LlamaIndex 有一个 retriever(检索器)的概念,负责查找给定查询最相关的节点。我们的 VectorStoreIndex 可以用作 retriever,并带有一些附加约束——在我们的例子中是 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 库的文档,所以没有其他选择。让我们尝试搜索 collection 中不存在的东西。

让我们定义另一个检索器,这次是针对 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

两个检索器返回的结果不同,这是由于不同的约束造成的,因此我们实现了一个真正的多租户搜索应用!

此页面有用吗?

感谢您的反馈! 🙏

很抱歉听到这个消息。😔 您可以在 GitHub 上编辑此页面,或者创建一个 GitHub Issue。