Tutorial: Exploración de Azure OpenAI en los embeddings de modelos de Microsoft Foundry y la búsqueda de documentos

Este tutorial le guiará por el uso de la API Azure OpenAI embeddings API para realizar document search donde consultará una base de conocimiento para encontrar el documento más relevante.

En este tutorial, aprenderá a:

  • Descargue un conjunto de datos de ejemplo y prepárelo para su análisis.
  • Cree variables de entorno para el punto de conexión de recursos y la clave de API.
  • Use uno de los modelos siguientes: text-embedding-ada-002 (versión 2), text-embeding-3-large, text-embedding-3-small models.
  • Use la similitud de coseno para clasificar los resultados de la búsqueda.

Requisitos previos

Configurar

bibliotecas de Python

Si aún no lo ha hecho, debe instalar las siguientes bibliotecas:

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

Descarga del conjunto de datos de BillSum

BillSum es un conjunto de datos de los proyectos de ley del Congreso de los Estados Unidos y del estado de California. Con fines ilustrativos, solo veremos las facturas estadounidenses. El corpus consta de proyectos de ley de las sesiones del 103 al 115 del Congreso de EE. UU. (1993-2018). Los datos se dividieron en 18.949 facturas de tren y 3.269 facturas de prueba. El corpus de BillSum se centra en la legislación de longitud media de 5000 a 20 000 caracteres de longitud. Puede encontrar más información sobre el proyecto y el documento académico original del que se deriva este conjunto de datos en el repositorio GitHub del proyecto BillSum

En este tutorial se usa el archivo bill_sum_data.csv que se puede descargar de nuestros datos de ejemplo de GitHub.

También puede descargar los datos de ejemplo ejecutando el siguiente comando en el equipo 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

actualmente no se admite la autenticación basada en Microsoft Entra ID para incrustaciones con la API v1.

Recuperación de la clave y el punto de conexión

Para realizar correctamente una llamada en Azure OpenAI, necesita un endpoint y un key.

Nombre de variable Valor
ENDPOINT El punto de conexión de servicio se puede encontrar en la sección de Claves y punto de conexión al examinar el recurso desde el portal de Azure. Como alternativa, puede encontrar el punto de conexión a través de la página Deployments en el portal de Microsoft Foundry. Un punto de conexión de ejemplo es: https://docs-test-001.openai.azure.com/.
API-KEY Este valor se puede encontrar en la sección Claves y puntos de conexión al examinar su recurso desde el portal de Azure. Puede usar KEY1 o KEY2.

Acceda a su recurso en el portal de Azure. La sección Claves y punto de conexión se puede encontrar en la sección Administración de recursos . Copie el punto de conexión y la clave de acceso, ya que necesitará ambos para autenticar las llamadas API. Puede usar KEY1 o KEY2. Tener siempre dos claves le permite rotar y regenerar las claves de forma segura sin provocar una interrupción del servicio.

Captura de pantalla de la interfaz de usuario de vista general de un recurso Azure OpenAI en el portal de Azure, con el punto de conexión y la ubicación de las claves de acceso resaltados en rojo.

Variables de entorno

Cree y asigne variables de entorno persistentes para la clave de API.

Importante

Use las claves de API con precaución. No incluya la clave de API directamente en el código y nunca la publique públicamente. Si usa una clave de API, almacénela de forma segura en Azure Key Vault. Para obtener más información sobre el uso de claves de API de forma segura en las aplicaciones, consulte CLAVESAPI con Azure Key Vault.

Para obtener más información sobre la seguridad de los servicios de inteligencia artificial, consulte Authenticate requests to Servicios de Azure AI (Solicitudes de autenticación a Servicios de Azure AI.

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 

Después de establecer las variables de entorno, es posible que tenga que cerrar y volver a abrir cuadernos de Jupyter Notebook o cualquier IDE que use para que las variables de entorno sean accesibles. Aunque se recomienda encarecidamente usar Jupyter Notebooks, si por algún motivo no puedes, tendrás que modificar cualquier código que devuelva un dataframe de pandas usando print(dataframe_name) en lugar de llamar directamente a dataframe_name, como se hace a menudo al final de un bloque de código.

Ejecute el código siguiente en el IDE de 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 OpenAI

Ahora es necesario leer el archivo CSV y crear un dataframe de Pandas. Después de crear el dataframe inicial, podemos ver el contenido de la tabla ejecutando 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

Salida:

Captura de pantalla de los resultados iniciales de la tabla DataFrame del archivo CSV.

La tabla inicial tiene más columnas de las que necesitamos crearemos un nuevo DataFrame más pequeño denominado df_bills que contendrá solo las columnas de text, summaryy title.

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

Salida:

Captura de pantalla de los resultados más pequeños de la tabla DataFrame con solo las columnas de texto, resumen y título mostradas.

A continuación, se realizará una limpieza de datos ligeros mediante la eliminación de espacios en blanco redundantes y la limpieza de los signos de puntuación para preparar los datos para la tokenización.

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

Ahora es necesario quitar las facturas que sean demasiado largas para el límite 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

En este caso, todas las facturas están por debajo del límite de tokens de entrada del modelo de inserción, pero puede usar la técnica anterior para quitar entradas que, de lo contrario, provocarían un error en la inserción. Al enfrentarse a contenido que supera el límite de incrustación, también puede fragmentar el contenido en partes más pequeñas y luego incrustar las partes una a una.

Volveremos a examinar df_bills.

df_bills

Salida:

Captura de pantalla del DataFrame con una nueva columna denominada n_tokens.

Para comprender el n_tokens columna un poco más, así como cómo se tokeniza el texto en última instancia, puede resultar útil ejecutar el código siguiente:

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

Para nuestros documentos, truncamos intencionadamente la salida, pero la ejecución de este comando en el entorno devolverá el texto completo del índice cero tokenizado en fragmentos. Puede ver que, en algunos casos, se representa una palabra completa con un solo token, mientras que en otras partes de palabras se dividen entre varios 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',

Si, a continuación, comprueba la longitud de la decode variable, encontrará que coincide con el primer número de la columna n_tokens.

len(decode)
1466

Ahora que entendemos más sobre cómo funciona la tokenización, podemos pasar a la inserción. Es importante tener en cuenta que aún no hemos tokenizado los documentos. La n_tokens columna es simplemente una manera de asegurarse de que ninguno de los datos que pasamos al modelo para la tokenización e incrustación supera el límite de token de entrada de 8192. Cuando pasamos los documentos al modelo de incrustaciones, dividirá los documentos en tokens similares (aunque no necesariamente idénticos) a los ejemplos anteriores y, a continuación, convertirá los tokens en una serie de números de punto flotante que serán accesibles a través de la búsqueda vectorial. Estas inserciones se pueden almacenar localmente o en una base de datos Azure para admitir la búsqueda de vectores. Como resultado, cada factura tendrá su propio vector de inserción correspondiente en la nueva ada_v2 columna del lado derecho del DataFrame.

En el ejemplo siguiente, llamamos al modelo de inserción una vez por cada elemento que queremos insertar. Al trabajar con proyectos de inserción de gran tamaño, también puede pasar al modelo una matriz de entradas para insertar en lugar de una entrada a la vez. Al pasar una matriz de entradas al modelo, el número máximo de elementos de entrada por llamada al punto de incrustación es 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

Salida:

Captura de pantalla de los resultados con formato del comando df_bills.

A medida que ejecutamos el bloque de código de búsqueda siguiente, insertaremos la consulta de búsqueda "¿Puedo obtener información sobre los ingresos fiscales de la compañía de cable?" con el mismo modelo text-embeding-ada-002 (versión 2). A continuación, veremos la inserción de factura más cercana al texto recién insertado de la consulta clasificada por similitud de coseno.

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)

Salida:

Captura de pantalla de los resultados con formato de res una vez que se ha ejecutado la consulta de búsqueda.

Por último, mostraremos el resultado superior de la búsqueda de documentos en función de la consulta de usuario en toda la base de conocimiento. Esto devuelve el mejor resultado de la "Ley del Derecho a la Vista del Contribuyente de 1993". Este documento tiene una puntuación de similitud de coseno de 0,76 entre la consulta y el 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."

Con este enfoque, puede usar incrustaciones como un mecanismo de búsqueda entre documentos de una base de conocimiento. Después, el usuario puede tomar el primer resultado de búsqueda y usarlo para su tarea posterior, que motivó su consulta inicial.

Solución de problemas

  • 401/403: Verifique que AZURE_OPENAI_API_KEY esté establecido y sea igual a su clave de recurso.
  • 404: Compruebe que AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT coincide con el nombre de la implementación.
  • Dirección URL no válida: compruebe AZURE_OPENAI_ENDPOINT que es el punto de conexión del recurso, por ejemplo https://<resource-name>.openai.azure.com.

Limpieza de recursos

Si creó un recurso de Azure OpenAI únicamente para completar este tutorial y desea limpiar y quitar un recurso de OpenAI de Azure, deberá eliminar los modelos implementados y, a continuación, eliminar el recurso o el grupo de recursos asociado si está dedicado al recurso de prueba. Al eliminar el grupo de recursos también se eliminan los demás recursos asociados.

Pasos siguientes

Obtenga más información sobre los modelos de Azure OpenAI:

modelos Azure OpenAI