Tutorial: Erkunden von Azure OpenAI Service-Einbettungen und Dokumentsuche

In diesem Tutorial erfahren Sie, wie Sie mithilfe der Azure OpenAI-Einbettungs-API eine Dokumentsuche durchführen, bei der Sie eine Wissensdatenbank abfragen, um das relevanteste Dokument zu finden.

In diesem Tutorial lernen Sie Folgendes:

  • Installieren Sie Azure OpenAI.
  • Laden Sie ein Beispieldataset herunter, und bereiten Sie es für die Analyse vor.
  • Erstellen Sie Umgebungsvariablen für Ihren Ressourcenendpunkt und den API-Schlüssel.
  • Verwenden des Modells text-embedding-ada-002 (Version 2)
  • Verwenden Sie Kosinusähnlichkeit, um die Suchergebnisse zu priorisieren.

Voraussetzungen

  • Azure-Abonnement: Kostenloses Azure-Konto
  • Zugriff auf Azure OpenAI im gewünschten Azure-Abonnement gewährt. Derzeit wird der Zugriff auf diesen Dienst nur auf Antrag gewährt. Sie können den Zugriff auf Azure OpenAI beantragen, indem Sie das Formular unter https://aka.ms/oai/access ausfüllen. Öffnen Sie ein Problem in diesem Repository, um uns bei einem Problem zu kontaktieren.
  • Eine Azure OpenAI-Ressource mit dem implementierten Modell text-embedding-ada-002 (Version 2). Dieses Modell ist derzeit nur in bestimmten Regionen verfügbar. Wenn Sie noch nicht über eine Ressource verfügen, finden Sie das Verfahren zum Erstellen einer Ressource im Leitfaden zur Ressourcenbereitstellung.
  • Python 3.8 oder eine höhere Version
  • Die folgenden Python-Bibliotheken: openai, num2words, matplotlib, plotly, scipy, scikit-learn,pandas, tiktoken.
  • Jupyter-Notebooks

Einrichten

Python-Bibliotheken

Falls noch nicht geschehen, müssen Sie die folgenden Bibliotheken installieren:

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

Herunterladen des BillSum-Datasets

BillSum ist ein Dataset mit Rechnungen des USA-Kongresses und des Bundesstaats Kalifornien. Der Einfachheit halber werden hier nur die US-Rechnungen verwendet. Das Dataset enthält Rechnungen der Kongresse 103–115 (1993–2018). Die Daten wurden in 18.949 Trainingsrechnungen und 3.269 Testrechnungen aufgeteilt. Das BillSum-Dataset enthält hauptsächlich Rechtsvorschriften mittlerer Länge von 5.000 bis 20.000 Zeichen. Weitere Informationen zum Projekt und zur ursprünglichen akademischen Arbeit, aus der dieses Dataset abgeleitet wurde, finden Sie im GitHub-Repository des BillSum-Projekts.

In diesem Tutorial wird die Datei bill_sum_data.csv verwendet, die aus den GitHub-Beispieldaten heruntergeladen werden kann.

Sie können die Beispieldaten auch herunterladen, indem Sie den folgenden Befehl auf Ihrem lokalen Computer ausführen:

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

Abrufen von Schlüssel und Endpunkt

Für erfolgreiche Azure OpenAI-Aufrufe benötigen Sie einen Endpunkt und einen Schlüssel.

Variablenname Wert
ENDPOINT Diesen Wert finden Sie im Abschnitt Schlüssel und Endpunkt, wenn Sie die Ressource über das Azure-Portal untersuchen. Alternativ dazu finden Sie den Wert auch unter Azure OpenAI Studio>Playground>Codeansicht. Ein Beispielendpunkt ist https://docs-test-001.openai.azure.com/.
API-KEY Diesen Wert finden Sie im Abschnitt Schlüssel und Endpunkt, wenn Sie die Ressource über das Azure-Portal untersuchen. Sie können KEY1 oder KEY2 verwenden.

Wechseln Sie zu Ihrer Ressource im Azure-Portal. Den Abschnitt Schlüssel und Endpunkt finden Sie im Abschnitt Ressourcenverwaltung. Kopieren Sie die Werte für Endpunkt und Zugriffsschlüssel, da Sie beide für die Authentifizierung Ihrer API-Aufrufe benötigen. Sie können KEY1 oder KEY2 verwenden. Wenn Sie jederzeit zwei Schlüssel zur Verfügung haben, können Sie die Schlüssel auf sichere Weise rotieren und neu generieren, ohne Dienstunterbrechungen zu verursachen.

Screenshot der Benutzeroberfläche mit der Übersicht über eine Azure OpenAI-Ressource im Azure-Portal, in der die Speicherorte für Endpunkt und Zugriffsschlüssel rot umrandet sind

Umgebungsvariablen

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 
setx AZURE_OPENAI_ENDPOINT "REPLACE_WITH_YOUR_ENDPOINT_HERE" 

Nach dem Festlegen der Umgebungsvariablen müssen Sie möglicherweise Jupyter Notebooks oder die von Ihnen bevorzugte IDE schließen und erneut öffnen, damit auf die Umgebungsvariablen zugegriffen werden kann. Es wird zwar dringend empfohlen, Jupyter Notebooks zu verwenden, aber wenn Sie dies aus irgendeinem Grund nicht können, müssen Sie jeglichen Code, der einen pandas-DataFrame zurückgibt, mithilfe von print(dataframe_name) ändern, anstatt lediglich dataframe_name direkt aufzurufen, wie dies häufig am Ende eines Codeblocks geschieht.

Führen Sie den folgenden Code in Ihrer bevorzugten Python-IDE aus:

Importbibliotheken

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

Nun müssen Sie die CSV-Datei lesen und einen pandas-DataFrame erstellen. Nachdem der anfängliche DataFrame erstellt wurde, können Sie den Inhalt der Tabelle anzeigen, indem Sie ausführen 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

Ausgabe:

Screenshot der Ergebnisse der ersten DataFrame-Tabelle aus der CSV-Datei

Die erste Tabelle enthält mehr Spalten als erforderlich. Sie erstellen daher einen neuen kleineren DataFrame namens df_bills, der nur die Spalten für text, summary und title enthält.

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

Ausgabe:

Screenshot der Ergebnisse der kleineren DataFrame-Tabelle, der nur die Spalten „text“, „summary“ und „title“ enthält

Als Nächstes führen Sie eine einfache Datenbereinigung durch, um redundanten Leerraum zu entfernen und die Interpunktion zu bereinigen und somit die Daten für die Tokenisierung vorzubereiten.

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))

Nun müssen Sie alle Rechnungen entfernen, die für das Tokenlimit (~8192 Token) zu lang sind.

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

Hinweis

In diesem Fall liegen alle Rechnungen unter dem Eingabetokenlimit des Einbettungsmodells. Sie können jedoch die oben genannte Methode verwenden, um Einträge zu entfernen, die andernfalls dazu führen würden, dass die Einbettung fehlschlägt. Wenn Sie mit Inhalten konfrontiert werden, die das Einbettungslimit überschreiten, können Sie den Inhalt auch in kleinere Blöcke unterteilen und diese dann einzeln einbetten.

Untersuchen Sie df_bills noch einmal.

df_bills

Ausgabe:

Screenshot des DataFrames mit der neuen Spalte „n_tokens“

Um die Spalte „n_tokens“ besser zu verstehen und um zu erfahren, wie Text letztendlich tokenisiert wird, kann es hilfreich sein, den folgenden Code auszuführen:

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

Für die Dokumente wurde die Ausgabe absichtlich abgeschnitten, wenn Sie diesen Befehl in Ihrer Umgebung ausführen, wird jedoch der vollständige Text ab Index null zurückgegeben, der in Blöcken tokenisiert wurde. Sie können sehen, dass in einigen Fällen ein ganzes Wort von einem einzelnen Token dargestellt wird, während in anderen Teilen Wörter auf mehrere Token aufgeteilt werden.

[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',

Wenn Sie dann die Länge der Variable decode überprüfen, werden Sie feststellen, dass sie mit der ersten Zahl in der Spalte „n_tokens“ übereinstimmt.

len(decode)
1466

Nachdem Sie nun mehr über die Funktionsweise der Tokenisierung erfahren haben, können Sie mit der Einbettung fortfahren. Es ist wichtig zu beachten, dass die Dokumente noch nicht tatsächlich tokenisiert wurden. Die Spalte n_tokens ist einfach eine Möglichkeit, um sicherzustellen, dass keine der Daten, die wir zur Tokenisierung und Einbettung an das Modell übergeben, das Eingabetokenlimit von 8.192 überschreitet. Wenn wir die Dokumente an das Einbettungsmodell übergeben, werden die Dokumente in Token unterteilt, die den obigen Beispielen ähneln (wenn auch nicht unbedingt damit identisch sind), woraufhin dann die Token in eine Reihe von Gleitkommazahlen konvertiert werden, auf die über die Vektorsuche zugegriffen werden kann. Diese Einbettungen können lokal oder in einer Azure-Datenbank gespeichert werden, um die Vektorsuche zu unterstützen. Daher verfügt jeder Rechnung über einen eigenen entsprechenden Einbettungsvektor in der neuen Spalte ada_v2 auf der rechten Seite des DataFrames.

Im folgenden Beispiel rufen Sie das Einbettungsmodell einmal pro Element auf, das Sie einbetten möchten. Beim Arbeiten mit großen Einbettungsprojekten können Sie das Modell alternativ an ein Array von einzubettenden Eingaben übergeben, anstatt eine Eingabe nach der anderen. Wenn Sie dem Modell ein Array von Eingaben übergeben, beträgt die maximale Anzahl von Eingaben pro Aufruf des Einbettungsendpunkts 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

Ausgabe:

Screenshot der formatierten Ergebnisse vom Befehl „df_bills“

Während wir den folgenden Suchcodeblock ausführen, betten wir die Suchabfrage „Kann ich Informationen zu Steuereinnahmen des Kabelunternehmens abrufen?“ mit demselben text-embedding-ada-002 (Version 2)-Modell ein. Als Nächstes suchen wir die dem neu eingebetteten Text nächstgelegene Rechnungseinbettung aus unserer Abfrage, sortiert nach Kosinusähnlichkeit.

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)

Ausgabe:

Screenshot der formatierten Ergebnisse von res, nachdem die Suchabfrage ausgeführt wurde

Zum Schluss zeigen Sie das beste Ergebnis der Dokumentsuche basierend auf der Benutzerabfrage für die gesamte Wissensdatenbank an. Damit wird das beste Ergebnis („Taxpayer's Right to View Act of 1993“) zurückgegeben. Dieses Dokument weist einen Kosinusähnlichkeitsscore von 0,76 zwischen Abfrage und Dokument auf:

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."

Voraussetzungen

  • Azure-Abonnement: Kostenloses Azure-Konto

  • Zugriff auf Azure OpenAI im gewünschten Azure-Abonnement gewährt.

    Derzeit wird der Zugriff auf diesen Dienst nur auf Antrag gewährt. Sie können den Zugriff auf Azure OpenAI beantragen, indem Sie das Formular unter https://aka.ms/oai/access ausfüllen. Öffnen Sie ein Problem in diesem Repository, um uns bei einem Problem zu kontaktieren.

  • Eine Azure OpenAI-Ressource mit dem implementierten Modell text-embedding-ada-002 (Version 2).

    Dieses Modell ist derzeit nur in bestimmten Regionen verfügbar. Wenn Sie noch nicht über eine Ressource verfügen, finden Sie das Verfahren zum Erstellen einer Ressource im Leitfaden zur Ressourcenbereitstellung.

  • PowerShell 7.4

Hinweis

Viele Beispiele in diesem Tutorial verwenden Variablen aus der Schritt-für-Schritt-Anleitung wieder. Lassen Sie die gleiche Terminalsitzung die ganze Zeit geöffnet. Wenn in einem vorherigen Schritt festgelegte Variablen verloren gehen, weil das Terminal geschlossen wird, müssen Sie wieder von vorn beginnen.

Abrufen von Schlüssel und Endpunkt

Für erfolgreiche Azure OpenAI-Aufrufe benötigen Sie einen Endpunkt und einen Schlüssel.

Variablenname Wert
ENDPOINT Diesen Wert finden Sie im Abschnitt Schlüssel und Endpunkt, wenn Sie die Ressource über das Azure-Portal untersuchen. Alternativ dazu finden Sie den Wert auch unter Azure OpenAI Studio>Playground>Codeansicht. Ein Beispielendpunkt ist https://docs-test-001.openai.azure.com/.
API-KEY Diesen Wert finden Sie im Abschnitt Schlüssel und Endpunkt, wenn Sie die Ressource über das Azure-Portal untersuchen. Sie können KEY1 oder KEY2 verwenden.

Wechseln Sie zu Ihrer Ressource im Azure-Portal. Den Abschnitt Schlüssel und Endpunkt finden Sie im Abschnitt Ressourcenverwaltung. Kopieren Sie die Werte für Endpunkt und Zugriffsschlüssel, da Sie beide für die Authentifizierung Ihrer API-Aufrufe benötigen. Sie können KEY1 oder KEY2 verwenden. Wenn Sie jederzeit zwei Schlüssel zur Verfügung haben, können Sie die Schlüssel auf sichere Weise rotieren und neu generieren, ohne Dienstunterbrechungen zu verursachen.

Screenshot der Benutzeroberfläche mit der Übersicht über eine Azure OpenAI-Ressource im Azure-Portal, in der die Speicherorte für Endpunkt und Zugriffsschlüssel rot umrandet sind

Erstellen und Zuweisen von beständigen Umgebungsvariablen für Ihren Schlüssel und Endpunkt.

Umgebungsvariablen

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 
setx AZURE_OPENAI_ENDPOINT "REPLACE_WITH_YOUR_ENDPOINT_HERE" 

In diesem Tutorial verwenden wir die Referenzdokumentation für PowerShell 7.4 als bekanntes und sicheres Beispieldataset. Alternativ können Sie die Beispieldatasets für Microsoft Research-Tools erkunden.

Erstellen Sie einen Ordner, in dem Sie Ihr Projekt speichern möchten. Legen Sie Projektordner als Speicherort fest. Laden Sie das Dataset mit dem Befehl Invoke-WebRequest auf Ihren lokalen Computer herunter, und erweitern Sie dann das Archiv. Legen Sie zuletzt Ihren Speicherort auf den Unterordner fest, der Referenzinformationen für PowerShell-Version 7.4 enthält.

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/'

In diesem Tutorial arbeiten wir mit einer großen Menge an Daten, daher verwenden wir ein .NET-Datentabellenobjekt, um eine effiziente Leistung zu erzielen. Die Datentabelle enthält die Spalten title, content, prep, uri, file und vectors. Die Spalte title ist der Primärschlüssel.

Im nächsten Schritt laden wir den Inhalt jeder Markdowndatei in die Datentabelle. Außerdem verwenden wir den PowerShell-Operator -match, um die bekannten Textzeilen title: und online version: zu erfassen und in unterschiedlichen Spalten zu speichern. Einige der Dateien enthalten nicht die Metadatentextzeilen, aber da sie Übersichtsseiten und keine detaillierten Referenzdokumente sind, schließen wir sie aus der Datentabelle aus.

# 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)
    }
}

Zeigen Sie die Daten mithilfe des Befehls out-gridview an (in Cloud Shell nicht verfügbar).

$Datatable | out-gridview

Ausgabe:

Screenshot der ersten DataTable-Ergebnisse.

Führen Sie als Nächstes eine einfache Datenreinigung durch Entfernen von zusätzlichen Zeichen, Leerzeichen und anderen Dokumentnotationen aus, um die Daten für die Tokenisierung vorzubereiten. Die Beispielfunktion Invoke-DocPrep veranschaulicht die Verwendung des PowerShell-Operators -replace zum Durchlaufen einer Liste von Zeichen, die Sie aus dem Inhalt entfernen möchten.

# 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
}

Nachdem Sie die Invoke-DocPrep-Funktion erstellt haben, verwenden Sie den Befehl ForEach-Object, um vorbereitete Inhalte in der Spalte prep für alle Zeilen in der Datentabelle zu speichern. Wir verwenden eine neue Spalte, damit die ursprüngliche Formatierung verfügbar ist, wenn wir sie später abrufen möchten.

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

Zeigen Sie die Datentabelle erneut an, um die Änderung zu sehen.

$Datatable | out-gridview

Wenn wir die Dokumente an das Einbettungsmodell übergeben, codiert es die Dokumente in Token und gibt dann eine Reihe von Gleitkommazahlen zurück, die in einer Suche nach der Kosinusähnlichkeit verwendet werden sollen. Diese Einbettungen können lokal oder in einem Dienst wie Vektorsuche in Azure KI-Suche gespeichert werden. Jedes Dokument verfügt über einen eigenen Einbettungsvektor in den neuen Spalte vectors.

Das nächste Beispiel durchläuft jede Zeile in der Datentabelle, ruft die Vektoren für den vorverarbeiteten Inhalt ab und speichert sie in Spalte vectors. Der OpenAI-Dienst drosselt häufige Anforderungen, sodass das Beispiel ein exponentielles Backoff enthält, wie in der Dokumentation vorgeschlagen.

Nach Abschluss des Skripts sollte jede Zeile eine durch Trennzeichen getrennte Liste mit 1.536 Vektoren für jedes Dokument aufweisen. Wenn ein Fehler auftritt und der Statuscode 400 lautet, werden zur Problembehandlung der Dateipfad, der Titel und der Fehlercode einer Variablen namens $errorDocs hinzugefügt. Am häufigsten tritt ein Fehler auf, wenn die Tokenanzahl den Prompt-Grenzwert für das Modell überschreitet.

# 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."
}

Sie verfügen jetzt über eine lokale In-Memory Database-Tabelle mit PowerShell 7.4-Referenzdokumenten.

Basierend auf einer Suchzeichenfolge müssen wir weitere Vektoren berechnen, damit PowerShell jedes Dokument nach Ähnlichkeit bewerten kann.

Im nächsten Beispiel werden Vektoren für die Suchzeichenfolge get a list of running processes abgerufen.

$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 ','

Schließlich führt die nächste Beispielfunktion, die ein Beispiel aus dem von Lee Holmes geschriebenen Beispielskript Measure-VectorSimilarity entlehnt, eine Berechnung der Kosinusähnlichkeit durch und weist dann jeder Zeile in der Datentabelle einen Rang zu.

# 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)
}

Die Befehle im nächsten Beispiel durchlaufen alle Zeilen in $Datatable und berechnen die Kosinusähnlichkeit mit der Suchzeichenfolge. Die Ergebnisse werden sortiert, und die ersten drei Ergebnisse werden in einer Variablen mit dem Namen $topThree gespeichert. Das Beispiel gibt keine Ausgabe zurück.

# 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 }
}

Überprüfen Sie die Ausgabe der Variablen $topThree, die nur die Eigenschaften title und url enthält, in GridView.

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

Ausgabe:

Screenshot der formatierten Ergebnisse von res, nachdem die Suchabfrage ausgeführt wurde.

Die Variable $topThree enthält alle Informationen aus den Zeilen in der Datentabelle. Beispielsweise enthält die Eigenschaft content das ursprüngliche Dokumentformat. Verwenden Sie [0] für die Indizierung im ersten Element im Array.

$topThree[0].content

Zeigen Sie das vollständige Dokument an (im Ausgabeausschnitt auf dieser Seite abgeschnitten).

---
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

Statt die Einbettungen jedes Mal neu zu generieren, wenn Sie das Dataset abfragen müssen, können Sie die Daten auf einem Datenträger speichern und in Zukunft abrufen. Die Methoden WriteXML() und ReadXML() der DataTable-Objekttypen im nächsten Beispiel vereinfachen den Prozess. Das Schema der XML-Datei erfordert, dass die Datentabelle über TableName verfügt.

Ersetzen Sie <YOUR-FULL-FILE-PATH> durch den vollständigen Pfad, in dem Sie die XML-Datei schreiben und lesen möchten. Der Pfad sollte mit .xml enden.

# 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>")

Wenn Sie die Daten wiederverwenden, müssen Sie die Vektoren jeder neuen Suchzeichenfolge abrufen (jedoch nicht die gesamte Datentabelle). Versuchen Sie als Übung, ein PowerShell-Skript zu erstellen, um den Befehl Invoke-RestMethod mit der Suchzeichenfolge als Parameter zu automatisieren.

Mit diesem Ansatz können Sie Einbettungen als Suchmechanismus für Dokumente in einer Wissensdatenbank verwenden. Benutzer*innen können dann das beste Suchergebnis ihrer ersten Abfrage für nachfolgende Aufgaben verwenden.

Bereinigen von Ressourcen

Wenn Sie nur für dieses Tutorial eine Azure OpenAI-Ressource erstellt haben und diese bereinigen und entfernen möchten, müssen Sie Ihre bereitgestellten Modelle löschen und dann die Ressource oder die zugeordnete Ressourcengruppe löschen, wenn diese nur Ihrer Testressource zugeordnet ist. Wenn Sie die Ressourcengruppe löschen, werden auch alle anderen Ressourcen gelöscht, die ihr zugeordnet sind.

Nächste Schritte

Weitere Informationen zu Azure OpenAI-Modellen: