你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

教程:在 Microsoft Foundry 模型嵌入和文档搜索中探索 Azure OpenAI

本教程将指导你完成使用 Azure OpenAI mbeddings API 执行 文档搜索,可在其中查询知识库以查找最相关的文档。

本教程介绍如何:

  • 下载示例数据集并将其准备进行分析。
  • 为资源终结点和 API 密钥创建环境变量。
  • 使用以下模型之一:text-embedding-ada-002(版本 2)、text-embedding-3-large、text-embedding-3-small 模型。
  • 使用 余弦相似性 对搜索结果进行排名。

先决条件

设置

Python库

如果尚未安装,则需要安装以下库:

pip install openai num2words matplotlib plotly scipy scikit-learn pandas tiktoken

下载 BillSum 数据集

BillSum 是一个美国国会和加州州法案的数据集。 出于说明目的,我们将只查看美国账单。 该语料库由国会第103-115届(1993-2018年)法案组成。 这些数据被拆分为18,949个火车账单和3,269个测试账单。 BillSum 语料库专注于中等长度、字符数量在 5,000 到 20,000 的立法文本。 有关此数据集派生自的项目和原始学术论文的详细信息,请参阅 BillSum 项目的GitHub存储库

本教程使用可从 GitHub 示例数据下载的 bill_sum_data.csv 文件

还可以在本地计算机上运行以下命令来下载示例数据:

curl "https://raw.githubusercontent.com/Azure-Samples/Azure-OpenAI-Docs-Samples/main/Samples/Tutorials/Embeddings/data/bill_sum_data.csv" --output bill_sum_data.csv

注意

目前不支持使用 v1 API 嵌入基于Microsoft Entra ID身份验证。

检索密钥和终结点

若要成功调用 Azure OpenAI,需要 endpointkey

变量名称 价值
ENDPOINT 可以在 Azure 门户中检查资源时,于 Keys & Endpoint 部分找到服务终结点。 或者,可以通过 Microsoft Foundry 门户中的 Deployments 页找到终结点。 一个示例终结点是: https://docs-test-001.openai.azure.com/.
API-KEY 可以在 Azure 门户中的 Keys & Endpoint 部分找到此值,在检查资源时。 可以使用KEY1KEY2

在 Azure 门户中转到您的资源。 可以在“资源管理”部分找到“密钥和终结点”部分。 复制终结点和访问密钥,因为需要两者对 API 调用进行身份验证。 可以使用KEY1KEY2。 始终使用两个密钥可以安全地轮换和重新生成密钥,而不会造成服务中断。

Azure 门户中 Azure OpenAI 资源概述界面的截图,端点和访问密钥位置以红色圈出。

环境变量

为 API 密钥创建和分配持久性环境变量。

重要

请谨慎使用 API 密钥。 不要直接在代码中包括 API 密钥,并且从不公开发布。 如果使用 API 密钥,请安全地将其存储在Azure 密钥保管库中。 有关在应用中安全地使用 API 密钥的详细信息,请参阅 API 密钥和 Azure 密钥保管库

有关 AI 服务安全性的详细信息,请参阅 请求 Azure AI 服务的身份验证

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 

设置环境变量后,可能需要关闭并重新打开 Jupyter 笔记本或正在使用的任何 IDE 才能访问环境变量。 如果因为某种原因你无法使用 Jupyter Notebook,我们建议你需要修改返回 pandas 数据帧的任何代码,使用 print(dataframe_name) 而不是像通常在代码块末尾那样直接调用 dataframe_name

在首选的 Python IDE 中运行以下代码:

导入库

import os
import re
import requests
import sys
from num2words import num2words
import os
import pandas as pd
import numpy as np
import tiktoken
from openai import OpenAI

现在,我们需要读取 csv 文件并创建 pandas 数据帧。 创建初始数据帧后,可以通过运行 df来查看表的内容。

df=pd.read_csv(os.path.join(os.getcwd(),'bill_sum_data.csv')) # This assumes that you have placed the bill_sum_data.csv in the same directory you are running Jupyter Notebooks
df

输出:

csv 文件中初始 DataFrame 表结果的屏幕截图。

我们初始的表包含的列数多于我们需要的列数,因此我们将创建一个较小的新 DataFrame,称为df_bills,它将只包含textsummarytitle的列。

df_bills = df[['text', 'summary', 'title']]
df_bills

输出:

较小的 DataFrame 表结果的屏幕截图,其中仅显示文本、摘要和标题列。

接下来,我们将执行一些轻数据清理,方法是删除冗余空格并清理标点符号,以便为令牌化准备数据。

pd.options.mode.chained_assignment = None #https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#evaluation-order-matters

# s is input text
def normalize_text(s, sep_token = " \n "):
    s = re.sub(r'\s+',  ' ', s).strip()
    s = re.sub(r"\. ,","",s) 
    # remove all instances of multiple spaces
    s = s.replace("..",".")
    s = s.replace(". .",".")
    s = s.replace("\n", "")
    s = s.strip()
    
    return s

df_bills['text']= df_bills["text"].apply(lambda x : normalize_text(x))

现在,我们需要移除任何超出令牌限制的账单(8,192 个令牌)。

tokenizer = tiktoken.get_encoding("cl100k_base")
df_bills['n_tokens'] = df_bills["text"].apply(lambda x: len(tokenizer.encode(x)))
df_bills = df_bills[df_bills.n_tokens<8192]
len(df_bills)
20

注意

在这种情况下,所有账单都在嵌入模型输入令牌限制下,但你可以使用上述技术删除导致嵌入失败的条目。 当面对超出嵌入限制的内容时,还可以将内容分块成较小的部分,然后逐个嵌入区块。

我们将再次检查 df_bills

df_bills

输出:

DataFrame 的屏幕截图,其中包含名为“n_tokens”的新列。

若要了解n_tokens列,以及文本最终如何进行标记化,运行以下代码会很有帮助:

sample_encode = tokenizer.encode(df_bills.text[0]) 
decode = tokenizer.decode_tokens_bytes(sample_encode)
decode

对于我们的文档,我们有意截断输出,但在环境中运行此命令将返回从索引零标记化为区块的全文。 你可以看到,在某些情况下,整个单词都用单个标记表示,而其他部分的单词则跨多个标记进行拆分。

[b'SECTION',
 b' ',
 b'1',
 b'.',
 b' SHORT',
 b' TITLE',
 b'.',
 b' This',
 b' Act',
 b' may',
 b' be',
 b' cited',
 b' as',
 b' the',
 b' ``',
 b'National',
 b' Science',
 b' Education',
 b' Tax',
 b' In',
 b'cent',
 b'ive',
 b' for',
 b' Businesses',
 b' Act',
 b' of',
 b' ',
 b'200',
 b'7',
 b"''.",
 b' SEC',
 b'.',
 b' ',
 b'2',
 b'.',
 b' C',
 b'RED',
 b'ITS',
 b' FOR',
 b' CERT',
 b'AIN',
 b' CONTRIBUT',
 b'IONS',
 b' BEN',
 b'EF',
 b'IT',
 b'ING',
 b' SC',

如果随后检查变量的 decode 长度,你会发现它与n_tokens列中的第一个数字匹配。

len(decode)
1466

现在,我们更了解令牌化的工作原理,接下来可以继续嵌入。 请务必注意,我们尚未对文档进行标记。 该 n_tokens 列只是用于确保传递给模型进行标记化和嵌入的数据不会超过输入令牌的最大限制8192的方式。 当我们将文档传递到嵌入模型时,它会将文档分解为与上面的示例类似的标记(尽管不一定相同),然后将这些标记转换为一系列浮点数,可通过矢量搜索访问。 这些嵌入可以存储在本地或Azure数据库中,以支持矢量搜索。 因此,每个账单都将在数据帧右侧的新 ada_v2 列中有自己的相应的嵌入向量。

在下面的示例中,我们为每个要嵌入的项调用一次嵌入模型。 在处理大型嵌入项目时,可以选择传递一个输入数组给模型进行嵌入,而不是每次仅传递一个输入。 当您向模型传递一个输入数组时,每次调用嵌入端点的最大输入项数为 2048。

client = OpenAI(
  api_key = os.getenv("AZURE_OPENAI_API_KEY"),  
  base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"
)

def generate_embeddings(text, model="text-embedding-ada-002"): # model = "deployment_name"
    return client.embeddings.create(input = [text], model=model).data[0].embedding

df_bills['ada_v2'] = df_bills["text"].apply(lambda x : generate_embeddings (x, model = 'text-embedding-ada-002')) # model should be set to the deployment name you chose when you deployed the text-embedding-ada-002 (Version 2) model
df_bills

输出:

df_bills命令中格式化结果的屏幕截图。

运行下面的搜索代码块时,我们将把搜索查询“获取有关电缆公司税收的信息”与相同的text-embedding-ada-002(版本 2)模型嵌入在一起。 接下来,我们将从按余弦相似性排序的查询中,找到与新嵌入文本最接近的向量。

def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

def get_embedding(text, model="text-embedding-ada-002"): # model = "deployment_name"
    return client.embeddings.create(input = [text], model=model).data[0].embedding

def search_docs(df, user_query, top_n=4, to_print=True):
    embedding = get_embedding(
        user_query,
        model="text-embedding-ada-002" # model should be set to the deployment name you chose when you deployed the text-embedding-ada-002 (Version 2) model
    )
    df["similarities"] = df.ada_v2.apply(lambda x: cosine_similarity(x, embedding))

    res = (
        df.sort_values("similarities", ascending=False)
        .head(top_n)
    )
    if to_print:
        display(res)
    return res

res = search_docs(df_bills, "Can I get information on cable company tax revenue?", top_n=4)

输出

运行搜索查询后格式化的 res 结果的屏幕截图。

最后,我们将根据针对整个知识库的用户查询显示文档搜索的顶级结果。 这返回了“1993年纳税人查看法”的最佳结果。该文档在查询与文档之间的余弦相似性分数为 0.76。

res["summary"][9]
"Taxpayer's Right to View Act of 1993 - Amends the Communications Act of 1934 to prohibit a cable operator from assessing separate charges for any video programming of a sporting, theatrical, or other entertainment event if that event is performed at a facility constructed, renovated, or maintained with tax revenues or by an organization that receives public financial support. Authorizes the Federal Communications Commission and local franchising authorities to make determinations concerning the applicability of such prohibition. Sets forth conditions under which a facility is considered to have been constructed, maintained, or renovated with tax revenues. Considers events performed by nonprofit or public organizations that receive tax subsidies to be subject to this Act if the event is sponsored by, or includes the participation of a team that is part of, a tax exempt organization."

使用这种方法,您可以将嵌入用作在知识库中文档中搜索的机制。 然后,用户可以获取排名靠前的搜索结果并将其用于其下游任务,从而提示其初始查询。

故障 排除

  • 401/403:验证 AZURE_OPENAI_API_KEY 是否已设置并匹配资源密钥。
  • 404:验证 AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT 是否与部署名称匹配。
  • 无效 URL:验证 AZURE_OPENAI_ENDPOINT 是否为您的资源终结点,例如 https://<resource-name>.openai.azure.com

清理资源

如果只为完成本教程创建了一个 Azure OpenAI 资源,并且想要清理和删除 Azure OpenAI 资源,则需要删除已部署的模型,然后删除资源或关联的资源组(如果专用于测试资源)。 删除资源组也会删除与之关联的任何其他资源。

后续步骤

详细了解 Azure OpenAI 的模型: