在 Weaviate 中使用 OpenAI Q&A 模块进行问答

本 Notebook 适用于以下场景:

本 Notebook 将引导您完成设置 Weaviate 实例、连接到它(使用 OpenAI API 密钥)、配置数据模式、导入数据(这将自动为您的数据生成向量嵌入)以及运行问答的简单流程。

什么是 Weaviate

Weaviate 是一个开源向量搜索引擎,它将数据对象与其向量一起存储。这允许将向量搜索与结构化过滤相结合。

Weaviate 使用 KNN 算法创建向量优化索引,从而使您的查询运行速度极快。在此处了解更多信息:here

Weaviate 让您可以使用您喜欢的 ML 模型,并无缝扩展到数十亿个数据对象。

部署选项

无论您的场景或生产设置如何,Weaviate 都有适合您的选项。您可以在以下设置中部署 Weaviate:

  • 自我托管 – 您可以使用 docker 在本地部署 Weaviate,或在任何您想要的服务器上部署。
  • SaaS – 您可以使用 Weaviate Cloud Service (WCS) 来托管您的 Weaviate 实例。
  • 混合 SaaS – 您可以在自己的私有云服务中部署 Weaviate

编程语言

Weaviate 提供四种 客户端库,允许您从应用程序与 Weaviate 通信:

此外,Weaviate 还提供了一个 REST 层。基本上,您可以使用任何支持 REST 请求的语言来调用 Weaviate。

演示流程

演示流程如下:

  • 先决条件设置:创建 Weaviate 实例并安装所需的库
  • 连接:连接到您的 Weaviate 实例
  • 模式配置:配置您数据的数据模式
    • 注意:在这里我们可以定义要使用的 OpenAI 嵌入模型
    • 注意:在这里我们可以配置要索引的属性
  • 导入数据:加载演示数据集并将其导入 Weaviate
    • 注意:导入过程将自动索引您的数据 - 基于模式中的配置
    • 注意:您无需显式向量化数据,Weaviate 将与 OpenAI 通信为您完成
  • 运行查询:查询
    • 注意:您无需显式向量化查询,Weaviate 将与 OpenAI 通信为您完成
    • 注意qna-openai 模块会自动与 OpenAI completions 端点通信

运行完此 Notebook 后,您应该对如何设置向量数据库以及如何使用嵌入进行各种酷炫操作有一个基本的了解。

Weaviate 中的 OpenAI 模块

所有 Weaviate 实例都配备了 text2vec-openaiqna-openai 模块。

第一个模块负责在导入(或任何 CRUD 操作)和运行搜索查询时处理向量化。第二个模块与 OpenAI completions 端点通信。

无需手动向量化数据

这对您来说是个好消息。使用 text2vec-openai,您无需手动向量化数据,因为 Weaviate 会在必要时为您调用 OpenAI。

您需要做的就是:

  1. 在连接到 Weaviate 客户端时提供您的 OpenAI API 密钥
  2. 在模式中定义要使用的 OpenAI 向量化器

先决条件

在开始此项目之前,我们需要设置以下内容:

  • 创建一个 Weaviate 实例
  • 安装库
    • weaviate-client
    • datasets
    • apache-beam
  • 获取您的 OpenAI API 密钥

===========================================================

创建 Weaviate 实例

要创建 Weaviate 实例,我们有两种选择:

  1. (推荐路径)Weaviate Cloud Service – 在云中托管您的 Weaviate 实例。免费沙盒应该足以满足此食谱的需求。
  2. 使用 Docker 在本地安装和运行 Weaviate。

选项 1 – WCS 安装步骤

使用 Weaviate Cloud Service (WCS) 创建免费的 Weaviate 集群。

  1. 创建一个免费帐户和/或登录到 WCS
  2. 创建一个 Weaviate Cluster 并进行以下设置:
    • 沙盒:Sandbox Free
    • Weaviate 版本:使用默认值(最新)
    • OIDC 身份验证:Disabled
  3. 您的实例应该在一两分钟内准备就绪
  4. 记下 Cluster Id。该链接将带您进入集群的完整路径(稍后连接它时需要)。它应该类似于:https://your-project-name.weaviate.network

选项 2 – 使用 Docker 的本地 Weaviate 实例

使用 Docker 在本地安装和运行 Weaviate。

  1. 下载 ./docker-compose.yml 文件
  2. 然后打开您的终端,导航到您的 docker-compose.yml 文件所在的位置,然后使用以下命令启动 docker:docker-compose up -d
  3. 一旦准备就绪,您的实例应该可以在 http://localhost:8080 上访问

注意。要关闭您的 docker 实例,您可以调用:docker-compose down

了解更多

要了解有关将 Weaviate 与 Docker 结合使用的更多信息,请参阅 安装文档

===========================================================

安装所需的库

在运行此项目之前,请确保已安装以下库:

Weaviate Python 客户端

Weaviate Python 客户端允许您从 Python 项目与 Weaviate 实例进行通信。

datasets & apache-beam

要加载示例数据,您需要 datasets 库及其依赖项 apache-beam

# 安装 Weaviate Python 客户端
!pip install weaviate-client>3.11.0

# 安装 datasets 和 apache-beam 以加载示例数据集
!pip install datasets apache-beam

===========================================================

准备您的 OpenAI API 密钥

OpenAI API 密钥 用于在导入时对数据进行向量化,以及用于查询。

如果您没有 OpenAI API 密钥,可以从 https://beta.openai.com/account/api-keys 获取。

获取密钥后,请将其添加到您的环境变量中,名为 OPENAI_API_KEY

# 导出 OpenAI API 密钥
!export OPENAI_API_KEY="your key"
# 测试您的 OpenAI API 密钥是否已正确设置为环境变量
# 注意。如果您在本地运行此 Notebook,则需要重新加载您的终端和 Notebook,环境变量才能生效。
import os

# 注意。或者,您可以像这样设置一个临时环境变量:
# os.environ['OPENAI_API_KEY'] = 'your-key-goes-here'

if os.getenv("OPENAI_API_KEY") is not None:
    print ("OPENAI_API_KEY is ready")
else:
    print ("OPENAI_API_KEY environment variable not found")

连接到您的 Weaviate 实例

在本节中,我们将:

  1. 测试环境变量 OPENAI_API_KEY请确保您已完成 #Prepare-your-OpenAI-API-key 步骤
  2. 使用您的 OpenAI API Key 连接到您的 Weaviate
  3. 并测试客户端连接

客户端

完成此步骤后,client 对象将用于执行所有与 Weaviate 相关的操作。

import weaviate
from datasets import load_dataset
import os

# 连接到您的 Weaviate 实例
client = weaviate.Client(
    url="https://your-wcs-instance-name.weaviate.network/",
#   url="http://localhost:8080/",
    auth_client_secret=weaviate.auth.AuthApiKey(api_key="<YOUR-WEAVIATE-API-KEY>"), # 如果您没有为 Weaviate 实例使用身份验证(例如,本地部署的实例),请注释掉此行
    additional_headers={
        "X-OpenAI-Api-Key": os.getenv("OPENAI_API_KEY")
    }
)

# 检查您的实例是否在线并准备就绪
# 这应该返回 `True`
client.is_ready()

模式

在本节中,我们将:

  1. 配置您数据的数据模式
  2. 选择 OpenAI 模块

这是第二步也是最后一步,需要 OpenAI 的特定配置。 完成此步骤后,其余说明将仅涉及 Weaviate,因为 OpenAI 的任务将自动处理。

什么是模式

在 Weaviate 中,您创建 模式 来捕获您将搜索的每个实体。

模式是您告诉 Weaviate 的方式:

  • 应使用哪个 OpenAI 嵌入模型来向量化数据
  • 您的数据由什么组成(属性名称和类型)
  • 哪些属性应被向量化和索引

在此食谱中,我们将使用一个 Articles 数据集,其中包含:

  • title
  • content
  • url

我们希望向量化 titlecontent,但不向量化 url

为了向量化和查询数据,我们将使用 text-embedding-3-small。对于问答,我们将使用 gpt-3.5-turbo-instruct

# 清除模式,以便我们可以重新创建它
client.schema.delete_all()
client.schema.get()

# 定义模式对象,对 `title` 和 `content` 使用 `text-embedding-3-small`,但跳过 `url`
article_schema = {
    "class": "Article",
    "description": "A collection of articles",
    "vectorizer": "text2vec-openai",
    "moduleConfig": {
        "text2vec-openai": {
          "model": "ada",
          "modelVersion": "002",
          "type": "text"
        },
        "qna-openai": {
          "model": "gpt-3.5-turbo-instruct",
          "maxTokens": 16,
          "temperature": 0.0,
          "topP": 1,
          "frequencyPenalty": 0.0,
          "presencePenalty": 0.0
        }
    },
    "properties": [{
        "name": "title",
        "description": "Title of the article",
        "dataType": ["string"]
    },
    {
        "name": "content",
        "description": "Contents of the article",
        "dataType": ["text"]
    },
    {
        "name": "url",
        "description": "URL to the article",
        "dataType": ["string"],
        "moduleConfig": { "text2vec-openai": { "skip": True } }
    }]
}

# 添加 Article 模式
client.schema.create_class(article_schema)

# 获取模式以确保其正常工作
client.schema.get()

导入数据

在本节中,我们将:

  1. 加载 Simple Wikipedia 数据集
  2. 配置 Weaviate Batch 导入(以提高导入效率)
  3. 将数据导入 Weaviate

注意:
如前所述。我们无需手动向量化数据。
text2vec-openai 模块将负责处理此问题。

### 步骤 1 - 加载数据集

from datasets import load_dataset
from typing import List, Iterator

# 我们将使用 datasets 库来获取 Simple Wikipedia 数据集进行嵌入
dataset = list(load_dataset("wikipedia", "20220301.simple")["train"])

# 为了测试,演示目的限制为 2.5k 篇文章
dataset = dataset[:2_500]

# 为了更大的演示目的,限制为 25k 篇文章
# dataset = dataset[:25_000]

# 对于免费的 OpenAI 账户,您可以使用 50 个对象
# dataset = dataset[:50]
### 步骤 2 - 配置 Weaviate Batch,包括
# - 起始批次大小为 100
# - 根据性能动态增加/减少
# - 添加超时重试以防万一出现问题

client.batch.configure(
    batch_size=10,
    dynamic=True,
    timeout_retries=3,
#   callback=None,
)
### 步骤 3 - 导入数据

print("正在导入文章")

counter=0

with client.batch as batch:
    for article in dataset:
        if (counter %10 == 0):
            print(f"正在导入 {counter} / {len(dataset)} ")

        properties = {
            "title": article["title"],
            "content": article["text"],
            "url": article["url"]
        }

        batch.add_data_object(properties, "Article")
        counter = counter+1

print("导入文章完成")
# 测试所有数据是否已加载 – 获取对象计数
result = (
    client.query.aggregate("Article")
    .with_fields("meta { count }")
    .do()
)
print("对象计数: ", result["data"]["Aggregate"]["Article"], "\n")
# 通过检查一个对象来测试一篇已成功处理的文章
test_article = (
    client.query
    .get("Article", ["title", "url", "content"])
    .with_limit(1)
    .do()
)["data"]["Get"]["Article"][0]

print(test_article['title'])
print(test_article['url'])
print(test_article['content'])

对数据进行问答

如上所述,我们将向新索引发送一些查询,并根据与现有向量的接近程度获得结果

def qna(query, collection_name):

    properties = [
        "title", "content", "url",
        "_additional { answer { hasAnswer property result startPosition endPosition } distance }"
    ]

    ask = {
        "question": query,
        "properties": ["content"]
    }

    result = (
        client.query
        .get(collection_name, properties)
        .with_ask(ask)
        .with_limit(1)
        .do()
    )

    # 检查错误
    if ("errors" in result):
        print ("\033[91m您可能已经用完了当前分钟的 OpenAI API 调用次数 – 限制为每分钟 60 次。")
        raise Exception(result["errors"][0]['message'])

    return result["data"]["Get"][collection_name]
query_result = qna("Did Alanis Morissette win a Grammy?", "Article")

for i, article in enumerate(query_result):
    print(f"{i+1}. { article['_additional']['answer']['result']} (Distance: {round(article['_additional']['distance'],3) })")
query_result = qna("What is the capital of China?", "Article")

for i, article in enumerate(query_result):
    if article['_additional']['answer']['hasAnswer'] == False:
      print('未找到答案')
    else:
      print(f"{i+1}. { article['_additional']['answer']['result']} (Distance: {round(article['_additional']['distance'],3) })")

感谢您的跟随,您现在已经具备了设置自己的向量数据库并使用嵌入来完成各种酷炫操作的知识——尽情享受吧!对于更复杂的用例,请继续完成此仓库中的其他食谱示例。