Tutorial: Explore o Azure OpenAI no Foundry da Microsoft, Modelos de Embeddings e Busca de Documentos

Este tutorial mostra-te como usar a API Azure OpenAI embeddings para realizar pesquisa de documentos. Consultas uma base de conhecimento para encontrar o documento mais relevante.

Neste tutorial, aprende como:

  • Descarregue um conjunto de dados de exemplo e prepare-o para análise.
  • Cria variáveis de ambiente para o endpoint dos recursos e a chave da API.
  • Use um dos seguintes modelos: text-embedding-ada-002 (Versão 2), text-embedding-3-large ou text-embedding-3-small.
  • Utilize a semelhança cosseno para classificar os resultados de pesquisa.

Pré-requisitos

Configuração

Bibliotecas Python

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

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

Descarregue o conjunto de dados BillSum

BillSum é um conjunto de dados de projetos de lei do Congresso dos Estados Unidos e dos estados da Califórnia. Para fins ilustrativos, vamos olhar apenas para as notas dos EUA. O corpus consiste em projetos de lei das 103.ª-115.ª sessões (1993-2018) do Congresso. Os dados foram divididos em 18.949 projetos de comboio e 3.269 projetos de teste. O corpus BillSum foca-se em legislação de comprimento médio entre 5.000 e 20.000 caracteres. Mais informações sobre o projeto e o artigo académico original de onde este conjunto de dados deriva podem ser encontradas no repositório de GitHub do projeto BillSum

Este tutorial utiliza o ficheiro bill_sum_data.csv, que pode ser descarregado das amostras de dados no nosso GitHub.

Também pode descarregar os dados de exemplo executando o seguinte comando na 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

Nota

Autenticação baseada no Microsoft Entra ID atualmente não é suportada para integrações com a API v1.

Recuperar chave e endpoint

Para realizar uma chamada bem-sucedida para o Azure OpenAI, precisa de um endpoint e de uma chave.

Nome da variável Valor
ENDPOINT O endpoint do serviço pode ser encontrado na secção Keys & Endpoint ao examinar o recurso no portal do Azure. Em alternativa, pode encontrar o endpoint através da página Deployments no portal Microsoft Foundry. Um exemplo de ponto final é: https://docs-test-001.openai.azure.com/.
API-KEY Este valor pode ser encontrado na secção Keys & Endpoint ao examinar o seu recurso no portal do Azure. Pode usar tanto KEY1 como KEY2.

Aceda ao seu recurso no portal Azure. A secção Chaves e Endpoints pode ser encontrada na secção de Gestão de Recursos . Copie o seu endpoint e a chave de acesso, pois vai precisar de ambos para autenticar as suas chamadas API. Pode usar tanto KEY1 como KEY2. Ter sempre duas chaves permite-lhe rotacionar e regenerar chaves de forma segura sem causar interrupções no serviço.

Captura de ecrã da interface de visão geral de um recurso Azure OpenAI no portal Azure com a localização do endpoint e das chaves de acesso circuladas a vermelho.

Variáveis ambientais

Cria e atribui variáveis persistentes de ambiente para a tua chave API.

Importante

Use as chaves API com cautela. Não incluas a chave API diretamente no teu código e nunca a publiques publicamente. Se usares uma chave API, guarda-a de forma segura no Azure Key Vault. Para mais informações sobre o uso seguro das chaves de API nas suas aplicações, consulte chaves de API com Azure Key Vault.

Para mais informações sobre a segurança dos serviços de IA, consulte Autenticar pedidos para Serviços de IA do Azure.

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 

Depois de definir as variáveis de ambiente, pode ser necessário fechar e reabrir os notebooks Jupyter ou qualquer IDE que esteja a usar para que as variáveis de ambiente sejam acessíveis. Embora recomendemos fortemente o uso do Jupyter Notebook, se por algum motivo não o conseguir fazer, terá de modificar qualquer código que devolva um dataframe pandas usando print(dataframe_name) em vez de apenas chamar o dataframe_name diretamente, como frequentemente é feito no final de um bloco de código.

Execute o seguinte código no seu IDE Python preferido:

Bibliotecas de importação

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 OpenAI

Agora precisamos ler o nosso ficheiro csv e criar um DataFrame pandas. Depois de criado o DataFrame inicial, podemos visualizar 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 ecrã da tabela DataFrame inicial resulta do ficheiro csv.

A tabela inicial tem mais colunas do que precisamos vamos criar um novo DataFrame mais pequeno 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 ecrã dos resultados da tabela DataFrame mais pequena com apenas texto, resumo e colunas de título exibidas.

De seguida, vamos fazer uma limpeza leve dos 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 de remover quaisquer bilhetes que sejam demasiado longos para o limite de tokens (8.192 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

Neste caso, todas as faturas estão abaixo do limite de tokens de entrada do modelo de embedding, mas pode usar a técnica acima para remover entradas que, de outra forma, fariam com que a embedding falhasse. Quando confrontado com conteúdo que ultrapassa o limite de embedding, também pode dividir o conteúdo em pedaços mais pequenos e depois incorporar os blocos um de cada vez.

Vamos examinar df_bills novamente.

df_bills

Saída:

Captura de ecrã do DataFrame com uma nova coluna chamada n_tokens.

Para compreender um pouco melhor 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 a nossa documentação, estamos a truncar intencionalmente a saída, mas ao executar este comando no seu ambiente, o texto completo a partir do índice zero será tokenizado em blocos. Pode ver que, em alguns casos, uma palavra inteira é representada com um único token, enquanto noutros partes das palavras são divididas por 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 depois verificar o comprimento da decode variável, verá que corresponde ao primeiro número na coluna n_tokens.

len(decode)
1466

Agora que compreendemos melhor como funciona a tokenização, podemos avançar para o embedding. É importante notar que ainda não tokenizámos os documentos. A n_tokens coluna é simplesmente uma forma de garantir que nenhum dos dados que passamos para o modelo para tokenização e embedding ultrapassa o limite de tokens de entrada de 8.192. Quando passamos os documentos para o modelo de embeddings, ele divide os documentos em tokens semelhantes (embora não necessariamente idênticos) aos exemplos acima e depois converte os tokens numa série de números de ponto flutuante que serão acessíveis via pesquisa vetorial. Estas incorporações podem ser armazenadas localmente ou numa base de dados Azure para suportar Pesquisa Vetorial. Como resultado, cada fatura terá o seu próprio vetor de incorporação correspondente na nova ada_v2 coluna do lado direito do DataFrame.

No exemplo abaixo, estamos a chamar o modelo de embedding uma vez por cada item que queremos incorporar. Ao trabalhar com grandes projetos de embedding, pode alternativamente passar ao modelo um array de entradas para incorporar, em vez de uma entrada de cada vez. Quando passa um array de entradas ao modelo, o número máximo de itens de entrada por chamada para o endpoint de embedding é 2048.

client = OpenAI(
  api_key = os.getenv("AZURE_OPENAI_API_KEY"),  
  base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"
)

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 do comando df_bills.

À medida que executamos o bloco de código de pesquisa abaixo, vamos incorporar a consulta de pesquisa "Posso obter informações sobre receitas fiscais das empresas de cabo?" com o mesmo modelo text-embedding-ada-002 (Versão 2 ). A seguir, encontraremos o vetor de fatura mais próximo do texto recém-incorporado da nossa consulta, classificado por similaridade do 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 do res, assim que a consulta de pesquisa foi executada.

Finalmente, vamos mostrar o resultado principal da pesquisa de documentos com base na consulta do utilizador contra toda a base de conhecimento. Isto apresenta o resultado principal da "Taxpayer's Right to View Act 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."

Ao usar esta abordagem, pode usar embeddings como mecanismo de pesquisa entre documentos numa base de conhecimento. O utilizador pode então pegar no principal resultado da pesquisa e usá-lo para a tarefa subsequente, que motivou a sua consulta inicial.

Resolução de problemas

  • 401/403: Verifique se AZURE_OPENAI_API_KEY está definida e corresponde à sua chave de recurso.
  • 404: Verifique se AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT corresponde ao nome da sua implementação.
  • URL inválido: Verifique se AZURE_OPENAI_ENDPOINT é o endpoint do recurso, como https://<resource-name>.openai.azure.com.

Recursos de limpeza

Se criou um recurso Azure OpenAI apenas para completar este tutorial e quer limpar e remover um recurso Azure OpenAI, elimine os seus modelos implementados. Depois, elimina o recurso ou o grupo de recursos associado se for dedicado ao teu recurso de teste. Eliminar o grupo de recursos também elimina quaisquer outros recursos associados a ele.

Próximos passos

Saiba mais sobre os modelos do Azure OpenAI: