Share via


チュートリアル: Azure OpenAI Service の埋め込みとドキュメント検索を確認する

このチュートリアルでは、Azure OpenAI 埋め込み API を使ってドキュメント検索を実行し、ナレッジ ベースにクエリを実行して最も関連性の高いドキュメントを見つける方法について説明します。

このチュートリアルでは、次の作業を行う方法について説明します。

  • Azure OpenAI をインストールする。
  • サンプル データセットをダウンロードして、分析用に準備する。
  • リソース エンドポイントと API キーの環境変数を作成します。
  • text-embedding-ada-002 (バージョン 2) モデルを使用します。
  • コサインの類似度を用いて検索結果を優先度付けします。

前提条件

  • Azure サブスクリプション - 無料アカウントを作成します
  • 目的の Azure サブスクリプション内の Azure OpenAI に付与されたアクセス権。 現時点では、このサービスへのアクセスは申請によってのみ許可されます。 Azure OpenAI へのアクセスを申請するには、https://aka.ms/oai/access のフォームに入力してください。 問題がある場合は、このリポジトリで問題をオープンしてお問い合わせください。
  • text-embedding-ada-002 (バージョン 2) モデルがデプロイされた Azure OpenAI リソース。 このモデルは現在、特定のリージョンでのみ使用できます。 リソースがない場合は、リソース デプロイ ガイドでリソース作成プロセスを参照してください。
  • Python 3.8 以降のバージョン
  • 次の Python ライブラリ: openai、num2words、matplotlib、plotly、scipy、scikit-learn、pandas、transformers
  • Jupyter Notebook

設定

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

キーとエンドポイントを取得する

Azure OpenAI に対して正常に呼び出しを行うには、エンドポイントキーが必要です。

変数名
ENDPOINT この値は、Azure portal からリソースを確認する際に、 [Keys & Endpoint](キーとエンドポイント) セクションで確認することができます。 Azure OpenAI Studio>[プレイグラウンド]>[コード ビュー] で値を確認することもできます。 エンドポイントの例: https://docs-test-001.openai.azure.com/
API-KEY この値は、Azure portal からリソースを確認する際に、 [Keys & Endpoint](キーとエンドポイント) セクションで確認することができます。 KEY1 または KEY2 を使用できます。

Azure portal でリソースに移動します。 [キーとエンドポイント] セクションは、[リソース管理] セクションにあります。 エンドポイントとアクセス キーをコピーします。これらは、API 呼び出しを認証するために両方とも必要です。 KEY1 または KEY2 を使用できます。 常に 2 つのキーを用意しておくと、サービスを中断させることなく、キーのローテーションと再生成を安全に行うことができます。

Azure portal の Azure OpenAI リソースの概要 UI のスクリーンショット。エンドポイントおよびアクセス キーの場所が赤色の丸で囲まれています。

環境変数

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 
setx AZURE_OPENAI_ENDPOINT "REPLACE_WITH_YOUR_ENDPOINT_HERE" 

環境変数を設定した後、その環境変数にアクセスできるようにするには、Jupyter Notebook や使用している IDE を閉じてから再度開く操作が必要になる場合があります。 Jupyter Notebooks を使用することを強くお勧めしますが、何らかの理由で使用できない場合は、コード ブロックの末尾で通常行われるように、dataframe_name を直接呼び出すのではなく print(dataframe_name) を使用して pandas データフレームを返すコードを変更する必要があります。

任意の 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 AzureOpenAI

次に、csv ファイルを読み取り、pandas DataFrame を作成する必要があります。 最初の DataFrame が作成されたら、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 テーブルの結果のスクリーンショット。

最初のテーブルには必要以上に多くの列があるため、textsummarytitle 列のみを含む df_bills という小さな DataFrame を新規に作成します。

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

Note

この場合、すべての法案には埋め込みモデルの入力トークン制限が適用されますが、上記の手法を使用すると、埋め込みの失敗を引き起こす可能性があるエントリを削除できます。 埋め込み制限を超えるコンテンツがある場合は、コンテンツを小さな部分にチャンクし、一度に 1 つずつ埋め込むこともできます。

もう一度 df_bills を調べます。

df_bills

出力:

n_tokens という新しい列を含む DataFrame のスクリーンショット。

次のコードを実行すると、n_tokens 列と、テキストが最終的にトークン化されるしくみついて理解を深めることができます。

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

このドキュメントでは意図的に出力を切り捨てていますが、実際の環境でこのコマンドを実行すると、ゼロのインデックスからトークン化されてチャンクになったフル テキストが返されます。 単語全体が 1 つのトークンで表されているものや、単語の一部が複数のトークンに分割されているものがあることがわかります。

[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 列を使用すると、トークン化と埋め込みのためにモデルに渡すデータが、入力トークン制限の 8,192 を超えないようにすることができます。 ドキュメントを埋め込みモデルに渡すと、ドキュメントは上記の例と似た (必ずしも同一ではない) トークンに分割され、トークンはベクター検索を介してアクセスできる一連の浮動小数点数に変換されます。 これらの埋め込みは、ローカルに保存するか、Azure データベースに保存してベクトル検索をサポートできます。 その結果、DataFrame の右側にある新しいada_v2 列に、各法案に対応する埋め込みベクターが表示されます。

次の例では、埋め込む項目ごとに 1 回、埋め込みモデルを呼び出しています。 大規模な埋め込みプロジェクトの作業時には、一度に 1 つの入力ではなく、埋め込む入力の配列をモデルに渡すことができます。 モデルに入力の配列を渡す場合、埋め込みエンドポイントへの呼び出しあたりの入力項目の最大数は 2048 です。

client = AzureOpenAI(
  api_key = os.getenv("AZURE_OPENAI_API_KEY"),  
  api_version = "2024-02-01",
  azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
)

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 のフォーマットされた結果のスクリーンショット。

最後に、ナレッジ ベース全体に対するユーザーのクエリに基づいて、ドキュメント検索の上位結果が表示されます。 ここでは、"Taxpayer's Right to View Act of 1993" (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."

前提条件

  • Azure サブスクリプション - 無料アカウントを作成します

  • 目的の Azure サブスクリプション内の Azure OpenAI に付与されたアクセス権。

    現時点では、このサービスへのアクセスは申請によってのみ許可されます。 Azure OpenAI へのアクセスを申請するには、https://aka.ms/oai/access のフォームに入力してください。 問題がある場合は、このリポジトリで問題をオープンしてお問い合わせください。

  • text-embedding-ada-002 (バージョン 2) モデルがデプロイされた Azure OpenAI リソース。

    このモデルは現在、特定のリージョンでのみ使用できます。 リソースがない場合は、リソース デプロイ ガイドでリソース作成プロセスを参照してください。

  • PowerShell 7.4

Note

このチュートリアルの多くの例では、ステップ間で変数を再利用します。 全体を通して、同じターミナル セッションを開いたままにしてください。 ターミナルを閉じたために、前のステップで設定した変数が失われた場合は、最初からもう一度始める必要があります。

キーとエンドポイントを取得する

Azure OpenAI に対して正常に呼び出しを行うには、エンドポイントキーが必要です。

変数名
ENDPOINT この値は、Azure portal からリソースを確認する際に、 [Keys & Endpoint](キーとエンドポイント) セクションで確認することができます。 Azure OpenAI Studio>[プレイグラウンド]>[コード ビュー] で値を確認することもできます。 エンドポイントの例: https://docs-test-001.openai.azure.com/
API-KEY この値は、Azure portal からリソースを確認する際に、 [Keys & Endpoint](キーとエンドポイント) セクションで確認することができます。 KEY1 または KEY2 を使用できます。

Azure portal でリソースに移動します。 [キーとエンドポイント] セクションは、[リソース管理] セクションにあります。 エンドポイントとアクセス キーをコピーします。これらは、API 呼び出しを認証するために両方とも必要です。 KEY1 または KEY2 を使用できます。 常に 2 つのキーを用意しておくと、サービスを中断させることなく、キーのローテーションと再生成を安全に行うことができます。

Azure portal の Azure OpenAI リソースの概要 UI のスクリーンショット。エンドポイントおよびアクセス キーの場所が赤色の丸で囲まれています。

キーとエンドポイントの永続的な環境変数を作成して割り当てます。

環境変数

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 
setx AZURE_OPENAI_ENDPOINT "REPLACE_WITH_YOUR_ENDPOINT_HERE" 

このチュートリアルでは、周知の安全なサンプル データセットとして PowerShell 7.4 リファレンス ドキュメントを使います。 代わりに、Microsoft 研究ツールのサンプル データセットを調べることもできます。

プロジェクトを格納するフォルダーを作成します。 現在の場所をプロジェクト フォルダーに設定します。 Invoke-WebRequest コマンドを使ってデータセットをローカル コンピューターにダウンロードした後、アーカイブを展開します。 最後に、現在の場所を PowerShell バージョン 7.4 の参照情報が含まれるサブフォルダーに設定します。

New-Item '<FILE-PATH-TO-YOUR-PROJECT>' -Type Directory
Set-Location '<FILE-PATH-TO-YOUR-PROJECT>'

$DocsUri = 'https://github.com/MicrosoftDocs/PowerShell-Docs/archive/refs/heads/main.zip'
Invoke-WebRequest $DocsUri -OutFile './PSDocs.zip'

Expand-Archive './PSDocs.zip'
Set-Location './PSDocs/PowerShell-Docs-main/reference/7.4/'

このチュートリアルでは大量のデータを扱うので、パフォーマンスがよくなるように、.NET データ テーブル オブジェクトを使います。 データテーブルには、titlecontentprepurifilevectors の各列が含まれます。 title 列が主キーです。

次のステップでは、各マークダウン ファイルの内容をデータ テーブルに読み込みます。 また、PowerShell の -match 演算子を使って、既知のテキスト行 title:online version: をキャプチャし、それらを個別の列に格納します。 一部のファイルにはメタデータのテキスト行が含まれていませんが、これらは概要ページであり、詳細な参照ドキュメントではないため、データテーブルから除外します。

# make sure your location is the project subfolder

$DataTable = New-Object System.Data.DataTable

'title', 'content', 'prep', 'uri', 'file', 'vectors' | ForEach-Object {
    $DataTable.Columns.Add($_)
} | Out-Null
$DataTable.PrimaryKey = $DataTable.Columns['title']

$md = Get-ChildItem -Path . -Include *.md -Recurse

$md | ForEach-Object {
    $file       = $_.FullName
    $content    = Get-Content $file
    $title      = $content | Where-Object { $_ -match 'title: ' }
    $uri        = $content | Where-Object { $_ -match 'online version: ' }
    if ($title -and $uri) {
        $row                = $DataTable.NewRow()
        $row.title          = $title.ToString().Replace('title: ', '')
        $row.content        = $content | Out-String
        $row.prep           = '' # use later in the tutorial
        $row.uri            = $uri.ToString().Replace('online version: ', '')
        $row.file           = $file
        $row.vectors        = '' # use later in the tutorial
        $Datatable.rows.add($row)
    }
}

out-gridview コマンドを使ってデータを表示します (Cloud Shell では使用できません)。

$Datatable | out-gridview

出力:

DataTable の最初の結果のスクリーンショット。

次に、余分な文字、空いているスペース、その他のドキュメント表記を削除して簡単なデータ クリーニングを実行し、トークン化用にデータを準備します。 サンプル関数 Invoke-DocPrep では、PowerShell の -replace 演算子を使って、内容から削除する文字の一覧を反復処理する方法が示されています。

# sample demonstrates how to use `-replace` to remove characters from text content
function Invoke-DocPrep {
param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [string]$content
)
    # tab, line breaks, empty space
    $replace = @('\t','\r\n','\n','\r')
    # non-UTF8 characters
    $replace += @('[^\x00-\x7F]')
    # html
    $replace += @('<table>','</table>','<tr>','</tr>','<td>','</td>')
    $replace += @('<ul>','</ul>','<li>','</li>')
    $replace += @('<p>','</p>','<br>')
    # docs
    $replace += @('\*\*IMPORTANT:\*\*','\*\*NOTE:\*\*')
    $replace += @('<!','no-loc ','text=')
    $replace += @('<--','-->','---','--',':::')
    # markdown
    $replace += @('###','##','#','```')
    $replace | ForEach-Object {
        $content = $content -replace $_, ' ' -replace '  ',' '
    }
    return $content
}

Invoke-DocPrep 関数を作成した後、ForEach-Object コマンドを使って、データテーブル内のすべての行の prep 列に準備の済んだ内容を格納します。 後で取得したくなった場合に元の書式設定を使用できるように、新しい列を使っています。

$Datatable.rows | ForEach-Object { $_.prep = Invoke-DocPrep $_.content }

データテーブルをもう一度表示して変更を確認します。

$Datatable | out-gridview

ドキュメントを埋め込みモデルに渡すと、ドキュメントがトークンにエンコードされた後、コサイン類似度の検索で使う一連の浮動小数点数が返されます。 これらの埋め込みは、ローカル環境に格納することも、Azure AI 検索のベクトル検索などのサービスに格納することもできます。 各ドキュメントの新しい vectors 列には、それ自体に対応する埋め込みベクトルが含まれます。

次の例では、データテーブルの各行をループ処理し、前処理された内容のベクトルを取得して、vectors 列に格納します。 要求を頻繁に行うと OpenAI サービスによって抑えられるため、この例には、ドキュメントで提案されているエクスポネンシャル バックオフが組み込まれています。

スクリプトが完了すると、各ドキュメントの 1,536 個のベクトルのコンマ区切りリストが、各行に格納されているはずです。 エラーが発生し、状態コードが 400 の場合は、トラブルシューティングのために、ファイル パス、タイトル、エラー コードが $errorDocs という名前の変数に追加されます。 最も一般的なエラーは、トークンの数がモデルのプロンプト制限を超えたときに発生するものです。

# Azure OpenAI metadata variables
$openai = @{
    api_key     = $Env:AZURE_OPENAI_API_KEY 
    api_base    = $Env:AZURE_OPENAI_ENDPOINT # should look like 'https://<YOUR_RESOURCE_NAME>.openai.azure.com/'
    api_version = '2024-02-01' # may change in the future
    name        = $Env:AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT # custom name you chose for your deployment
}

$headers = [ordered]@{
    'api-key' = $openai.api_key
}

$url = "$($openai.api_base)/openai/deployments/$($openai.name)/embeddings?api-version=$($openai.api_version)"

$Datatable | ForEach-Object {
    $doc = $_

    $body = [ordered]@{
        input = $doc.prep
    } | ConvertTo-Json

    $retryCount = 0
    $maxRetries = 10
    $delay      = 1
    $docErrors = @()

    do {
        try {
            $params = @{
                Uri         = $url
                Headers     = $headers
                Body        = $body
                Method      = 'Post'
                ContentType = 'application/json'
            }
            $response = Invoke-RestMethod @params
            $Datatable.rows.find($doc.title).vectors = $response.data.embedding -join ','
            break
        } catch {
            if ($_.Exception.Response.StatusCode -eq 429) {
                $retryCount++
                [int]$retryAfter = $_.Exception.Response.Headers |
                    Where-Object key -eq 'Retry-After' |
                    Select-Object -ExpandProperty Value

                # Use delay from error header
                if ($delay -lt $retryAfter) { $delay = $retryAfter++ }
                Start-Sleep -Seconds $delay
                # Exponential back-off
                $delay = [math]::min($delay * 1.5, 300)
            } elseif ($_.Exception.Response.StatusCode -eq 400) {
                if ($docErrors.file -notcontains $doc.file) {
                    $docErrors += [ordered]@{
                        error   = $_.exception.ErrorDetails.Message | ForEach-Object error | ForEach-Object message
                        file    = $doc.file
                        title   = $doc.title
                    }
                }
            } else {
                throw
            }
        }
    } while ($retryCount -lt $maxRetries)
}
if (0 -lt $docErrors.count) {
    Write-Host "$($docErrors.count) documents encountered known errors such as too many tokens.`nReview the `$docErrors variable for details."
}

これで、PowerShell 7.4 リファレンス ドキュメントのローカル メモリ内データベース テーブルが作成されました。

検索文字列に基づくと、PowerShell が類似度によって各ドキュメントをランク付けできるように、別のベクトルのセットを計算する必要があります。

次の例では、検索文字列 get a list of running processes のベクトルを取得します。

$searchText = "get a list of running processes"

$body = [ordered]@{
    input = $searchText
} | ConvertTo-Json

$url = "$($openai.api_base)/openai/deployments/$($openai.name)/embeddings?api-version=$($openai.api_version)"

$params = @{
    Uri         = $url
    Headers     = $headers
    Body        = $body
    Method      = 'Post'
    ContentType = 'application/json'
}
$response = Invoke-RestMethod @params
$searchVectors = $response.data.embedding -join ','

最後に、次のサンプル関数 (Lee Holmes によって書かれたスクリプト例 Measure-VectorSimilarity を借用) では、コサイン類似度の計算が実行されて、データテーブル内の各行がランク付けされます。

# Sample function to calculate cosine similarity
function Get-CosineSimilarity ([float[]]$vector1, [float[]]$vector2) {
    $dot = 0
    $mag1 = 0
    $mag2 = 0

    $allkeys = 0..($vector1.Length-1)

    foreach ($key in $allkeys) {
        $dot  += $vector1[$key]  * $vector2[$key]
        $mag1 += ($vector1[$key] * $vector1[$key])
        $mag2 += ($vector2[$key] * $vector2[$key])
    }

    $mag1 = [Math]::Sqrt($mag1)
    $mag2 = [Math]::Sqrt($mag2)

    return [Math]::Round($dot / ($mag1 * $mag2), 3)
}

次の例のコマンドは、$Datatable 内のすべての行をループ処理して、検索文字列に対するコサイン類似度を計算します。 結果が並べ替えられて、上位 3 つの結果が $topThree という名前の変数に格納されます。 この例では、出力は返されません。

# Calculate cosine similarity for each row and select the top 3
$topThree = $Datatable | ForEach-Object {
    [PSCustomObject]@{
        title = $_.title
        similarity = Get-CosineSimilarity $_.vectors.split(',') $searchVectors.split(',')
    }
} | Sort-Object -property similarity -descending | Select-Object -First 3 | ForEach-Object {
    $title = $_.title
    $Datatable | Where-Object { $_.title -eq $title }
}

プロパティ titleurl のみを含む $topThree 変数の出力をグリッド ビューで確認します。

$topThree | Select "title", "uri" | Out-GridView

出力:

検索クエリが完了した後のフォーマットされた結果のスクリーンショット。

$topThree 変数には、データテーブル内の行のすべての情報が含まれます。 たとえば、content プロパティには、元のドキュメントの形式が含まれます。 配列の最初の項目のインデックスを指定するには、[0] を使います。

$topThree[0].content

ドキュメント全体を表示します (このページの出力スニペットでは切り捨てられています)。

---
external help file: Microsoft.PowerShell.Commands.Management.dll-Help.xml
Locale: en-US
Module Name: Microsoft.PowerShell.Management
ms.date: 07/03/2023
online version: https://learn.microsoft.com/powershell/module/microsoft.powershell.management/get-process?view=powershell-7.4&WT.mc_id=ps-gethelp
schema: 2.0.0
title: Get-Process
---

# Get-Process

## SYNOPSIS
Gets the processes that are running on the local computer.

## SYNTAX

### Name (Default)

Get-Process [[-Name] <String[]>] [-Module] [-FileVersionInfo] [<CommonParameters>]
# truncated example

最後に、データセットのクエリが必要になるたびに埋め込みを生成し直すのではなく、データをディスクに格納し、後でそれを再度呼び出すことができます。 次の例の DataTable オブジェクト型の WriteXML()ReadXML() メソッドを使うと、プロセスが簡単になります。 XML ファイルのスキーマでは、データテーブルに TableName が必要です。

<YOUR-FULL-FILE-PATH> を、XML ファイルの読み書きを行う完全なパスに置き換えます。 パスは、.xml で終わっている必要があります。

# Set DataTable name
$Datatable.TableName = "MyDataTable"

# Writing DataTable to XML
$Datatable.WriteXml("<YOUR-FULL-FILE-PATH>", [System.Data.XmlWriteMode]::WriteSchema)

# Reading XML back to DataTable
$newDatatable = New-Object System.Data.DataTable
$newDatatable.ReadXml("<YOUR-FULL-FILE-PATH>")

データを再利用するときは、新しい各検索文字列のベクトルを取得する必要があります (ただし、データテーブル全体は必要ありません)。 学習のための演習として、検索文字列をパラメーターにすることで Invoke-RestMethod コマンドを自動化する PowerShell スクリプトを作成してみてください。

このアプローチにより、ナレッジ ベース内のドキュメントを横断的に検索するメカニズムとして埋め込みを利用することができます。 ユーザーは、上位の検索結果を取得して、最初のクエリのきっかけとなった下流のタスクに使用することができます。

リソースをクリーンアップする

このチュートリアルを実施するためだけに Azure OpenAI リソースを作成し、Azure OpenAI リソースをクリーンアップして削除する場合は、デプロイしたモデルを削除し、テスト リソース専用のリソースまたは関連するリソース グループを削除する必要があります。 リソース グループを削除すると、それに関連付けられている他のリソースも削除されます。

次のステップ

Azure OpenAI のモデルの詳細をご覧ください。