Tutorial: explorar incorporações do Serviço OpenAI do Azure e a pesquisa de documentos

Este tutorial descreverá como usar a API de inserções do OpenAI do Azure para executar a pesquisa de documentos, na qual você consultará uma base de dados de conhecimento para encontrar o documento mais relevante.

Neste tutorial, você aprenderá a:

  • Instale o OpenAI do Azure.
  • Baixe um conjunto de dados de exemplo e prepare-o para análise.
  • Criar variáveis de ambiente para o ponto de extremidade de recursos e a chave de API.
  • Usar o modelo text-embedding-ada-002 (versão 2)
  • Use a similaridade cosseno para classificar os resultados da pesquisa.

Pré-requisitos

  • Uma assinatura do Azure – crie uma gratuitamente
  • Acesso permitido ao OpenAI do Azure na assinatura do Azure desejada. No momento, o acesso a esse serviço é permitido somente por aplicativo. Você pode solicitar acesso ao Serviço OpenAI do Azure preenchendo o formulário em https://aka.ms/oai/access. Abra um problema neste repositório para entrar em contato conosco se você tiver algum problema.
  • Um recurso do Azure OpenAI com o modelo text-embedding-ada-002 (Versão 2) implantado. Este modelo está atualmente disponível apenas em determinadas regiões. Se você não tiver um recurso, o processo de criação de um está documentado em nosso guia de implantação de recursos.
  • Python 3.8 ou versão posterior
  • As seguintes bibliotecas Python: openai, num2words, matplotlib, plotly, scipy, scikit-learn, panda, tiktoken.
  • Jupyter Notebooks

Configuração

Bibliotecas do Python

Caso ainda não tenha feito isso, você precisa instalar as seguintes bibliotecas:

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

Baixar o conjunto de dados BillSum

O BillSum é um conjunto de dados de projetos de lei do Congresso dos Estados Unidos e da Califórnia. Para fins de ilustração, vejamos apenas os projetos dos EUA. O corpus consiste em projetos de lei das sessões 103 a 115 (1993 a 2018) do Congresso. Os dados foram divididos em 18.949 projetos de treino e 3.269 projetos de teste. O corpus do BillSum se concentra na legislação de comprimento médio de 5.000 a 20.000 caracteres de comprimento. Mais informações sobre o projeto e o artigo acadêmico original do qual esse conjunto de dados é proveniente podem ser encontrados no repositório GitHub do projeto BillSum

Este tutorial usa o arquivo bill_sum_data.csv que pode ser baixado de nossos dados de amostra do GitHub.

Você também pode baixar os dados de amostra executando o seguinte comando em seu computador local:

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

Recuperar chave e ponto de extremidade

Para efetuar uma chamada com êxito no Serviço OpenAI do Azure, um ponto de extremidade e uma chave serão necessários.

Nome da variável Valor
ENDPOINT Esse valor pode ser encontrado na seção Chaves & Ponto de Extremidade ao examinar o recurso no portal do Azure. Como alternativa, você pode encontrar o valor em Estúdio OpenAI do Azure>Playground>Exibição de código. Um ponto de extremidade de exemplo é: https://docs-test-001.openai.azure.com/.
API-KEY Esse valor pode ser encontrado na seção Chaves & Ponto de Extremidade ao examinar o recurso no portal do Azure. Você pode usar KEY1 ou KEY2.

Acesse o seu recurso no portal do Azure. A seção Chaves e Ponto de Extremidade pode ser encontrada na seção Gerenciamento de Recursos. Copie o ponto de extremidade e as chave de acesso, pois você precisará de ambos para autenticar suas chamadas à API. Você pode usar KEY1 ou KEY2. Ter sempre duas chaves permite girar e regenerar chaves com segurança, sem causar interrupção de serviço.

Captura de tela da visão geral da interface do usuário de um recurso OpenAI do Azure no portal do Azure com o ponto de extremidade e a localização das chaves de acesso ressaltadas com um círculo vermelho.

Variáveis de ambiente

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 
setx AZURE_OPENAI_ENDPOINT "REPLACE_WITH_YOUR_ENDPOINT_HERE" 

Depois de definir as variáveis de ambiente, talvez seja necessário fechar e reabrir os notebooks Jupyter ou o IDE que estiver usando para que as variáveis de ambiente estejam acessíveis. Embora recomendemos enfaticamente o uso do Jupyter Notebooks, se por algum motivo não for possível, você precisará modificar qualquer código que esteja retornando um dataframe do pandas usando print(dataframe_name) em vez de apenas chamar o dataframe_name diretamente, como geralmente é feito no final de um bloco de código.

Execute o seguinte código no seu IDE do Python favorito:

Importar bibliotecas

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

Agora precisamos ler nosso arquivo csv e criar uma Estrutura de Dados pandas. Depois de criar o DataFrame inicial, podemos exibir o conteúdo da tabela executando 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

Saída:

Captura de tela dos resultados da tabela DataFrame inicial do arquivo csv.

A tabela inicial tem mais colunas do que o necessário, sendo assim, criaremos um DataFrame menor chamado df_bills que conterá apenas as colunas para text, summary e title.

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

Saída:

Captura de tela dos resultados da tabela DataFrame menores com apenas colunas de texto, resumo e título exibidas.

Em seguida, executaremos algumas limpezas de dados leves removendo o espaço em branco redundante e limpando a pontuação para preparar os dados para a tokenização.

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

Agora precisamos remover quaisquer contas que sejam muito longas para o limite de token (8192 tokens).

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

Observação

Neste caso, todas as contas estão abaixo do limite do token de entrada do modelo de inserção, mas você pode usar a técnica acima para remover entradas que de outra forma causariam falha na inserção. Quando confrontado com um conteúdo que excede o limite de inserção, você também pode dividir o conteúdo em partes menores e depois inserir um de cada vez.

Examinaremos novamente df_bills.

df_bills

Saída:

Captura de tela do DataFrame com uma nova coluna chamada n_tokens.

Para entender um pouco mais a coluna n_tokens e como o texto, em última análise, é tokenizado, pode ser útil executar o seguinte código:

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

Para nossos documentos, estamos intencionalmente truncando a saída, mas executando este comando em seu ambiente, retornaremos o texto completo do índice zero tokenizado em partes. Você pode ver que em alguns casos uma palavra inteira é representada com um único token, enquanto em outras partes das palavras são divididas entre vários tokens.

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

Se verificar o comprimento da variável decode, você verá que ela corresponde ao primeiro número na coluna n_tokens.

len(decode)
1466

Agora que entendemos mais sobre como funciona a tokenização, podemos seguir para a incorporação. É importante observar que ainda não fizemos a tokenização dos documentos. A coluna n_tokens é simplesmente uma forma de garantir que nenhum dos dados que passamos para o modelo para tokenização e inserção exceda o limite de token de entrada de 8.192. Quando passarmos os documentos para o modelo de inserções, ele dividirá os documentos em tokens semelhantes (embora não necessariamente idênticos) aos exemplos acima e então converterá os tokens para uma série de números de ponto flutuante que serão acessíveis via pesquisa vetorial. Essas inserções pode ser armazenadas localmente ou em um Banco de Dados do Azure para dar suporte à Busca em Vetores. Como resultado, cada conta terá seu próprio vetor de inserção correspondente na nova ada_v2 coluna do lado direito da Estrutura de Dados.

No exemplo abaixo, estamos chamando o modelo de inserção uma vez para cada item que desejamos inserir. Ao trabalhar com grandes projetos de incorporação, como alternativa, você pode transmitir ao modelo uma matriz de entradas a serem inseridas, em vez de uma entrada de cada vez. Quando você transmite uma matriz de entradas ao modelo, o número máximo de itens de entrada por chamada ao ponto de extremidade de incorporação é 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

Saída:

Captura de tela dos resultados formatados do comando df_bills.

Ao executarmos o bloco de código de pesquisa abaixo, vamos inserir a consulta de pesquisa "Posso obter informações sobre a receita fiscal da empresa de TV a cabo?" com o mesmo modelo text-embedding-ada-002 (Versão 2). Em seguida, encontraremos a conta mais próxima ao texto recém-inserido de nossa consulta classificada por similaridade cosseno.

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)

Saída:

Captura de tela dos resultados formatados de res após a execução da consulta de pesquisa.

Por fim, mostraremos o primeiro resultado dos documentos com base na consulta do usuário em toda base de dados de conhecimento. Isso retorna o primeiro resultado da "Lei do Direito de Exibição do Contribuinte de 1993". Este documento tem uma pontuação de similaridade cosseno de 0,76 entre a consulta e o documento:

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

Pré-requisitos

  • Uma assinatura do Azure – crie uma gratuitamente

  • Acesso permitido ao OpenAI do Azure na assinatura do Azure desejada.

    No momento, o acesso a esse serviço é permitido somente por aplicativo. Você pode solicitar acesso ao Serviço OpenAI do Azure preenchendo o formulário em https://aka.ms/oai/access. Abra um problema neste repositório para entrar em contato conosco se você tiver algum problema.

  • Um recurso do Azure OpenAI com o modelo text-embedding-ada-002 (Versão 2) implantado.

    Este modelo está atualmente disponível apenas em determinadas regiões. Se você não tiver um recurso, o processo de criação de um está documentado em nosso guia de implantação de recursos.

  • PowerShell 7.4

Observação

Muitos exemplos neste tutorial reutilizam variáveis entre as etapas. Mantenha a mesma sessão do terminal aberta durante todo o processo. Se as variáveis definidas em uma etapa anterior forem perdidas ao fechar o terminal, você deverá começar novamente desde o início.

Recuperar chave e ponto de extremidade

Para efetuar uma chamada com êxito no Serviço OpenAI do Azure, um ponto de extremidade e uma chave serão necessários.

Nome da variável Valor
ENDPOINT Esse valor pode ser encontrado na seção Chaves & Ponto de Extremidade ao examinar o recurso no portal do Azure. Como alternativa, você pode encontrar o valor em Estúdio OpenAI do Azure>Playground>Exibição de código. Um ponto de extremidade de exemplo é: https://docs-test-001.openai.azure.com/.
API-KEY Esse valor pode ser encontrado na seção Chaves & Ponto de Extremidade ao examinar o recurso no portal do Azure. Você pode usar KEY1 ou KEY2.

Acesse o seu recurso no portal do Azure. A seção Chaves e Ponto de Extremidade pode ser encontrada na seção Gerenciamento de Recursos. Copie o ponto de extremidade e as chave de acesso, pois você precisará de ambos para autenticar suas chamadas à API. Você pode usar KEY1 ou KEY2. Ter sempre duas chaves permite girar e regenerar chaves com segurança, sem causar interrupção de serviço.

Captura de tela da visão geral da interface do usuário de um recurso OpenAI do Azure no portal do Azure com o ponto de extremidade e a localização das chaves de acesso ressaltadas com um círculo vermelho.

Crie e atribua variáveis de ambiente persistentes para sua chave e ponto de extremidade.

Variáveis de ambiente

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 
setx AZURE_OPENAI_ENDPOINT "REPLACE_WITH_YOUR_ENDPOINT_HERE" 

Para este tutorial, usamos a documentação de referência do PowerShell 7.4 como um conjunto de dados de exemplo conhecido e seguro. Como alternativa, você pode optar por explorar os conjuntos de dados de exemplo das ferramentas do Microsoft Research.

Crie uma pasta onde deseja armazenar seu projeto. Defina seu local para a pasta do projeto. Baixe o conjunto de dados em seu computador local usando o comando Invoke-WebRequest e expanda o arquivo. Por fim, defina o local para a subpasta que contém informações de referência para o PowerShell versão 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/'

Estamos trabalhando com uma grande quantidade de dados neste tutorial, então usamos um objeto de tabela de dados .NET para um desempenho eficiente. A datatable possui as colunas title, content, prep, uri, file e vectors. A coluna title é a chave primária.

Na próxima etapa, carregamos o conteúdo de cada arquivo markdown na tabela de dados. Também usamos o operador -match do PowerShell para capturar linhas conhecidas de texto title: e online version: e armazená-las em colunas distintas. Alguns dos arquivos não contêm as linhas de metadados do texto, mas como são páginas de visão geral e não documentos de referência detalhados, os excluímos da 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)
    }
}

Exiba os dados usando o comando out-gridview (indisponível no Cloud Shell).

$Datatable | out-gridview

Saída:

Captura de tela dos resultados iniciais do DataTable.

Em seguida, realize uma limpeza leve nos dados, removendo caracteres extras, espaços vazios e outras notações de documento, para preparar os dados para a geração de tokens. A função de exemplo Invoke-DocPrep demonstra como usar o operador -replace do PowerShell para iterar por meio de uma lista de caracteres que você deseja remover do conteúdo.

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

Depois de criar a função Invoke-DocPrep, use o comando ForEach-Object para armazenar o conteúdo preparado na coluna prep, para todas as linhas na datatable. Estamos usando uma nova coluna para que a formatação original esteja disponível se quisermos recuperá-la mais tarde.

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

Visualize a datatable novamente para ver a mudança.

$Datatable | out-gridview

Quando passamos os documentos para o modelo de incorporações, ele codifica os documentos em tokens e, em seguida, retorna uma série de números de ponto flutuante a serem usados em uma pesquisa de similaridade de cosseno. Essas incorporações podem ser armazenadas localmente ou em um serviço como a Busca em Vetores na Pesquisa de IA do Azure. Cada documento tem seu próprio vetor de incorporações correspondente na nova coluna vectors.

O próximo exemplo faz loops em cada linha no datatable, recupera os vetores para o conteúdo pré-processado e os armazena na coluna vectors. O serviço OpenAI limita as solicitações frequentes, então o exemplo inclui um "exponential back-off" (espera exponencial), conforme sugerido pela documentação.

Após o término do script, cada linha deve ter uma lista delimitada por vírgulas de 1536 vetores para cada documento. Se ocorrer um erro e o código de status for 400, o caminho do arquivo, o título e o código de erro serão adicionados a uma variável chamada $errorDocs para solucionar o problema. O erro mais comum ocorre quando o número de tokens ultrapassa o limite da solicitação para o modelo.

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

Agora você tem uma tabela de banco de dados em memória local dos documentos de referência do PowerShell 7.4.

Com base em uma cadeia de caracteres de pesquisa, precisamos calcular outro conjunto de vetores para que o PowerShell possa classificar cada documento por similaridade.

No próximo exemplo, os vetores são recuperados para a cadeia de caracteres de pesquisa 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 ','

Por fim, a próxima função de exemplo, que empresta um exemplo do script de exemplo Measure-VectorSimilarity escrito por Lee Holmes, realiza um cálculo de similaridade de cosseno e, em seguida, classifica cada linha na datatable.

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

Os comandos no próximo exemplo percorrem todas as linhas em $Datatable e calculam a similaridade de cosseno com a cadeia de caracteres de pesquisa. Os resultados são classificados e os três principais resultados são armazenados em uma variável chamada $topThree. O exemplo não retorna uma saída.

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

Analise a saída da variável $topThree, com apenas as propriedades title e url, na visão de grade.

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

Saída:

Captura de tela dos resultados formatados após a conclusão da consulta da pesquisa.

A variável $topThree contém todas as informações das linhas na tabela de dados. Por exemplo, a propriedade conteúdo contém o formato original do documento. Use [0] para indexar no primeiro item da matriz.

$topThree[0].content

Veja o documento completo (truncado no snippet de saída desta página).

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

Finalmente, em vez de regenerar as incorporações toda vez que precisar consultar o conjunto de dados, você poderá armazenar os dados no disco e recuperá-los no futuro. Os métodos WriteXML() e ReadXML() dos tipos de objeto DataTableno próximo exemplo simplificam o processo. O esquema do arquivo XML exige que a datatable tenha um TableName.

Substitua <YOUR-FULL-FILE-PATH> pelo caminho completo onde você deseja gravar e ler o arquivo XML. O caminho deve terminar com .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>")

Ao reutilizar os dados, você precisa obter os vetores de cada nova cadeia de caracteres de pesquisa (mas não a datatable inteira). A título de aprendizado, tente criar um script do PowerShell para automatizar o comando Invoke-RestMethod com a cadeia de caracteres de pesquisa como um parâmetro.

Usando essa abordagem, você pode usar incorporações como um mecanismo de pesquisa entre documentos em uma base de dados de conhecimento. Em seguida, o usuário pode obter o primeiro resultado da pesquisa e usá-lo para sua tarefa downstream, que solicitou a sua consulta inicial.

Limpar os recursos

Se você criou um recurso OpenAI do Azure somente para concluir este tutorial e deseja limpar e remover um recurso OpenAI do Azure, é necessário excluir seus modelos implantados e, em seguida, excluir o recurso ou o grupo de recursos associado se ele for dedicado ao recurso de teste. Excluir o grupo de recursos também exclui todos os recursos associados a ele.

Próximas etapas

Saiba mais sobre os modelos do OpenAI do Azure: