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

Este tutorial irá guiá-lo através do uso da API de incorporação do Azure OpenAI para executar a pesquisa de documentos, onde você consultará uma base de dados de conhecimento para encontrar o documento mais relevante.

Neste tutorial, irá aprender a:

  • Instale o Azure OpenAI.
  • Faça o download de um conjunto de dados de exemplo e prepare-o para análise.
  • Crie variáveis de ambiente para seus recursos, ponto de extremidade e chave de API.
  • Use o modelo text-embedding-ada-002 (Versão 2)
  • Use a semelhança cosseno para classificar os resultados da pesquisa.

Pré-requisitos

  • Uma assinatura do Azure - Crie uma gratuitamente
  • Acesso concedido ao Azure OpenAI na subscrição pretendida do Azure. Atualmente, o acesso a este serviço é concedido apenas por pedido. Você pode solicitar acesso ao Azure OpenAI preenchendo o formulário em https://aka.ms/oai/access. Abra um problema neste repositório para entrar em contato conosco se tiver um problema.
  • Um recurso OpenAI do Azure com o modelo text-embedding-ada-002 (Versão 2) implantado. Atualmente, este modelo só está disponível 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, pandas, tiktoken.
  • Blocos de Notas do Jupyter

Configurar

Bibliotecas Python

Se ainda não o fez, terá de instalar as seguintes bibliotecas:

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

Baixe o conjunto de dados BillSum

BillSum é um conjunto de dados de projetos de lei do Congresso dos Estados Unidos e do estado da Califórnia. Para fins de ilustração, vamos olhar apenas para as contas dos EUA. O corpus é composto por projetos de lei das 103ª a 115ª sessões (1993-2018) do Congresso. Os dados foram divididos em 18.949 contas de trens e 3.269 contas de testes. O corpus BillSum concentra-se na legislação de tamanho médio de 5.000 a 20.000 caracteres de comprimento. Mais informações sobre o projeto e o artigo acadêmico original de onde esse conjunto de dados é derivado podem ser encontradas no repositório GitHub do projeto BillSum.

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

Você também pode baixar os dados de exemplo executando o seguinte comando em sua máquina 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 fazer uma chamada com êxito no Azure OpenAI, você precisa de um ponto de extremidade e uma chave.

Nome da variável Valor
ENDPOINT Este valor pode ser encontrado na secção Chaves e Ponto Final ao examinar o recurso no portal do Azure. Como alternativa, você pode encontrar o valor na Exibição de Código do Azure OpenAI Studio>Playground.> Um exemplo de ponto de extremidade é: https://docs-test-001.openai.azure.com/.
API-KEY Este valor pode ser encontrado na secção Chaves e Ponto Final ao examinar o recurso no portal do Azure. Pode utilizar KEY1 ou KEY2.

Vá para o seu recurso no portal do Azure. A seção Chaves & Ponto Final pode ser encontrada na seção Gerenciamento de Recursos. Copie seu endpoint e sua chave de acesso, pois você precisará de ambos para autenticar suas chamadas de API. Pode utilizar KEY1 ou KEY2. Ter sempre duas chaves permite-lhe rodar e regenerar chaves de forma segura sem causar uma interrupção do serviço.

Captura de ecrã da IU de descrição geral de um recurso OpenAI do Azure no portal do Azure com a localização do ponto de extremidade e das chaves de acesso em 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 blocos de anotações Jupyter ou qualquer IDE que esteja usando para que as variáveis de ambiente sejam acessíveis. Embora seja altamente recomendável usar o Jupyter Notebooks, se por algum motivo você não puder, precisará modificar qualquer código que esteja retornando um dataframe 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 Python preferido:

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 um DataFrame pandas. Depois que o DataFrame inicial é criado, 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 da tabela DataFrame inicial resulta do arquivo csv.

A tabela inicial tem mais colunas do que precisamos, vamos criar um novo DataFrame menor chamado df_bills que conterá apenas as colunas para text, summarye title.

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

Saída:

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

Em seguida, realizaremos uma leve limpeza de dados, removendo espaços em branco redundantes e limpando a pontuação para preparar os dados para 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 todas as contas que são 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

Nota

Nesse caso, todas as faturas estão abaixo do limite de token de entrada do modelo de incorporação, mas você pode usar a técnica acima para remover entradas que, de outra forma, causariam falha na incorporação. Quando confrontado com conteúdo que excede o limite de incorporação, você também pode dividir o conteúdo em partes menores e, em seguida, incorporá-las uma de cada vez.

Examinaremos mais uma vez 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, bem como como o texto é 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 executar esse comando em seu ambiente retornará 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 outros partes das palavras são divididas em 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 você verificar o comprimento da decode variável, descobrirá que ela corresponde ao primeiro número na coluna n_tokens.

len(decode)
1466

Agora que entendemos mais sobre como a tokenização funciona, podemos passar para a incorporação. É importante notar que ainda não tokenizamos os documentos. A n_tokens coluna é simplesmente uma maneira de garantir que nenhum dos dados que passamos para o modelo para tokenização e incorporação exceda o limite de token de entrada de 8.192. Quando passamos os documentos para o modelo de incorporações, ele quebrará os documentos em tokens semelhantes (embora não necessariamente idênticos) aos exemplos acima e, em seguida, converterá os tokens em uma série de números de ponto flutuante que serão acessíveis por meio de pesquisa vetorial. Essas incorporações podem ser armazenadas localmente ou em um Banco de Dados do Azure para dar suporte à Pesquisa Vetorial. Como resultado, cada fatura terá seu próprio vetor de incorporação correspondente na nova ada_v2 coluna no lado direito do DataFrame.

No exemplo abaixo, estamos chamando o modelo de incorporação uma vez por cada item que queremos incorporar. Ao trabalhar com grandes projetos de incorporação, você pode, alternativamente, passar ao modelo uma matriz de entradas para incorporar, em vez de uma entrada de cada vez. Quando você passa o modelo para uma matriz de entradas, o número máximo de itens de entrada por chamada para o 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 ecrã dos resultados formatados df_bills comando.

À medida que executamos o bloco de código de pesquisa abaixo, incorporaremos a consulta de pesquisa "Posso obter informações sobre a receita fiscal da empresa de cabo?" com o mesmo modelo text-embedding-ada-002 (Versão 2). Em seguida, encontraremos a fatura mais próxima da incorporação ao texto recém-incorporado de nossa consulta classificada por semelhança de 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 ecrã dos resultados formatados de res depois de a consulta de pesquisa ter sido executada.

Finalmente, mostraremos o resultado superior da pesquisa de documentos com base na consulta do usuário em relação a toda a base de conhecimento. Isso retorna o resultado principal da "Lei do Direito do Contribuinte de Ver de 1993". Este documento tem uma pontuação de semelhança 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 concedido ao Azure OpenAI na subscrição pretendida do Azure.

    Atualmente, o acesso a este serviço é concedido apenas por pedido. Você pode solicitar acesso ao Azure OpenAI preenchendo o formulário em https://aka.ms/oai/access. Abra um problema neste repositório para entrar em contato conosco se tiver um problema.

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

    Atualmente, este modelo só está disponível 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

Nota

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

Recuperar chave e ponto de extremidade

Para fazer uma chamada com êxito no Azure OpenAI, você precisa de um ponto de extremidade e uma chave.

Nome da variável Valor
ENDPOINT Este valor pode ser encontrado na secção Chaves e Ponto Final ao examinar o recurso no portal do Azure. Como alternativa, você pode encontrar o valor na Exibição de Código do Azure OpenAI Studio>Playground.> Um exemplo de ponto de extremidade é: https://docs-test-001.openai.azure.com/.
API-KEY Este valor pode ser encontrado na secção Chaves e Ponto Final ao examinar o recurso no portal do Azure. Pode utilizar KEY1 ou KEY2.

Vá para o seu recurso no portal do Azure. A seção Chaves & Ponto Final pode ser encontrada na seção Gerenciamento de Recursos. Copie seu endpoint e sua chave de acesso, pois você precisará de ambos para autenticar suas chamadas de API. Pode utilizar KEY1 ou KEY2. Ter sempre duas chaves permite-lhe rodar e regenerar chaves de forma segura sem causar uma interrupção do serviço.

Captura de ecrã da IU de descrição geral de um recurso OpenAI do Azure no portal do Azure com a localização do ponto de extremidade e das chaves de acesso em vermelho.

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

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 de pesquisa da Microsoft.

Crie uma pasta onde você gostaria de armazenar seu projeto. Defina seu local para a pasta do projeto. Transfira o conjunto de dados para a sua máquina local utilizando o comando e, em seguida, expanda Invoke-WebRequest o arquivo. Por último, defina seu 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, portanto, usamos um objeto de tabela de dados .NET para um desempenho eficiente. A tabela de dados tem colunas título, conteúdo, preparação, uri, arquivo e vetores. A coluna de título é a chave primária.

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

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

Visualize os dados usando o out-gridview comando (não disponível no Cloud Shell).

$Datatable | out-gridview

Resultado:

Captura de tela dos resultados iniciais do DataTable.

Em seguida, execute uma limpeza leve de dados removendo caracteres extras, espaço vazio e outras anotações de documento, para preparar os dados para tokenização. A função Invoke-DocPrep de exemplo demonstra como usar o operador PowerShell -replace para iterar através de uma lista de caracteres que você gostaria de 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 Invoke-DocPrep função, use o ForEach-Object comando para armazenar o conteúdo preparado na coluna de preparação , para todas as linhas da tabela de dados. 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 }

Exiba a tabela de dados novamente para ver a alteração.

$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 para usar em uma pesquisa de semelhança de cosseno. Essas incorporações podem ser armazenadas localmente ou em um serviço como a Pesquisa Vetorial no Azure AI Search. Cada documento tem seu próprio vetor de incorporação correspondente na nova coluna de vetores.

O próximo exemplo percorre cada linha da tabela de dados, recupera os vetores para o conteúdo pré-processado e os armazena na coluna de vetores. O serviço OpenAI limita solicitações frequentes, de modo que o exemplo inclui um back-off exponencial, conforme sugerido pela documentação.

Após a conclusão 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 nomeada $errorDocs para solução de problemas. O erro mais comum ocorre quando a contagem de tokens é maior do que o limite de prompt 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 na memória local de 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 semelhança.

No próximo exemplo, os vetores são recuperados para a cadeia de caracteres get a list of running processesde pesquisa .

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

Finalmente, a próxima função de exemplo, que toma emprestado um exemplo do script de exemplo Measure-VectorSimilarity escrito por Lee Holmes, executa um cálculo de semelhança cosseno e, em seguida, classifica cada linha na tabela de dados.

# 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 e calculam a semelhança cosseno com $Datatable a cadeia de pesquisa. Os resultados são classificados e os três primeiros resultados são armazenados em uma variável chamada $topThree. O exemplo não retorna a 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 }
}

Revise a $topThree saída da variável, com apenas as propriedades title e url , em gridview.

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

Saída:

Captura de ecrã dos resultados formatados assim que a consulta de pesquisa terminar.

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

$topThree[0].content

Veja o documento completo (truncado no trecho 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 sempre que precisar consultar o conjunto de dados, você pode armazenar os dados em disco e recuperá-los no futuro. Os WriteXML() métodos e ReadXML() dos tipos de objeto DataTable no próximo exemplo simplificam o processo. O esquema do arquivo XML requer que a tabela de dados tenha um TableName.

Substitua <YOUR-FULL-FILE-PATH> pelo caminho completo onde você gostaria de escrever 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>")

À medida que reutiliza os dados, você precisa obter os vetores de cada nova cadeia de caracteres de pesquisa (mas não a tabela de dados inteira). Como um exercício de aprendizagem, tente criar um script do PowerShell para automatizar o Invoke-RestMethod comando com a cadeia de caracteres de pesquisa como parâmetro.

Usando essa abordagem, você pode usar incorporações como um mecanismo de pesquisa em documentos em uma base de dados de conhecimento. O usuário pode então pegar o resultado da pesquisa superior e usá-lo para sua tarefa a jusante, que solicitou sua consulta inicial.

Clean up resources (Limpar recursos)

Se você criou um recurso do Azure OpenAI apenas para concluir este tutorial e deseja limpar e remover um recurso do Azure OpenAI, precisará excluir seus modelos implantados e, em seguida, excluir o recurso ou o grupo de recursos associado se ele for dedicado ao seu recurso de teste. A exclusão do grupo de recursos também exclui quaisquer outros recursos associados a ele.

Próximos passos

Saiba mais sobre os modelos do Azure OpenAI: