Samouczek: eksplorowanie osadzania i wyszukiwania dokumentów w usłudze Azure OpenAI Service

Ten samouczek przeprowadzi Cię przez proces korzystania z interfejsu API osadzania usługi Azure OpenAI w celu przeprowadzenia wyszukiwania dokumentów, w którym wykonasz zapytanie dotyczące baza wiedzy w celu znalezienia najbardziej odpowiedniego dokumentu.

Z tego samouczka dowiesz się, jak wykonywać następujące czynności:

  • Zainstaluj usługę Azure OpenAI.
  • Pobierz przykładowy zestaw danych i przygotuj go do analizy.
  • Utwórz zmienne środowiskowe dla punktu końcowego zasobów i klucza interfejsu API.
  • Korzystanie z modelu osadzania tekstu ada-002 (wersja 2)
  • Użyj podobieństwa cosinus, aby sklasyfikować wyniki wyszukiwania.

Wymagania wstępne

  • Subskrypcja platformy Azure — utwórz bezpłatnie
  • Dostęp jest udzielany usłudze Azure OpenAI w żądanej subskrypcji platformy Azure. Obecnie dostęp do tej usługi jest udzielany tylko przez aplikację. Możesz ubiegać się o dostęp do usługi Azure OpenAI, wypełniając formularz pod adresem https://aka.ms/oai/access. Otwórz problem w tym repozytorium, aby skontaktować się z nami, jeśli masz problem.
  • Zasób usługi Azure OpenAI z wdrożonym modelem osadzania tekstu ada-002 (wersja 2). Ten model jest obecnie dostępny tylko w niektórych regionach. Jeśli nie masz zasobu, proces tworzenia zasobu jest udokumentowany w naszym przewodniku wdrażania zasobów.
  • Środowisko Python w wersji 3.8 lub nowszej
  • Następujące biblioteki języka Python: openai, num2words, matplotlib, plotly, scipy, scikit-learn, pandas, tiktoken.
  • Notesy programu Jupyter

Konfiguruj

Biblioteki języka Python

Jeśli jeszcze tego nie zrobiono, musisz zainstalować następujące biblioteki:

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

Pobieranie zestawu danych BillSum

BillSum jest zestawem danych Stany Zjednoczone Kongresu i Kalifornii ustaw stanowych. W celach ilustracyjnych przyjrzymy się tylko rachunkom amerykańskim. Korpus składa się z rachunków z 103-115(1993-2018) sesji Kongresu. Dane zostały podzielone na 18 949 rachunków za pociągi i 3269 rachunków testowych. Korpus BillSum koncentruje się na średniej długości prawodawstwa z 5000 do 20 000 znaków długości. Więcej informacji na temat projektu i oryginalnego artykułu akademickiego, z którego pochodzi ten zestaw danych, można znaleźć w repozytorium GitHub projektu BillSum

W tym samouczku bill_sum_data.csv jest używany plik, który można pobrać z przykładowych danych usługi GitHub.

Możesz również pobrać przykładowe dane, uruchamiając następujące polecenie na komputerze lokalnym:

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

Pobieranie klucza i punktu końcowego

Aby pomyślnie wykonać wywołanie usługi Azure OpenAI, potrzebujesz punktu końcowegoi klucza.

Nazwa zmiennej Wartość
ENDPOINT Tę wartość można znaleźć w sekcji Klucze i punkt końcowy podczas badania zasobu z poziomu witryny Azure Portal. Alternatywnie możesz znaleźć wartość w widoku kodu placu zabaw>usługi Azure OpenAI Studio>. Przykładowy punkt końcowy to: https://docs-test-001.openai.azure.com/.
API-KEY Tę wartość można znaleźć w sekcji Klucze i punkt końcowy podczas badania zasobu z poziomu witryny Azure Portal. Możesz użyć wartości KEY1 lub KEY2.

Przejdź do zasobu w witrynie Azure Portal. Sekcję Klucze i punkt końcowy można znaleźć w sekcji Zarządzanie zasobami. Skopiuj punkt końcowy i klucz dostępu, ponieważ będzie potrzebny zarówno do uwierzytelniania wywołań interfejsu API. Możesz użyć wartości KEY1 lub KEY2. Zawsze posiadanie dwóch kluczy umożliwia bezpieczne obracanie i ponowne generowanie kluczy bez powodowania zakłóceń usługi.

Zrzut ekranu przedstawiający interfejs użytkownika przeglądu zasobu usługi Azure OpenAI w witrynie Azure Portal z lokalizacją punktu końcowego i kluczy dostępu w kolorze czerwonym.

Zmienne środowiskowe

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 
setx AZURE_OPENAI_ENDPOINT "REPLACE_WITH_YOUR_ENDPOINT_HERE" 

Po ustawieniu zmiennych środowiskowych może być konieczne zamknięcie i ponowne otwarcie notesów Jupyter lub dowolnego środowiska IDE, którego używasz, aby zmienne środowiskowe były dostępne. Zdecydowanie zalecamy korzystanie z notesów Jupyter Notebook, jeśli z jakiegoś powodu nie będzie trzeba modyfikować dowolnego kodu zwracającego ramkę print(dataframe_name) danych biblioteki pandas, a nie tylko wywoływać dataframe_name bezpośrednio, jak to często odbywa się na końcu bloku kodu.

Uruchom następujący kod w preferowanym środowisku IDE języka Python:

Importowanie bibliotek

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

Teraz musimy odczytać nasz plik CSV i utworzyć ramkę danych biblioteki pandas. Po utworzeniu początkowej ramki danych możemy wyświetlić zawartość tabeli, uruchamiając polecenie 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

Wyjście:

Zrzut ekranu przedstawiający wyniki początkowej tabeli ramki danych z pliku CSV.

Początkowa tabela zawiera więcej kolumn, niż potrzebujemy, utworzymy nową mniejszą ramkę danych o nazwie df_bills , która będzie zawierać tylko kolumny dla text, summaryi title.

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

Wyjście:

Zrzut ekranu przedstawiający mniejsze wyniki tabeli DataFrame z wyświetlanymi tylko kolumnami tekstowymi, podsumowania i tytułu.

Następnie przeprowadzimy czyszczenie lekkich danych przez usunięcie nadmiarowego odstępu i oczyszczenie interpunkcji w celu przygotowania danych do tokenizacji.

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

Teraz musimy usunąć wszelkie rachunki, które są zbyt długie dla limitu tokenu (tokeny 8192).

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

Uwaga

W takim przypadku wszystkie rachunki znajdują się w ramach limitu tokenu wejściowego modelu osadzania, ale możesz użyć powyższej techniki, aby usunąć wpisy, które w przeciwnym razie spowodują niepowodzenie osadzania. W przypadku przekroczenia limitu osadzania zawartości można również podzielić zawartość na mniejsze elementy, a następnie osadzić je pojedynczo.

Ponownie zbadamy df_bills.

df_bills

Wyjście:

Zrzut ekranu przedstawiający ramkę danych z nową kolumną o nazwie n_tokens.

Aby lepiej zrozumieć kolumnę n_tokens, a także sposób, w jaki tekst ostatecznie jest tokenizowany, warto uruchomić następujący kod:

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

W przypadku naszych dokumentacji celowo obcinamy dane wyjściowe, ale uruchomienie tego polecenia w środowisku zwróci pełny tekst z indeksu zero tokenizowanego na fragmenty. Widać, że w niektórych przypadkach całe słowo jest reprezentowane za pomocą jednego tokenu, podczas gdy w innych częściach wyrazów są podzielone na wiele tokenów.

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

Jeśli następnie sprawdzisz długość decode zmiennej, znajdziesz ją zgodną z pierwszą liczbą w kolumnie n_tokens.

len(decode)
1466

Teraz, gdy dowiesz się więcej o sposobie działania tokenizacji, możemy przejść do osadzania. Należy pamiętać, że jeszcze nie tokenizowaliśmy dokumentów. Kolumna n_tokens jest po prostu sposobem upewnienia się, że żadne dane przekazywane do modelu na potrzeby tokenizacji i osadzania przekraczają limit tokenu wejściowego 8192. Gdy przekażemy dokumenty do modelu osadzania, spowoduje to podzielenie dokumentów na tokeny podobne (choć niekoniecznie identyczne) do powyższych przykładów, a następnie przekonwertowanie tokenów na serię liczb zmiennoprzecinkowych, które będą dostępne za pośrednictwem wyszukiwania wektorowego. Te osadzania można przechowywać lokalnie lub w usłudze Azure Database w celu obsługi wyszukiwania wektorowego. W rezultacie każdy rachunek będzie miał własny wektor osadzania w nowej ada_v2 kolumnie po prawej stronie ramki danych.

W poniższym przykładzie wywołujemy model osadzania raz na każdy element, który chcemy osadzić. Podczas pracy z dużymi projektami osadzania można również przekazać modelowi tablicę danych wejściowych do osadzania, a nie jednego danych wejściowych naraz. Po przekazaniu modelu tablica danych wejściowych maksymalna liczba elementów wejściowych na wywołanie punktu końcowego osadzania wynosi 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

Wyjście:

Zrzut ekranu przedstawiający sformatowane wyniki z polecenia df_bills.

Po uruchomieniu poniższego bloku kodu wyszukiwania osadzimy zapytanie wyszukiwania "Czy mogę uzyskać informacje na temat przychodów podatkowych firmy kablowej?" z tym samym modelem osadzania tekstu ada-002 (wersja 2). Następnie znajdziemy najbliższy rachunek osadzony w nowo osadzonym tekście z zapytania sklasyfikowanego według podobieństwa cosinus.

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)

Dane wyjściowe:

Zrzut ekranu przedstawiający sformatowane wyniki res po uruchomieniu zapytania wyszukiwania.

Na koniec pokażemy najlepszy wynik z wyszukiwania dokumentów na podstawie zapytania użytkownika względem całego baza wiedzy. Zwraca to najwyższy wynik ustawy "Prawo podatnika do poglądu ustawy z 1993 roku". Ten dokument ma wynik podobieństwa cosinus o wartości 0,76 między zapytaniem a dokumentem:

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

Wymagania wstępne

  • Subskrypcja platformy Azure — utwórz bezpłatnie

  • Dostęp jest udzielany usłudze Azure OpenAI w żądanej subskrypcji platformy Azure.

    Obecnie dostęp do tej usługi jest udzielany tylko przez aplikację. Możesz ubiegać się o dostęp do usługi Azure OpenAI, wypełniając formularz pod adresem https://aka.ms/oai/access. Otwórz problem w tym repozytorium, aby skontaktować się z nami, jeśli masz problem.

  • Zasób usługi Azure OpenAI z wdrożonym modelem osadzania tekstu ada-002 (wersja 2).

    Ten model jest obecnie dostępny tylko w niektórych regionach. Jeśli nie masz zasobu, proces tworzenia zasobu jest udokumentowany w naszym przewodniku wdrażania zasobów.

  • PowerShell 7.4

Uwaga

Wiele przykładów w tym samouczku ponownie używa zmiennych od kroku do kroku. Zachowaj tę samą sesję terminalu otwartą przez cały czas. Jeśli zmienne ustawione w poprzednim kroku zostaną utracone z powodu zamknięcia terminalu, należy zacząć ponownie od początku.

Pobieranie klucza i punktu końcowego

Aby pomyślnie wykonać wywołanie usługi Azure OpenAI, potrzebujesz punktu końcowegoi klucza.

Nazwa zmiennej Wartość
ENDPOINT Tę wartość można znaleźć w sekcji Klucze i punkt końcowy podczas badania zasobu z poziomu witryny Azure Portal. Alternatywnie możesz znaleźć wartość w widoku kodu placu zabaw>usługi Azure OpenAI Studio>. Przykładowy punkt końcowy to: https://docs-test-001.openai.azure.com/.
API-KEY Tę wartość można znaleźć w sekcji Klucze i punkt końcowy podczas badania zasobu z poziomu witryny Azure Portal. Możesz użyć wartości KEY1 lub KEY2.

Przejdź do zasobu w witrynie Azure Portal. Sekcję Klucze i punkt końcowy można znaleźć w sekcji Zarządzanie zasobami. Skopiuj punkt końcowy i klucz dostępu, ponieważ będzie potrzebny zarówno do uwierzytelniania wywołań interfejsu API. Możesz użyć wartości KEY1 lub KEY2. Zawsze posiadanie dwóch kluczy umożliwia bezpieczne obracanie i ponowne generowanie kluczy bez powodowania zakłóceń usługi.

Zrzut ekranu przedstawiający interfejs użytkownika przeglądu zasobu usługi Azure OpenAI w witrynie Azure Portal z lokalizacją punktu końcowego i kluczy dostępu w kolorze czerwonym.

Tworzenie i przypisywanie trwałych zmiennych środowiskowych dla klucza i punktu końcowego.

Zmienne środowiskowe

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 
setx AZURE_OPENAI_ENDPOINT "REPLACE_WITH_YOUR_ENDPOINT_HERE" 

Na potrzeby tego samouczka używamy dokumentacji referencyjnej programu PowerShell 7.4 jako dobrze znanego i bezpiecznego przykładowego zestawu danych. Alternatywnie możesz zapoznać się z przykładowymi zestawami danych narzędzi Microsoft Research Tools .

Utwórz folder, w którym chcesz przechowywać projekt. Ustaw lokalizację na folder projektu. Pobierz zestaw danych na komputer lokalny przy użyciu Invoke-WebRequest polecenia , a następnie rozwiń archiwum. Na koniec ustaw lokalizację na podfolder zawierający informacje referencyjne dla programu PowerShell w wersji 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/'

W tym samouczku pracujemy z dużą ilością danych, dlatego do wydajnej wydajności używamy obiektu tabeli danych platformy .NET. Tabela danych zawiera tytuł kolumn, zawartość, przygotowanie, identyfikator URI, plik i wektory. Kolumna tytułu jest kluczem podstawowym.

W następnym kroku załadujemy zawartość każdego pliku markdown do tabeli danych. Używamy również operatora programu PowerShell -match do przechwytywania znanych wierszy tekstu title: i online version:, i przechowywania ich w odrębnych kolumnach. Niektóre pliki nie zawierają wierszy metadanych tekstu, ale ponieważ są to strony przeglądu, a nie szczegółowe dokumenty referencyjne, wykluczamy je z tabeli datatable.

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

Wyświetl dane przy użyciu out-gridview polecenia (niedostępne w usłudze Cloud Shell).

$Datatable | out-gridview

Dane wyjściowe:

Zrzut ekranu przedstawiający początkowe wyniki tabeli DataTable.

Następnie wykonaj czyszczenie lekkich danych, usuwając dodatkowe znaki, puste miejsce i inne notacje dokumentów, aby przygotować dane do tokenizacji. Przykładowa funkcja Invoke-DocPrep pokazuje, jak używać operatora programu PowerShell -replace do iterowania po liście znaków, które chcesz usunąć z zawartości.

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

Po utworzeniu Invoke-DocPrep funkcji użyj ForEach-Object polecenia , aby przechowywać przygotowaną zawartość w kolumnie przygotowywania dla wszystkich wierszy w tabeli danych. Używamy nowej kolumny, więc oryginalne formatowanie jest dostępne, jeśli chcemy go pobrać później.

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

Ponownie wyświetl tabelę danych, aby zobaczyć zmianę.

$Datatable | out-gridview

Gdy przekazujemy dokumenty do modelu osadzania, koduje on dokumenty do tokenów, a następnie zwraca serię liczb zmiennoprzecinkowych do użycia w wyszukiwaniu podobieństwa cosinus. Te osadzania można przechowywać lokalnie lub w usłudze, takiej jak wyszukiwanie wektorowe w usłudze Azure AI Search. Każdy dokument ma swój własny wektor osadzania w nowej kolumnie wektorów .

Następny przykład wykonuje pętlę dla każdego wiersza w tabeli danych, pobiera wektory dla wstępnie przetworzonej zawartości i zapisuje je w kolumnie wektorów . Usługa OpenAI ogranicza częste żądania, więc w przykładzie znajduje się wykładne wycofywanie zgodnie z sugestią dokumentacji.

Po zakończeniu działania skryptu każdy wiersz powinien mieć rozdzielaną przecinkami listę 1536 wektorów dla każdego dokumentu. Jeśli wystąpi błąd, a kod stanu to 400, ścieżka pliku, tytuł i kod błędu zostaną dodane do zmiennej o nazwie $errorDocs na potrzeby rozwiązywania problemów. Najczęstszy błąd występuje, gdy liczba tokenów jest większa niż limit monitu dla modelu.

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

Masz teraz lokalną tabelę bazy danych w pamięci zawierającą dokumentację referencyjną programu PowerShell 7.4.

Na podstawie ciągu wyszukiwania musimy obliczyć inny zestaw wektorów, aby program PowerShell mógł sklasyfikować każdy dokument według podobieństwa.

W następnym przykładzie wektory są pobierane dla ciągu get a list of running processeswyszukiwania .

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

Na koniec kolejna przykładowa funkcja, która pożycza przykład z przykładowego skryptu Measure-VectorSimilarity napisanego przez Lee Holmes, wykonuje obliczenia podobieństwa cosinus, a następnie klasyfikuje każdy wiersz w tabeli danych.

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

Polecenia w następnej pętli przykładu przechodzą przez wszystkie wiersze w $Datatable obiekcie i obliczają podobieństwo cosinus do ciągu wyszukiwania. Wyniki są sortowane, a trzy pierwsze wyniki są przechowywane w zmiennej o nazwie $topThree. Przykład nie zwraca danych wyjściowych.

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

Przejrzyj dane wyjściowe zmiennej $topThree z tylko właściwościami tytułu i adresu URL w widoku gridview.

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

Wyjście:

Zrzut ekranu przedstawiający sformatowane wyniki po zakończeniu zapytania wyszukiwania.

Zmienna $topThree zawiera wszystkie informacje z wierszy w tabeli danych. Na przykład właściwość content zawiera oryginalny format dokumentu. Użyj polecenia [0] , aby zaindeksować do pierwszego elementu w tablicy.

$topThree[0].content

Wyświetl pełny dokument (obcięty we fragmencie kodu wyjściowego dla tej strony).

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

Na koniec zamiast ponownie wygenerować osadzanie za każdym razem, gdy musisz wykonać zapytanie dotyczące zestawu danych, możesz zapisać dane na dysku i odwołać je w przyszłości. Metody WriteXML()i ReadXML() typów obiektów DataTable w następnym przykładzie upraszczają proces. Schemat pliku XML wymaga, aby tabela datatable miała tabelę TableName.

Zastąp <YOUR-FULL-FILE-PATH> ciąg pełną ścieżką, w której chcesz zapisać i odczytać plik XML. Ścieżka powinna kończyć się ciągiem .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>")

Podczas ponownego używania danych należy pobrać wektory każdego nowego ciągu wyszukiwania (ale nie całą tabelę danych). W ramach ćwiczenia szkoleniowego spróbuj utworzyć skrypt programu PowerShell, aby zautomatyzować Invoke-RestMethod polecenie przy użyciu ciągu wyszukiwania jako parametru.

Korzystając z tego podejścia, można użyć osadzania jako mechanizmu wyszukiwania w dokumentach w baza wiedzy. Użytkownik może następnie pobrać górny wynik wyszukiwania i użyć go do zadania podrzędnego, które wyświetliło monit o początkowe zapytanie.

Czyszczenie zasobów

Jeśli utworzono zasób usługi Azure OpenAI wyłącznie na potrzeby ukończenia tego samouczka i chcesz wyczyścić i usunąć zasób usługi Azure OpenAI, musisz usunąć wdrożone modele, a następnie usunąć zasób lub skojarzona grupa zasobów, jeśli jest ona przeznaczona dla zasobu testowego. Usunięcie grupy zasobów powoduje również usunięcie wszelkich innych skojarzonych z nią zasobów.

Następne kroki

Dowiedz się więcej o modelach usługi Azure OpenAI: