Press "Enter" to skip to content

开始使用Weaviate:向量数据库搜索入门指南

如何使用OpenAI和Weaviate在Python中使用向量数据库进行语义搜索、问题回答和生成式搜索

图像

如果您看到了这篇文章,我假设您已经在使用大型语言模型(LLM)构建应用程序,并遇到了向量数据库这个术语。

使用LLM构建应用程序的工具生态系统正在迅速发展,其中诸如LangChain或LlamaIndex等工具越来越受欢迎。

在最近的一篇文章中,我描述了如何使用LangChain入门,在本文中,我想继续探索LLM工具生态系统,并尝试使用Weaviate。

什么是Weaviate?

Weaviate是一个开源的向量数据库。它使您能够存储数据对象和向量嵌入,并基于相似度度量对它们进行查询。

GitHub – weaviate/weaviate: Weaviate是一个开源的向量数据库,可以同时存储对象和向量

Weaviate是一个开源的向量数据库,可以同时存储对象和向量,允许组合向量搜索

github.com

自从媒体关注LLM以来,向量数据库一直受到广泛关注。在LLM的背景下,向量数据库最受欢迎的用例可能是“为LLM提供长期记忆”。

如果您需要了解向量数据库的概念,请参阅我的上一篇文章:

以3个难度级别解释向量数据库

从新手到专家:揭秘不同背景下的向量数据库

towardsdatascience.com

在本教程中,我们将演示如何使用您的数据集的嵌入来填充Weaviate向量数据库。然后,我们将介绍三种不同的从中检索信息的方法:

  • 向量搜索
  • 问题回答
  • 生成式搜索

先决条件

要参与本教程,您需要具备以下条件:

  • Python 3环境
  • OpenAI API密钥(或者,Hugging Face、Cohere或PaLM的API密钥)

关于API密钥的说明:在本教程中,我们将通过推理服务(在本例中为OpenAI)从文本生成嵌入。根据您使用的推理服务,确保查看提供商的定价页面,以避免意外费用。例如,使用的Ada模型(版本2)在撰写本教程时每1,000个标记的成本为0.0001美元,并且在本教程的推理费用中不到1美分。

设置

您可以在自己的实例上运行Weaviate(使用Docker、Kubernetes或嵌入式Weaviate),也可以使用Weaviate Cloud Services(WCS)作为托管服务运行Weaviate。对于本教程,我们将使用WCS运行Weaviate实例,因为这是推荐且最简单的方式。

如何使用Weaviate Cloud Services(WCS)创建集群

要使用该服务,您首先需要在WCS上注册。

注册后,您可以通过点击“创建集群”按钮来创建一个新的Weaviate集群。

Weaviate云服务的截图

在本教程中,我们将使用免费试用计划,为您提供一个为期14天的沙箱环境。(您不需要添加任何付款信息。相反,在试用期结束后,沙箱将自动过期。但您可以随时创建一个新的免费试用沙箱。)

在“免费沙箱”选项卡下,进行以下设置:

  1. 输入群集名称
  2. 启用身份验证(设置为“是”)
Weaviate云服务计划的屏幕截图

最后,点击“创建”来创建您的沙箱实例。

如何在Python中安装Weaviate

最后一步,使用pipweaviate-client添加到您的Python环境中

$ pip install weaviate-client

并导入库:

import weaviate

如何通过客户端访问Weaviate群集

在下一步中,您将需要以下两个信息来访问您的群集:

  • 群集URL
  • Weaviate API密钥(在“启用 – 身份验证”下)
Weaviate云服务沙箱的屏幕截图

现在,您可以实例化一个Weaviate客户端来访问您的Weaviate群集,如下所示。

auth_config = weaviate.AuthApiKey(api_key="YOUR-WEAVIATE-API-KEY")  # 替换为您的Weaviate实例API密钥# 实例化客户端client = weaviate.Client(    url="https://<your-sandbox-name>.weaviate.network", # 替换为您的Weaviate群集URL    auth_client_secret=auth_config,    additional_headers={        "X-OpenAI-Api-Key": "YOUR-OPENAI-API-KEY", # 替换为您的OpenAI密钥        })

如您所见,我们将在additional_headers下使用OpenAI API密钥来访问嵌入模型。如果您使用的是除OpenAI之外的其他提供商,请将密钥参数更改为以下适用之一:X-Cohere-Api-KeyX-HuggingFace-Api-KeyX-Palm-Api-Key

为了检查是否设置正确,请运行:

client.is_ready()

如果返回True,则已准备好进行下一步。

如何创建和填充Weaviate向量数据库

现在,我们准备在Weaviate中创建一个向量数据库,并填充一些数据。

在本教程中,我们将使用Kaggle上的200,000+ Jeopardy问题数据集[1]的前100行。

import pandas as pddf = pd.read_csv("your_file_path.csv", nrows = 100)
来自Kaggle的200,000+ Jeopardy问题数据集[1]的前几行。

关于令牌数量和相关成本的说明:在下面的示例中,我们将嵌入前100行的“category”、“question”和“answer”列。根据使用tiktoken库的计算,这将导致大约3000个令牌需要嵌入,这在2023年7月时以OpenAI的Ada模型(版本2)进行推理的成本大约为0.0003美元。

步骤 1:创建模式

首先,我们需要定义底层的数据结构和一些配置:

  • class: 这个向量空间中的对象集合将被称为什么?
  • properties: 对象的属性,包括属性名称和数据类型。在Pandas Dataframe中的类比,这些将成为DataFrame中的列。
  • vectorizer: 生成嵌入的模型。对于文本对象,您通常会选择text2vec模块之一(text2vec-coheretext2vec-huggingfacetext2vec-openaitext2vec-palm),具体根据您使用的提供者来决定。
  • moduleConfig: 在这里,您可以定义所使用模块的详细信息。例如,嵌入器是一个模块,您可以定义要使用的模型和版本。
class_obj = {    # 类定义    "class": "JeopardyQuestion",    # 属性定义    "properties": [        {            "name": "category",            "dataType": ["text"],        },        {            "name": "question",            "dataType": ["text"],        },        {            "name": "answer",            "dataType": ["text"],        },    ],    # 指定一个嵌入器    "vectorizer": "text2vec-openai",    # 模块设置    "moduleConfig": {        "text2vec-openai": {            "vectorizeClassName": False,            "model": "ada",            "modelVersion": "002",            "type": "text"        },    },}

在上面的模式中,您可以看到我们将创建一个名为"JeopardyQuestion"的类,其中包含三个文本属性"category""question""answer"。我们使用的嵌入器是OpenAI的Ada模型(版本2)。所有属性都将被嵌入,但类名不会被嵌入("vectorizeClassName" : False)。如果您有不希望嵌入的属性,您可以指定这一点(请参阅文档)。

一旦您定义了模式,您可以使用create_class()方法创建类。

client.schema.create_class(class_obj)

要检查类是否已成功创建,您可以按以下方式查看其模式:

client.schema.get("JeopardyQuestion")

创建的模式如下所示:

{  "class": "JeopardyQuestion",  "invertedIndexConfig": {    "bm25": {      "b": 0.75,      "k1": 1.2    },    "cleanupIntervalSeconds": 60,    "stopwords": {      "additions": null,      "preset": "en",      "removals": null    }  },  "moduleConfig": {    "text2vec-openai": {      "model": "ada",      "modelVersion": "002",      "type": "text",      "vectorizeClassName": false    }  },  "properties": [    {      "dataType": [        "text"      ],      "indexFilterable": true,      "indexSearchable": true,      "moduleConfig": {        "text2vec-openai": {          "skip": false,          "vectorizePropertyName": false        }      },      "name": "category",      "tokenization": "word"    },    {      "dataType": [        "text"      ],      "indexFilterable": true,      "indexSearchable": true,      "moduleConfig": {        "text2vec-openai": {          "skip": false,          "vectorizePropertyName": false        }      },      "name": "question",      "tokenization": "word"    },    {      "dataType": [        "text"      ],      "indexFilterable": true,      "indexSearchable": true,      "moduleConfig": {        "text2vec-openai": {          "skip": false,          "vectorizePropertyName": false        }      },      "name": "answer",      "tokenization": "word"    }  ],  "replicationConfig": {    "factor": 1  },  "shardingConfig": {    "virtualPerPhysical": 128,    "desiredCount": 1,    "actualCount": 1,    "desiredVirtualCount": 128,    "actualVirtualCount": 128,    "key": "_id",    "strategy": "hash",    "function": "murmur3"  },  "vectorIndexConfig": {    "skip": false,    "cleanupIntervalSeconds": 300,    "maxConnections": 64,    "efConstruction": 128,    "ef": -1,    "dynamicEfMin": 100,    "dynamicEfMax": 500,    "dynamicEfFactor": 8,    "vectorCacheMaxObjects": 1000000000000,    "flatSearchCutoff": 40000,    "distance": "cosine",    "pq": {      "enabled": false,      "bitCompression": false,      "segments": 0,      "centroids": 256,      "encoder": {        "type": "kmeans",        "distribution": "log-normal"      }    }  },  "vectorIndexType": "hnsw",  "vectorizer": "text2vec-openai"}

步骤2:将数据导入Weaviate

在这个阶段,向量数据库具有模式但还是空的。所以,让我们用我们的数据集填充它。这个过程也被称为“upserting”。

我们将以200的批次进行upsert。如果你注意到了,你就会知道这在这里是不必要的,因为我们只有100行数据。但是一旦你准备好upsert更大量的数据,你就会希望以批次进行。这就是为什么我会在这里留下批处理代码的原因:

from weaviate.util import generate_uuid
with client.batch(
    batch_size=200,  # 指定批次大小
    num_workers=2,   # 并行化处理
) as batch:
    for _, row in df.iterrows():
        question_object = {
            "category": row.category,
            "question": row.question,
            "answer": row.answer,
        }
        batch.add_data_object(
            question_object,
            class_name="JeopardyQuestion",
            uuid=generate_uuid5(question_object)
        )

虽然Weaviate会自动生成一个全局唯一标识符(uuid),但我们会使用generate_uuid5()函数从question_object手动生成uuid,以避免导入重复项。

为了进行合理性检查,你可以使用以下代码片段查看导入的对象数量:

client.query.aggregate("JeopardyQuestion").with_meta_count().do()

{'data': {'Aggregate': {'JeopardyQuestion': [{'meta': {'count': 100}}]}}}

如何查询Weaviate向量数据库

你在向量数据库中最常见的操作是检索对象。要检索对象,你可以使用get()函数查询Weaviate向量数据库:

client.query.get(
    <类名>,
    [<属性>]
).<参数>.do()
  • 类名:指定要检索的对象类的名称。这里是: "JeopardyQuestion"
  • 属性:指定要检索的对象的属性。这里是一个或多个"category""question""answer"
  • 参数:指定用于检索对象的搜索条件,如限制或聚合。我们将在以下示例中介绍其中一些。

让我们使用get()函数从JeopardyQuestion类中检索一些条目,看看它们是什么样子。在Pandas类比中,你可以将以下内容视为df.head(2)。由于get()函数的响应是JSON格式,所以我们将导入相关的库以以视觉上令人愉悦的格式显示结果。

import json
res = client.query.get("JeopardyQuestion",
                ["question", "answer", "category"])
                .with_additional(["id", "vector"])
                .with_limit(2)
                .do()
print(json.dumps(res, indent=4))

{    "data": {        "Get": {            "JeopardyQuestion": [                {                    "_additional": {                        "id": "064fee53-f8fd-4513-9294-432170cc9f77",                        "vector": [ -0.02465364, ...] # 向量被截断以便更好读取                     },                    "answer": "(Lou) Gehrig",                    "category": "ESPN's TOP 10 ALL-TIME ATHLETES",                    "question": "No. 10: FB/LB for Columbia U. in the 1920s; MVP for the Yankees in '27 & '36; \"Gibraltar in Cleats\""                },                {                    "_additional": {                        "id": "1041117a-34af-40a4-ad05-3dae840ad6b9",                        "vector": [ -0.031970825, ...] # 向量被截断以便更好读取                    },                    "answer": "Jim Thorpe",                    "category": "ESPN's TOP 10 ALL-TIME ATHLETES",                    "question": "No. 2: 1912 Olympian; football star at Carlisle Indian School; 6 MLB seasons with the Reds, Giants & Braves"                },            ]        }    }}

在上面的代码片段中,您可以看到我们正在从”JeopardyQuestion”类中检索对象。我们指定了要检索的属性”category”,”question”和”answer”。

我们指定了两个额外的参数:首先,我们使用”.with_additional()”参数指定检索对象的id和向量嵌入的附加信息。然后,我们使用”.with_limit(2)”参数指定只检索两个对象。这个限制很重要,在后面的示例中您会再次看到它。这是因为从向量数据库中检索对象不返回精确匹配,而是根据相似性返回对象,这必须通过阈值进行限制。

现在,我们准备进行一些向量搜索!从向量数据库中检索信息的很酷之处在于,您可以告诉它检索与动物相关的”concepts”的Jeopardy问题。

为此,我们可以使用”.with_near_text()”参数,并将我们感兴趣的”concepts”作为参数传递,如下所示:

res = client.query.get(    "JeopardyQuestion",    ["question", "answer", "category"])\    .with_near_text({"concepts": "animals"})\    .with_limit(2)\    .do()

指定的向量化器然后将输入文本(“animals”)转换为向量嵌入并检索两个最接近的结果:

{    "data": {        "Get": {            "JeopardyQuestion": [                {                    "answer": "an octopus",                    "category": "SEE & SAY",                    "question": "Say the name of <a href=\"http://www.j-archive.com/media/2010-07-06_DJ_26.jpg\" target=\"_blank\">this</a> type of mollusk you see"                },                {                    "answer": "the ant",                    "category": "3-LETTER WORDS",                    "question": "In the title of an Aesop fable, this insect shared billing with a grasshopper"                }            ]        }    }}

您已经可以看到这是多么酷:我们可以看到向量搜索返回了两个答案都是动物的问题,而这两个问题属于完全不同的类别。使用传统的关键字搜索,您首先需要定义一个动物列表,然后检索包含其中一个定义的动物的所有问题。

问答

问答是将LLMs与向量数据库结合的最受欢迎的示例之一。

要启用问答功能,您需要在模块配置下指定一个向量化器(您应该已经有了)和一个问答模块,如下例所示:

# 模块设置    "moduleConfig": {        "text2vec-openai": {          ...        },        "qna-openai": {          "model": "text-davinci-002"        }    },

对于问答,您需要添加”with_ask()”参数,并且还需要检索”_additional”属性。

ask = {  "question": "Which animal was mentioned in the title of the Aesop fable?",  "properties": ["answer"]}res = (  client.query  .get("JeopardyQuestion", [      "question",      "_additional {answer {hasAnswer property result} }"  ])  .with_ask(ask)  .with_limit(1)  .do())

上面的代码片段查找可能包含答案”Which animal was mentioned in the title of the Aesop fable?”的所有问题,并返回答案”The ant”。

{    "JeopardyQuestion": [        {            "_additional": {                "answer": {                    "hasAnswer": true,                    "property": "",                    "result": " The ant"                }            },            "question": "In the title of an Aesop fable, this insect shared billing with a grasshopper"        }    ]}

通过集成LLMs,您还可以在返回搜索结果之前对数据进行转换。这个概念被称为生成式搜索。

要启用生成式搜索,您需要在模块配置下指定一个生成式模块,如下例所示:

# 模块设置    "moduleConfig": {        "text2vec-openai": {          ...        },        "generative-openai": {          "model": "gpt-3.5-turbo"        }    },

对于生成式搜索,您只需要在之前的向量搜索代码中添加with_generate()参数,如下所示:

res = client.query.get(    "JeopardyQuestion",     ["question", "answer"])\  .with_near_text({"concepts": ["animals"]})\  .with_limit(1)\  .with_generate(single_prompt= "生成一个问题,其答案为{answer}")\  .do()

上面的代码段执行以下操作:

  1. 搜索与概念"animals"最接近的问题
  2. 返回问题"Say the name of this type of mollusk you see"和答案"an octopus"
  3. 生成一个针对提示"生成一个问题,其答案为an octopus"的补全,最终结果如下:
{    "generate": {        "error": null,        "singleResult": "What sea creature has eight arms and is known for its intelligence and camouflage abilities?"    }}

总结

LLM空间的流行不仅带来了许多有趣的新开发者工具,如LangChain或LLaMaIndex,而且还向我们展示了如何利用已有的工具,如向量数据库,来增强LLM驱动应用的潜力。

在本文中,我们开始使用Weaviate来不仅使用向量数据库进行向量搜索,还结合LLMs进行问答和生成式搜索。

如果您对更深入的介绍感兴趣,我建议查看这个关于向量数据库和Weaviate的全面四部分课程:

1. 从零开始到MVP | Weaviate-向量数据库

课程概述

weaviate.io

免责声明:我是Weaviate的开发者倡导者,在撰写本文时。

喜欢这个故事吗?

免费订阅以在我发布新故事时收到通知。

想要阅读超过3个免费故事吗?-成为VoAGI会员,每月5美元。您可以通过在注册时使用我的推荐链接来支持我。我将不会额外收取费用。

作为VoAGI会员,您会员费的一部分将给到您阅读的作家,并且您将获得对每个故事的完全访问权限…

VoAGI.com

在LinkedIn、Twitter和Kaggle上找到我!

参考文献

数据集

[1] Ulrik Thyge Pedersen (2023). 200.000+ Jeopardy Questions in Kaggle Datasets.

许可证:署名 4.0 国际(CC BY 4.0)

图片来源

除非另有说明,所有图片均由作者创建。

网络与文献

[2] Weaviate (2023). Weaviate documentation (accessed 14. July 2023)

Leave a Reply

Your email address will not be published. Required fields are marked *