Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este artículo se muestra cómo usar los modelos multimodales de Azure OpenAI para generar respuestas a mensajes de usuario e imágenes cargadas en una aplicación de chat. Este ejemplo de aplicación de chat también incluye toda la infraestructura y la configuración necesarias para aprovisionar Azure recursos de OpenAI e implementar la aplicación en Azure Container Apps mediante la CLI para desarrolladores de Azure.
Siguiendo las instrucciones de este artículo, podrá:
- Implemente una aplicación de chat de contenedor de Azure que use la identidad administrada para la autenticación.
- Cargue imágenes que se usarán como parte de la secuencia de chat.
- Chatear con un modelo de lenguaje grande multimodal de Azure OpenAI mediante la API de Respuestas de la biblioteca OpenAI.
Una vez finalizado este artículo, puede comenzar a modificar el nuevo proyecto con su código personalizado.
Nota:
En este artículo se usan una o varias plantillas de aplicaciones de IA como base para los ejemplos e instrucciones del artículo. Las plantillas de aplicaciones de IA le proporcionan implementaciones de referencia bien mantenidas y fáciles de implementar que le ayudan a garantizar un punto inicial de alta calidad para sus aplicaciones de IA.
Introducción a la arquitectura
En el diagrama siguiente se muestra una arquitectura sencilla de la aplicación de chat:
La aplicación de chat se ejecuta como una aplicación contenedora de Azure. La aplicación usa la identidad administrada a través de Microsoft Entra ID para autenticarse con Azure OpenAI en producción, en lugar de una clave de API. Durante el desarrollo, la aplicación admite varios métodos de autenticación, como Azure credenciales de la CLI para desarrolladores y claves de API.
La arquitectura de la aplicación se basa en los siguientes servicios y componentes:
- Azure OpenAI representa el proveedor de IA al que se envían las consultas del usuario.
- Azure Container Apps es el entorno de contenedor donde se hospeda la aplicación.
- La identidad administrada nos ayuda a garantizar la mejor seguridad de la clase y elimina el requisito de que sea un desarrollador para administrar de forma segura un secreto.
- Archivos Bicep para el aprovisionamiento de recursos de Azure, incluidos Azure OpenAI, Azure Container Apps, Azure Container Registry, Azure Log Analytics y el control de acceso en función de roles (RBAC).
- Una aplicación Python Quart que usa el paquete
openaipara generar respuestas a mensajes de usuario con archivos de imagen cargados. - Un front-end HTML/JavaScript básico que transmite respuestas desde el back-end mediante JSON Lines a través de un ReadableStream.
Costos
En un intento de mantener los precios tan bajos como sea posible en este ejemplo, la mayoría de los recursos usan un plan de tarifa básico o de consumo. Modifique el nivel de nivel según sea necesario en función del uso previsto. Para dejar de incurrir en cargos, elimine los recursos cuando haya terminado con el artículo.
Obtenga más información sobre cost en el repositorio de ejemplo.
Requisitos previos
Hay disponible un entorno contenedor de desarrollo con todas las dependencias necesarias para completar este artículo. Puede ejecutar el contenedor de desarrollo en GitHub Codespaces (en un explorador) o localmente mediante Visual Studio Code.
Para usar este artículo, debe cumplir los siguientes requisitos previos:
Una suscripción Azure: Crear una gratuita
permisos de cuenta Azure: la cuenta de Azure debe tener permisos
Microsoft.Authorization/roleAssignments/write, como Administrador de acceso de usuario o Owner.cuenta de GitHub
Entorno de desarrollo abierto
Use las instrucciones siguientes para implementar un entorno de desarrollo preconfigurado que contenga todas las dependencias necesarias para completar este artículo.
GitHub Codespaces ejecuta un contenedor de desarrollo administrado por GitHub con Visual Studio Code para web como interfaz de usuario. Para el entorno de desarrollo más sencillo, use GitHub Codespaces para que tenga las herramientas de desarrollo y las dependencias correctas preinstaladas para completar este artículo.
Importante
Todas las cuentas de GitHub pueden usar Codespaces durante un máximo de 60 horas gratis cada mes con dos instancias principales. Para obtener más información, consulte almacenamiento incluido mensualmente y horas de núcleo de GitHub Codespaces.
Siga estos pasos para crear un nuevo GitHub Codespace en la rama main del repositorio de Azure-Samples/openai-chat-vision-quickstart GitHub.
Haga clic con el botón derecho en el botón siguiente y seleccione Abrir vínculo en la nueva ventana. Esta acción le permite tener el entorno de desarrollo y la documentación disponible para su revisión.
En la página Crear espacio de códigos , revise y seleccione Crear nuevo espacio de código.
Espere a que se inicie Codespaces. Este proceso de startup puede tardar unos minutos.
Inicie sesión en Azure con la CLI para desarrolladores de Azure en el terminal situado en la parte inferior de la pantalla.
azd auth loginCopie el código del terminal y péguelo en un navegador. Siga las instrucciones para autenticarse con su cuenta de Azure.
Las tareas restantes de este artículo tienen lugar en el contexto de este contenedor de desarrollo.
Despliegue y ejecución
El repositorio de ejemplo contiene todos los archivos de código y de configuración para la implementación de la aplicación de chat en Azure. Los siguientes pasos le guiarán a través del proceso de implementación de la aplicación de chat de ejemplo en Azure.
Implementación de la aplicación de chat en Azure
Importante
Para mantener bajos los costos, en este ejemplo se usan planes de tarifa básicos o de consumo para la mayoría de los recursos. Ajuste el nivel según sea necesario y elimine los recursos cuando haya terminado para evitar cargos.
Ejecute el siguiente comando Azure Developer CLI para el aprovisionamiento de recursos de Azure y la implementación del código fuente:
azd upUse la siguiente tabla para responder a las solicitudes:
Prompt Respuesta Nombre del entorno Hágala corta y en minúsculas. Agregue su nombre o alias. Por ejemplo, chat-vision. Se usa como parte del nombre del grupo de recursos.Suscripción Seleccione la suscripción en la que crear los recursos. Ubicación (para el hospedaje) Seleccione una ubicación cercana en la lista. Ubicación del modelo de OpenAI de Azure Seleccione una ubicación cercana en la lista. Si está disponible la misma ubicación que la primera, selecciónela. Espere hasta que se implemente la aplicación. La implementación suele tardar entre 5 y 10 minutos en completarse.
Uso de la aplicación de chat para formular preguntas al modelo de lenguaje grande
El terminal muestra una dirección URL después de una implementación correcta de la aplicación.
Seleccione esa URL etiquetada
Deploying service webpara abrir la aplicación de chat en un navegador.En el explorador, cargue una imagen haciendo clic en Elegir archivo y seleccionando una imagen.
Haga una pregunta sobre la imagen cargada, como "¿Cuál es la imagen?".
La respuesta procede de Azure OpenAI y se muestra el resultado.
Exploración del código de ejemplo
En este ejemplo se utiliza un modelo multimodal de Azure OpenAI para generar respuestas a mensajes de usuario e imágenes cargadas.
Codificación base64 de la imagen cargada en el front-end
La imagen cargada debe estar codificada en Base64 para que se pueda usar directamente como un URI de datos como parte del mensaje.
En el ejemplo, el siguiente fragmento de código de front-end en la scriptetiqueta del src/quartapp/templates/index.html archivo controla esa funcionalidad. La toBase64 función de flecha usa el readAsDataURL método del FileReader para leer de forma asincrónica el archivo de imagen cargado como una cadena codificada en base64.
const toBase64 = file => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
});
La función toBase64 es llamada por un oyente en el evento submit del formulario.
El submit escuchador de eventos controla el flujo completo de interacción del chat. Cuando el usuario envía un mensaje, se produce el siguiente flujo:
- Obtiene el archivo de imagen cargado (si está presente) y codifica como Base64.
- Crea y muestra el mensaje del usuario en el chat, incluida la imagen cargada.
- Prepara un contenedor de mensaje de asistente con un indicador de "Escribiendo..."
- Agrega el mensaje del usuario al array del historial de mensajes en formato de la API de respuestas
- Envía una
fetchsolicitud POST al/chat/streampunto de conexión con el historial de mensajes y el contexto (incluida la imagen codificada en Base64 y el nombre de archivo) - Procesa la respuesta de líneas JSON transmitidas para mostrar cada texto de forma incremental
- Controla los errores durante el streaming.
- Agrega un botón de salida de voz después de recibir la respuesta completa para que los usuarios puedan escuchar la respuesta.
- Borra el campo de entrada y devuelve el foco para el siguiente mensaje.
form.addEventListener("submit", async function(e) {
e.preventDefault();
// Hide the no-messages-heading when a message is added
document.getElementById("no-messages-heading").style.display = "none";
const file = document.getElementById("file").files[0];
const fileData = file ? await toBase64(file) : null;
const message = messageInput.value;
const userTemplateClone = userTemplate.content.cloneNode(true);
userTemplateClone.querySelector(".message-content").innerText = message;
if (file) {
const img = document.createElement("img");
img.src = fileData;
userTemplateClone.querySelector(".message-file").appendChild(img);
}
targetContainer.appendChild(userTemplateClone);
const assistantTemplateClone = assistantTemplate.content.cloneNode(true);
let messageDiv = assistantTemplateClone.querySelector(".message-content");
targetContainer.appendChild(assistantTemplateClone);
messages.push({
"role": "user",
"content": [{"type": "input_text", "text": message}]
});
try {
messageDiv.scrollIntoView();
const response = await fetch("/chat/stream", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
messages: messages,
context: {
file: fileData,
file_name: file ? file.name : null
}
})
});
if (!response.ok || !response.body) {
throw new Error(`Request failed (${response.status})`);
}
let answer = "";
for await (const chunk of readNDJSONStream(response.body)) {
if (chunk.type === "error" || chunk.type === "response.failed") {
messageDiv.innerHTML = "Error: " + (chunk.error || "Unknown error");
break;
}
if (chunk.type === "response.output_text.delta") {
// Clear out the DIV if its the first answer chunk we've received
if (answer == "") {
messageDiv.innerHTML = "";
}
answer += chunk.delta;
messageDiv.innerHTML = converter.makeHtml(answer);
messageDiv.scrollIntoView();
}
}
messages.push({
"role": "assistant",
"content": [{"type": "output_text", "text": answer}]
});
messageInput.value = "";
const speechOutput = document.createElement("speech-output-button");
speechOutput.setAttribute("text", answer);
messageDiv.appendChild(speechOutput);
messageInput.focus();
} catch (error) {
messageDiv.innerHTML = "Error: " + error;
}
});
Manejo de la imagen en el backend
En el src\quartapp\chat.py archivo, el código de back-end para el control de imágenes comienza después de configurar la autenticación sin claves.
Nota:
Para obtener más información sobre cómo usar conexiones sin claves para la autenticación y autorización para Azure OpenAI, consulte el artículo Get started with the Azure OpenAI security building block Microsoft Learn .
Configuración de autenticación
La configure_openai() función configura el cliente de OpenAI antes de que la aplicación empiece a atender solicitudes. Usa el decorador de @bp.before_app_serving Quart para configurar la autenticación basada en variables de entorno. Este sistema flexible permite a los desarrolladores trabajar en diferentes contextos sin cambiar el código.
Modos de autenticación explicados
-
Desarrollo local (
OPENAI_HOST=local): se conecta a un servicio de API compatible con OpenAI local (como Ollama o LocalAI) sin autenticación. Use este modo para realizar pruebas sin costos de Internet o API. -
Azure OpenAI con clave de API (
AZURE_OPENAI_KEY_FOR_CHATVISIONvariable de entorno): usa una clave de API para la autenticación. Evite este modo en producción porque las claves de API requieren rotación manual y suponen riesgos de seguridad si se exponen. Úselo para realizar pruebas locales dentro de un contenedor de Docker sin CLI de Azure credenciales. -
Production with Managed Identity (
RUNNING_IN_PRODUCTION=true): usaManagedIdentityCredentialpara autenticarse con Azure OpenAI a través de la identidad administrada de la aplicación contenedora. Este método se recomienda para producción porque elimina la necesidad de administrar secretos. Azure Container Apps proporciona automáticamente la identidad administrada y concede permisos durante la implementación a través de Bicep. -
Desarrollo con CLI de Azure (modo predeterminado): Usa
AzureDeveloperCliCredentialpara autenticarse con Azure OpenAI mediante credenciales de CLI de Azure de inicio de sesión local. Este modo simplifica el desarrollo local sin administrar claves de API.
Detalles clave de implementación
- La función
get_bearer_token_provider()actualiza las credenciales de Azure y las usa como tokens portadores. - La ruta de acceso del punto de conexión de OpenAI de Azure termina con
/openai/v1/, el punto de conexión compatible con OpenAI que está disponible en general para los modelos de Foundry de Microsoft. - La función es asincrónica, ya que Quart es un marco de aplicación web asincrónica. Quart permite que los controladores de solicitudes sean asincrónicos, por lo que mientras la aplicación espera respuestas lentas de la API LLM, el servidor puede seguir controlando otras solicitudes.
Este es el código de configuración de autenticación completo de chat.py:
@bp.before_app_serving
async def configure_openai():
bp.model_name = os.getenv("OPENAI_MODEL", "gpt-4o")
openai_host = os.getenv("OPENAI_HOST", "azure")
if openai_host == "local":
bp.openai_client = AsyncOpenAI(api_key="no-key-required", base_url=os.getenv("LOCAL_OPENAI_ENDPOINT"))
current_app.logger.info("Using local OpenAI-compatible API service with no key")
elif os.getenv("AZURE_OPENAI_KEY_FOR_CHATVISION"):
bp.openai_client = AsyncOpenAI(
base_url=os.environ["AZURE_OPENAI_ENDPOINT"].rstrip("/") + "/openai/v1",
api_key=os.getenv("AZURE_OPENAI_KEY_FOR_CHATVISION"),
)
current_app.logger.info("Using Azure OpenAI with key")
elif os.getenv("RUNNING_IN_PRODUCTION"):
client_id = os.environ["AZURE_CLIENT_ID"]
azure_credential = ManagedIdentityCredential(client_id=client_id)
token_provider = get_bearer_token_provider(azure_credential, "https://cognitiveservices.azure.com/.default")
bp.openai_client = AsyncOpenAI(
base_url=os.environ["AZURE_OPENAI_ENDPOINT"].rstrip("/") + "/openai/v1",
api_key=token_provider,
)
current_app.logger.info("Using Azure OpenAI with managed identity credential for client ID %s", client_id)
else:
tenant_id = os.environ["AZURE_TENANT_ID"]
azure_credential = AzureDeveloperCliCredential(tenant_id=tenant_id)
token_provider = get_bearer_token_provider(azure_credential, "https://cognitiveservices.azure.com/.default")
bp.openai_client = AsyncOpenAI(
base_url=os.environ["AZURE_OPENAI_ENDPOINT"].rstrip("/") + "/openai/v1",
api_key=token_provider,
)
current_app.logger.info("Using Azure OpenAI with az CLI credential for tenant ID: %s", tenant_id)
Función de controlador de chat
La chat_handler() función procesa las solicitudes de chat enviadas al punto de conexión /chat/stream. Recibe una solicitud POST con una carga JSON.
La carga JSON incluye:
-
messages: una lista del historial de conversaciones. Cada mensaje tiene un
role("usuario" o "asistente") ycontent(una matriz de elementos de contenido mediante el formato de entrada de la API de respuestas). -
context: datos adicionales para el procesamiento, entre los que se incluyen:
-
file: datos de imagen codificados en Base64 (por ejemplo,
data:image/png;base64,...). - file_name: nombre de archivo original de la imagen cargada (útil para registrar o identificar el tipo de imagen).
-
file: datos de imagen codificados en Base64 (por ejemplo,
El controlador extrae el historial de mensajes y los datos de imagen. Si no se carga ninguna imagen, el valor de la imagen es nully el código controla este caso.
@bp.post("/chat/stream")
async def chat_handler():
request_json = await request.get_json()
request_messages = request_json["messages"]
# Get the base64 encoded image from the request context
# This will be None if no image was uploaded
image = request_json["context"]["file"]
Creación de la matriz de entrada para solicitudes de visión
La función response_stream() prepara la matriz de entrada que se envía a la API de respuestas de OpenAI de Azure. El @stream_with_context decorador mantiene el contexto de solicitud mientras transmite la respuesta.
Lógica de preparación de entrada
-
Comience con el historial de conversaciones: la función comienza con
all_input, que incluye todos los mensajes anteriores, excepto el más reciente (request_messages[0:-1]). Los mensajes ya están en formato de API de respuestas desde el front-end. -
Controle el mensaje de usuario actual en función de la presencia de la imagen:
-
Con imagen: añada una parte del
input_imagecontenido a la matriz de contenido existente del usuario. - Sin imagen: anexe el mensaje del usuario as-is.
-
Con imagen: añada una parte del
@stream_with_context
async def response_stream():
# This sends all messages, so API request may exceed token limits
all_input = list(request_messages[0:-1])
# Add the current user message, appending image if provided
if image:
user_content = request_messages[-1]["content"] + [{"type": "input_image", "image_url": image}]
all_input.append({"role": "user", "content": user_content})
else:
all_input.append(request_messages[-1])
A continuación, bp.openai_client.responses.create llama a la API de respuestas de OpenAI Azure y transmite la respuesta. El store=False parámetro indica a la API que no almacene respuestas en el servidor, lo que hace que la llamada no tenga estado.
openai_stream = await bp.openai_client.responses.create(
model=bp.model_name,
input=all_input,
stream=True,
temperature=0.3,
store=False,
)
Por último, la respuesta se devuelve al cliente. La API de respuestas emite muchos tipos de eventos, pero solo se necesita el evento para transmitir response.output_text.delta texto generado. Los eventos de error se registran y se reenvían a la interfaz de usuario.
try:
async for event in openai_stream:
if event.type == "response.output_text.delta":
yield json.dumps({"type": event.type, "delta": event.delta}, ensure_ascii=False) + "\n"
elif event.type in ("response.failed", "error"):
current_app.logger.error("Responses API error: %s", event)
yield json.dumps({"type": event.type}, ensure_ascii=False) + "\n"
except Exception as e:
current_app.logger.exception("Error in response stream")
yield json.dumps({"error": str(e)}, ensure_ascii=False) + "\n"
return Response(response_stream())
Bibliotecas y características de front-end
El front-end usa las API y bibliotecas modernas del explorador para crear una experiencia de chat interactiva. Los desarrolladores pueden personalizar la interfaz o agregar características mediante la comprensión de estos componentes:
Entrada y salida de voz: los componentes web personalizados usan las API de voz del explorador:
<speech-input-button>: convierte la voz en texto mediante la API deSpeechRecognitionVoz Web. Proporciona un botón de micrófono que escucha la entrada de voz y emite unspeech-input-resultevento con el texto transcrito.<speech-output-button>: lee el texto en voz alta mediante laSpeechSynthesisAPI. Aparece después de cada respuesta del asistente con un icono de altavoz, lo que permite a los usuarios escuchar la respuesta.
¿Por qué usar las API del navegador en lugar de Azure Speech Services?
- Sin costo: se ejecuta completamente en el explorador
- Respuesta instantánea: sin latencia de red
- Privacidad: los datos de voz permanecen en el dispositivo del usuario
- No es necesario usar recursos adicionales de Azure
Estos componentes están en
src/quartapp/static/speech-input.jsyspeech-output.js.Vista previa de la imagen: muestra la imagen cargada en el chat antes de enviar el análisis para su confirmación. La vista previa se actualiza automáticamente cuando se selecciona un archivo.
fileInput.addEventListener("change", async function() { const file = fileInput.files[0]; if (file) { const fileData = await toBase64(file); imagePreview.src = fileData; imagePreview.style.display = "block"; } });Bootstrap 5 e Iconos de Bootstrap: proporciona componentes de interfaz de usuario adaptables e iconos. La aplicación usa el tema Cosmo de Bootswatch para un aspecto moderno.
Representación de mensajes basada en plantillas: usa elementos HTML
<template>para diseños de mensajes reutilizables, lo que garantiza un estilo y una estructura coherentes.
Otros recursos de ejemplo que se van a explorar
Además del ejemplo de aplicación de chat, hay otros recursos en el repositorio para explorar para obtener más información. Consulte los cuadernos siguientes en el notebooks directorio :
| Notebook | Descripción |
|---|---|
| chat_pdf_images.ipynb | En este cuaderno se muestra cómo convertir páginas PDF en imágenes y enviarlos a un modelo de visión para la inferencia. |
| chat_vision.ipynb | Este cuaderno se proporciona para la experimentación manual con el modelo de visión que se usa en la aplicación. |
Contenido localizado: las versiones en español de los cuadernos están en el notebooks/Spanish/ directorio y ofrecen el mismo aprendizaje práctico para desarrolladores de habla española. Los cuadernos inglés y español muestran:
- Cómo llamar a modelos de visión directamente para la experimentación
- Cómo convertir páginas PDF en imágenes para el análisis
- Cómo ajustar parámetros e indicaciones de prueba
Limpieza de recursos
Limpieza de recursos de Azure
Los recursos de Azure creados en este artículo se facturan a su suscripción de Azure. Si no espera necesitar estos recursos en el futuro, elimínelos para evitar incurrir en más gastos.
Para eliminar los recursos de Azure y quitar el código fuente, ejecute el siguiente comando de la CLI para desarrolladores de Azure:
azd down --purge
Limpieza de GitHub Codespaces
Al eliminar el entorno de GitHub Codespaces, se asegura que puedas maximizar la cantidad de horas gratuitas por núcleo disponibles para tu cuenta.
Importante
Para obtener más información sobre los derechos de su cuenta de GitHub, consulte almacenamiento y horas centrales mensuales incluidas en GitHub Codespaces.
Inicie sesión en el panel GitHub Codespaces.
Localice los Codespaces que se ejecutan actualmente en el repositorio de
Azure-Samples//openai-chat-vision-quickstartGitHub.Abra el menú contextual delcodespace y seleccione Eliminar.
Obtener ayuda
Registre su problema en los Issues del repositorio.