Tutorial: Explore o Azure OpenAI nos Microsoft Foundry Models e na pesquisa de documentos

Este tutorial irá guiá-lo através da utilização da API embeddings da Azure OpenAI para realizar pesquisa de documentos em que vai consultar 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.
  • Crie as variáveis de ambiente para o ponto de acesso a recursos e a chave da API.
  • Use um dos seguintes modelos: text-embedding-ada-002 (Versão 2), text-embedding-3-grande, text-embedding-3-pequeno.
  • Utilize 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 que pode ser descarregado do nosso repositório de exemplos no 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

A API v1 não suporta atualmente a autenticação baseada no Microsoft Entra ID para integrações.

Recuperar chave e endpoint

Para fazer uma chamada com êxito 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 Chaves & Endpoint ao examinar o seu 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 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 alternar e regenerar chaves de forma segura sem causar interrupção 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 da API nas suas aplicações, consulte Chaves da 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 Notebooks, se por algum motivo não puder usar o Jupyter Notebooks, terá de modificar qualquer código que esteja a devolver um dataframe do 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 remover quaisquer faturas que sejam demasiado longas 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 o 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 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-se passar ao modelo um array de entradas para embedder em vez de uma entrada de cada vez. Quando passa ao modelo um array de entradas, o número máximo de itens de entrada por chamada do 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 a partir 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 vector de fatura mais próximo ao texto recém-incorporado da nossa consulta, ordenando-o 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 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 retorna o resultado principal da "Lei do Direito do Contribuinte à Visualização 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."

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

Resolução de problemas

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

Liberar recursos

Se criou um recurso Azure OpenAI apenas para completar este tutorial e quiser limpar e remover um recurso Azure OpenAI, terá de eliminar os seus modelos implementados e depois eliminar o recurso ou grupo de recursos associado se for dedicado ao seu 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: