Menu iconMenu icon
OpenAI API Biblia Volumen 2

Capítulo 5: Proyectos de Integración de Imagen y Audio

5.5 Integración Básica de Múltiples Modalidades

En esta sección, exploraremos cómo combinar múltiples modalidades de IA - habla, comprensión del lenguaje y generación de imágenes - en una única aplicación cohesiva. Mientras que las secciones anteriores se centraron en trabajar con tecnologías individuales, aquí aprenderemos cómo estas poderosas herramientas pueden trabajar juntas para crear experiencias de usuario más sofisticadas y atractivas.

Esta integración representa un avance significativo en el desarrollo de aplicaciones de IA, yendo más allá de simples herramientas de propósito único para crear sistemas que pueden procesar y responder a través de diferentes formas de comunicación. Al combinar las capacidades de transcripción de audio de Whisper, la comprensión del lenguaje natural de GPT-4o y la generación de imágenes de DALL·E, podemos construir aplicaciones que verdaderamente demuestran el potencial de las tecnologías modernas de IA.

El proyecto que construiremos sirve como una excelente introducción a la integración multimodal de IA, demostrando cómo diferentes modelos de IA pueden ser orquestados para crear una experiencia fluida. Este enfoque abre posibilidades emocionantes para desarrolladores que buscan crear interacciones humano-IA más naturales e intuitivas.

5.5.1 Lo que Construirás

En esta sección, crearás una aplicación web sofisticada basada en Flask que funciona como un asistente multimodal básico. Esta aplicación demuestra la integración perfecta de múltiples tecnologías de IA para crear una experiencia de usuario interactiva e inteligente. El asistente es capaz de:

  • Aceptar un mensaje de audio del usuario a través de una interfaz web limpia, compatible con varios formatos de audio
  • Aprovechar la tecnología Whisper de OpenAI para transcribir con precisión el mensaje de audio a texto, manejando diferentes acentos e idiomas
  • Utilizar las capacidades avanzadas de procesamiento del lenguaje natural de GPT-4o para analizar el texto transcrito, comprendiendo el contexto, la intención y los temas clave
  • Emplear las sofisticadas capacidades de generación de imágenes de DALL·E 3 para crear visuales relevantes y de alta calidad basados en el contexto comprendido
  • Presentar una experiencia de usuario cohesiva mostrando tanto la transcripción original como la imagen generada por IA en una única página web bien diseñada

Este proyecto sirve como un excelente ejemplo de un asistente multimodal que combina perfectamente tres tipos diferentes de procesamiento de IA: procesamiento de audio, comprensión del lenguaje natural y generación de imágenes. Al procesar la entrada de audio, extraer contexto significativo y crear representaciones visuales, demuestra el potencial de las tecnologías de IA integradas. Esta base abre emocionantes posibilidades para varias aplicaciones del mundo real, como:

  • Instrucciones de diseño habladas para creadores visuales - permitiendo a artistas y diseñadores describir verbalmente su visión y verla renderizada al instante
  • Diario en audio con salida ilustrada - transformando entradas de diario habladas en memorias visuales con obras de arte generadas por IA correspondientes
  • Aplicaciones de narración controladas por voz - creando narrativas interactivas donde las palabras habladas cobran vida a través de la generación visual instantánea

5.5.2 Implementación Paso a Paso

Paso 1: Instalar Paquetes Requeridos

Descarga el audio de muestra: https://files.cuantum.tech/audio/audio-file-sample.mp3

Asegúrate de tener las bibliotecas de Python necesarias instaladas. Abre tu terminal y ejecuta el siguiente comando:

pip install flask openai python-dotenv

Este comando instala:

  • flask: Un micro framework web para construir la aplicación web.
  • openai: La biblioteca de Python de OpenAI para interactuar con las APIs de Whisper, Chat Completion y DALL·E 3.
  • python-dotenv: Una biblioteca para cargar variables de entorno desde un archivo .env.

Paso 2: Configurar la Estructura del Proyecto

Crea la siguiente estructura de carpetas para tu proyecto:

/multimodal_app

├── app.py
├── .env
└── templates/
    └── index.html
  • /multimodal_app: El directorio raíz de tu proyecto.
  • app.py: El archivo Python que contiene el código de la aplicación Flask.
  • .env: Un archivo para almacenar tu clave de API de OpenAI.
  • templates/: Un directorio para almacenar tus plantillas HTML.
  • templates/index.html: La plantilla HTML para la página principal de tu aplicación.

Paso 3: Crear la Aplicación Flask (app.py)

Crea un archivo Python llamado app.py en el directorio raíz de tu proyecto y agrega el siguiente código:

from flask import Flask, request, render_template, jsonify, make_response
import openai
import os
from dotenv import load_dotenv
import logging
from typing import Optional, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

app = Flask(__name__)

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

ALLOWED_EXTENSIONS = {'mp3', 'mp4', 'wav', 'm4a'}  # Allowed audio file extensions


def allowed_file(filename: str) -> bool:
    """
    Checks if the uploaded file has an allowed extension.

    Args:
        filename (str): The name of the file.

    Returns:
        bool: True if the file has an allowed extension, False otherwise.
    """
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


def transcribe_audio(file_path: str) -> Optional[str]:
    """
    Transcribes an audio file using OpenAI's Whisper API.

    Args:
        file_path (str): The path to the audio file.

    Returns:
        Optional[str]: The transcribed text, or None on error.
    """
    try:
        logger.info(f"Transcribing audio file: {file_path}")
        audio_file = open(file_path, "rb")
        response = openai.Audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
        )
        transcript = response.text
        logger.info(f"Transcription successful. Length: {len(transcript)} characters.")
        return transcript
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error during transcription: {e}")
        return None


def generate_image_prompt(text: str) -> Optional[str]:
    """
    Generates a prompt for DALL·E 3 based on the transcribed text using GPT-4o.

    Args:
        text (str): The transcribed text.

    Returns:
        Optional[str]: The generated image prompt, or None on error.
    """
    try:
        logger.info("Generating image prompt using GPT-4o")
        response = openai.chat.completions.create(
            model="gpt-4o",  # You can also experiment with other chat models
            messages=[
                {
                    "role": "system",
                    "content": "You are a creative assistant. Your task is to create a vivid and detailed text description of a scene that could be used to generate an image with an AI image generation model. Focus on capturing the essence and key visual elements of the audio content.  Do not include any phrases like 'based on the audio' or 'from the user audio'.",
                },
                {"role": "user", "content": text},
            ],
        )
        prompt = response.choices[0].message.content
        logger.info(f"Generated image prompt: {prompt}")
        return prompt
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image prompt: {e}")
        return None


def generate_image(prompt: str, model: str = "dall-e-3", size: str = "1024x1024", response_format: str = "url") -> Optional[str]:
    """
    Generates an image using OpenAI's DALL·E API.

    Args:
        prompt (str): The text prompt to generate the image from.
        model (str, optional): The DALL·E model to use. Defaults to "dall-e-3".
        size (str, optional): The size of the generated image. Defaults to "1024x1024".
        response_format (str, optional): The format of the response. Defaults to "url".

    Returns:
        Optional[str]: The URL of the generated image, or None on error.
    """
    try:
        logger.info(f"Generating image with prompt: {prompt}, model: {model}, size: {size}, format: {response_format}")
        response = openai.Image.create(
            prompt=prompt,
            model=model,
            size=size,
            response_format=response_format,
        )
        image_url = response.data[0].url
        logger.info(f"Image URL: {image_url}")
        return image_url
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image: {e}")
        return None



@app.route("/", methods=["GET", "POST"])
def index():
    """
    Handles the main route for the web application.
    Processes audio uploads, transcribes them, generates image prompts, and displays images.
    """
    transcript = None
    image_url = None
    prompt_summary = None
    error_message = None

    if request.method == "POST":
        if 'audio_file' not in request.files:
            error_message = "No file part"
            logger.warning(error_message)
            return render_template("index.html", error=error_message)
        file = request.files['audio_file']
        if file.filename == '':
            error_message = "No file selected"
            logger.warning(error_message)
            return render_template("index.html", error=error_message)

        if file and allowed_file(file.filename):
            try:
                # Securely save the uploaded file to a temporary location
                temp_file_path = os.path.join(app.root_path, "temp_audio." + file.filename.rsplit('.', 1)[1].lower())
                file.save(temp_file_path)

                transcript = transcribe_audio(temp_file_path)  # Transcribe audio
                if not transcript:
                    error_message = "Audio transcription failed. Please try again."
                    return render_template("index.html", error=error_message)

                prompt_summary = generate_image_prompt(transcript)  # Generate prompt
                if not prompt_summary:
                    error_message = "Failed to generate image prompt. Please try again."
                    return render_template("index.html", error=error_message)

                image_url = generate_image(prompt_summary)  # Generate image
                if not image_url:
                    error_message = "Failed to generate image. Please try again."
                    return render_template("index.html", error=error_message)

                # Optionally, delete the temporary file after processing
                os.remove(temp_file_path)

            except Exception as e:
                error_message = f"An error occurred: {e}"
                logger.error(error_message)
                return render_template("index.html", error=error_message)
        else:
            error_message = "Invalid file type. Please upload a valid audio file (MP3, MP4, WAV, M4A)."
            logger.warning(error_message)
            return render_template("index.html", error=error_message)

    return render_template("index.html", transcript=transcript, image_url=image_url, prompt_summary=prompt_summary, error=error_message)



@app.errorhandler(500)
def internal_server_error(e):
    """Handles internal server errors."""
    logger.error(f"Internal Server Error: {e}")
    return render_template("error.html", error="Internal Server Error"), 500


if __name__ == "__main__":
    app.run(debug=True)

Desglose del Código:

  • Declaraciones de Importación: Importa los módulos necesarios de Flask, la biblioteca OpenAI, osdotenvlogging, y Optional y Dict para la tipificación.
  • Variables de Entorno: Carga la clave API de OpenAI desde el archivo .env.
  • Aplicación Flask: Crea una instancia de la aplicación Flask.
  • Configuración de Registro: Configura el sistema de registro.
  • Función allowed_file: Verifica si el archivo subido tiene una extensión de audio permitida.
  • Función transcribe_audio:
    • Recibe la ruta del archivo de audio como entrada.
    • Abre el archivo de audio en modo binario ("rb").
    • Llama al método openai.Audio.transcribe() de la API de OpenAI para transcribir el audio.
    • Extrae el texto transcrito de la respuesta.
    • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
    • Incluye manejo de errores para errores de la API de OpenAI y otras excepciones.
  • Función generate_image_prompt:
    • Recibe el texto transcrito como entrada.
    • Utiliza la API de Chat Completion de OpenAI (openai.chat.completions.create()) con el modelo gpt-4o para generar un texto apropiado para la generación de imágenes.
    • El mensaje del sistema instruye al modelo para actuar como un asistente creativo y proporcionar una descripción vívida de una escena basada en el audio.
    • Extrae el texto generado de la respuesta de la API.
    • Incluye manejo de errores.
  • Función generate_image:
    • Recibe el texto para generar la imagen como entrada.
    • Llama al método openai.Image.create() de la API de OpenAI para generar una imagen usando DALL·E 3.
    • Extrae la URL de la imagen de la respuesta de la API.
    • Incluye manejo de errores.
  • Ruta index:
    • Maneja las peticiones GET y POST.
    • Para peticiones GET, renderiza la página HTML inicial.
    • Para peticiones POST (cuando el usuario sube un archivo de audio):
      • Valida el archivo subido.
      • Guarda el archivo subido temporalmente.
      • Llama a transcribe_audio() para transcribir el audio.
      • Llama a generate_image_prompt() para generar un texto para la imagen a partir de la transcripción.
      • Llama a generate_image() para generar una imagen a partir del texto.
      • Renderiza la plantilla index.html, pasando el texto de la transcripción y la URL de la imagen.
    • Incluye manejo exhaustivo de errores para capturar posibles problemas durante la subida de archivos, transcripción, generación de texto y generación de imágenes.
  • @app.errorhandler(500): Maneja errores HTTP 500 (Error Interno del Servidor) registrando el error y mostrando una página de error amigable para el usuario.
  • if __name__ == "__main__":: Inicia el servidor de desarrollo Flask si el script se ejecuta directamente.

Paso 4: Crear Plantilla HTML (templates/index.html)

Crea una carpeta llamada templates en el mismo directorio que app.py. Dentro de la carpeta templates, crea un archivo llamado index.html con el siguiente código HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Multimodal Assistant</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
    <style>
        /* --- General Styles --- */
        body {
            font-family: 'Inter', sans-serif;
            padding: 40px;
            background-color: #f9fafb; /* Tailwind's gray-50 */
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            color: #374151; /* Tailwind's gray-700 */
        }
        .container {
            max-width: 800px; /* Increased max-width */
            width: 95%; /* Take up most of the viewport */
            background-color: #fff;
            padding: 2rem;
            border-radius: 0.75rem; /* Tailwind's rounded-lg */
            box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.05); /* Tailwind's shadow-xl */
            text-align: center;
        }
        h2 {
            font-size: 2.25rem; /* Tailwind's text-3xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1.5rem; /* Tailwind's mb-6 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        p{
            color: #6b7280; /* Tailwind's gray-500 */
            margin-bottom: 1rem;
        }

        /* --- Form Styles --- */
        form {
            margin-top: 1rem; /* Tailwind's mt-4 */
            display: flex;
            flex-direction: column;
            align-items: center; /* Center form elements */
            gap: 0.5rem; /* Tailwind's gap-2 */
        }
        label {
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600;  /* Tailwind's font-semibold */
            color: #4b5563; /* Tailwind's gray-600 */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            display: block; /* Ensure label takes full width */
            text-align: left;
            width: 100%;
            max-width: 400px; /* Added max-width for label */
            margin-left: auto;
            margin-right: auto;
        }
        input[type="file"] {
            width: 100%;
            max-width: 400px; /* Added max-width for file input */
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            font-size: 1rem; /* Tailwind's text-base */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            margin-left: auto;
            margin-right: auto;

        }
        input[type="submit"] {
            padding: 0.75rem 1.5rem; /* Tailwind's px-6 py-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #4f46e5; /* Tailwind's bg-indigo-500 */
            color: #fff;
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600; /* Tailwind's font-semibold */
            cursor: pointer;
            transition: background-color 0.3s ease; /* Smooth transition */
            border: none;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Subtle shadow */
            margin-top: 1rem;
        }
        input[type="submit"]:hover {
            background-color: #4338ca; /* Tailwind's bg-indigo-700 on hover */
        }
        input[type="submit"]:focus {
            outline: none;
            box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3); /* Tailwind's ring-indigo-500 */
        }

        /* --- Result Styles --- */
        .result-container {
            margin-top: 2rem; /* Tailwind's mt-8 */
            padding: 1.5rem; /* Tailwind's p-6 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #f8fafc; /* Tailwind's bg-gray-50 */
            border: 1px solid #e2e8f0; /* Tailwind's border-gray-200 */
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */
        }
        .transcript-container{
            margin-top: 2rem; /* Tailwind's mt-8 */
            padding: 1.5rem; /* Tailwind's p-6 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #f8fafc; /* Tailwind's bg-gray-50 */
            border: 1px solid #e2e8f0; /* Tailwind's border-gray-200 */
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */

        }

        h3 {
            font-size: 1.5rem; /* Tailwind's text-2xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1rem; /* Tailwind's mb-4 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        textarea {
            width: 100%;
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            resize: none;
            font-size: 1rem; /* Tailwind's text-base */
            line-height: 1.5rem; /* Tailwind's leading-relaxed */
            margin-top: 0.5rem; /* Tailwind's mt-2 */
            margin-bottom: 0;
            box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); /* Inner shadow */
        }
        textarea:focus {
            outline: none;
            border-color: #3b82f6; /* Tailwind's border-blue-500 */
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); /* Tailwind's ring-blue-500 */
        }
        img {
            max-width: 100%;
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            margin-top: 1.5rem; /* Tailwind's mt-6 */
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* Tailwind's shadow-md */
        }

        /* --- Error Styles --- */
        .error-message {
            color: #dc2626; /* Tailwind's text-red-600 */
            margin-top: 1rem; /* Tailwind's mt-4 */
            padding: 0.75rem;
            background-color: #fee2e2; /* Tailwind's bg-red-100 */
            border-radius: 0.375rem; /* Tailwind's rounded-md */
            border: 1px solid #fecaca; /* Tailwind's border-red-300 */
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="container">
        <h2>🎤🧠🎨 Multimodal Assistant</h2>
        <p> Upload an audio file to transcribe and generate a corresponding image. </p>
        <form method="POST" enctype="multipart/form-data">
            <label for="audio_file">Upload your voice note:</label><br>
            <input type="file" name="audio_file" accept="audio/*" required><br><br>
            <input type="submit" value="Generate Visual Response">
        </form>

        {% if transcript %}
            <div class = "transcript-container">
                <h3>📝 Transcript:</h3>
                <textarea readonly>{{ transcript }}</textarea>
            </div>
        {% endif %}

        {% if prompt_summary %}
            <div class = "result-container">
                <h3>🎯 Prompt Used for Image:</h3>
                <p>{{ prompt_summary }}</p>
            </div>
        {% endif %}

        {% if image_url %}
            <div class = "result-container">
                <h3>🖼️ Generated Image:</h3>
                <img src="{{ image_url }}" alt="Generated image">
            </div>
        {% endif %}
        {% if error %}
            <div class="error-message">{{ error }}</div>
        {% endif %}
    </div>
</body>
</html>

Elementos clave en la plantilla HTML:

  • Estructura HTML:
    • La sección <head> define el título, enlaza una hoja de estilos CSS y establece el viewport para hacer el diseño adaptable.
    • El <body> contiene el contenido visible, incluyendo un formulario para subir archivos de audio y secciones para mostrar la transcripción y la imagen generada.
  • Estilo CSS:
    • Diseño moderno: El CSS se ha actualizado con un diseño moderno, similar al estilo de Tailwind CSS.
    • Diseño responsivo: El diseño es más adaptable, especialmente para pantallas pequeñas.
    • Experiencia de usuario: Mejora en el estilo del formulario y de los campos de entrada.
    • Visualización clara de errores: Los mensajes de error están estilizados para ser claramente visibles.
  • Formulario:
    • Se utiliza un <form> con enctype="multipart/form-data" para gestionar la carga de archivos.
    • Un <label> y un <input type="file"> permiten al usuario seleccionar un archivo de audio. El atributo accept="audio/*" restringe la selección a archivos de audio.
    • Un botón <input type="submit"> permite al usuario enviar el formulario.
  • Visualización de la transcripción e imagen:
    • La plantilla usa Jinja2 para mostrar condicionalmente el texto transcrito y la imagen generada, si están disponibles. La transcripción se muestra en un textarea y la imagen mediante una etiqueta <img>.
  • Manejo de errores:
    • Un <div class="error-message"> se utiliza para mostrar cualquier mensaje de error al usuario.

Pruébalo

  1. Guarda los archivos como app.py y templates/index.html.
  2. Asegúrate de tener tu clave de API de OpenAI en el archivo .env.
  3. Ejecuta la aplicación:
    python app.py
  4. Abre http://localhost:5000 en tu navegador.
  5. Sube un archivo de audio (puedes usar el archivo de muestra .mp3 proporcionado).
  6. Visualiza la transcripción y la imagen generada en la página.

5.5.3 Cómo Funciona (Detrás de Escena)

Esto demuestra la orquestación multimodal en acción - un proceso sofisticado donde diferentes modelos de IA colaboran a través de la capa lógica de tu aplicación. Cada modelo se especializa en una forma diferente de procesamiento de datos (audio, texto e imagen), y juntos crean una experiencia fluida. La aplicación coordina estos modelos, manejando la transformación de datos entre cada paso y asegurando un flujo de comunicación adecuado.

Flujo de Ejemplo: Una Explicación Detallada

Para entender mejor cómo funciona este sistema multimodal, analicemos un ejemplo completo. Cuando un usuario sube una nota de voz diciendo:

"Tuve la mañana más tranquila — sentado junto a un lago con pájaros cantando y el sol saliendo detrás de los árboles."

El sistema procesa esta entrada a través de tres etapas distintas:

  1. Procesamiento de Audio con Transcripción: Primero, Whisper convierte el audio a texto, manteniendo la precisión incluso con ruido de fondo o variaciones de acento. Resultado: "Tuve la mañana más tranquila..."
  2. Análisis y Mejora de la Escena: GPT-4o analiza el texto transcrito, identificando elementos visuales clave y relaciones espaciales para crear un prompt optimizado para la imagen. Resultado: "Un amanecer sobre un lago tranquilo, con pájaros en el cielo y árboles reflejándose en el agua"
  3. Creación Visual: DALL·E toma este prompt refinado y genera una imagen fotorrealista, equilibrando cuidadosamente todos los elementos descritos en una escena coherente

El Poder de la Integración

En esta sección final del capítulo, creaste un mini-asistente multimodal que demuestra la integración perfecta de tres capacidades distintas de IA:

  • Whisper: Reconocimiento de voz avanzado que maneja varios acentos, idiomas y calidades de audio con notable precisión
  • GPT-4o: Procesamiento sofisticado del lenguaje que comprende el contexto, la emoción y la composición de la escena para crear descripciones detalladas de imágenes
  • DALL·E: Generación de imágenes de última generación que traduce descripciones textuales en escenas visuales vívidas y coherentes

Esta integración muestra el futuro de las aplicaciones de IA donde múltiples modelos trabajan en conjunto para procesar, comprender y responder a la entrada del usuario de formas ricas y significativas. Al orquestar estos modelos juntos, has creado una interfaz intuitiva que reduce la brecha entre la comunicación humana y las capacidades de la IA.

5.5 Integración Básica de Múltiples Modalidades

En esta sección, exploraremos cómo combinar múltiples modalidades de IA - habla, comprensión del lenguaje y generación de imágenes - en una única aplicación cohesiva. Mientras que las secciones anteriores se centraron en trabajar con tecnologías individuales, aquí aprenderemos cómo estas poderosas herramientas pueden trabajar juntas para crear experiencias de usuario más sofisticadas y atractivas.

Esta integración representa un avance significativo en el desarrollo de aplicaciones de IA, yendo más allá de simples herramientas de propósito único para crear sistemas que pueden procesar y responder a través de diferentes formas de comunicación. Al combinar las capacidades de transcripción de audio de Whisper, la comprensión del lenguaje natural de GPT-4o y la generación de imágenes de DALL·E, podemos construir aplicaciones que verdaderamente demuestran el potencial de las tecnologías modernas de IA.

El proyecto que construiremos sirve como una excelente introducción a la integración multimodal de IA, demostrando cómo diferentes modelos de IA pueden ser orquestados para crear una experiencia fluida. Este enfoque abre posibilidades emocionantes para desarrolladores que buscan crear interacciones humano-IA más naturales e intuitivas.

5.5.1 Lo que Construirás

En esta sección, crearás una aplicación web sofisticada basada en Flask que funciona como un asistente multimodal básico. Esta aplicación demuestra la integración perfecta de múltiples tecnologías de IA para crear una experiencia de usuario interactiva e inteligente. El asistente es capaz de:

  • Aceptar un mensaje de audio del usuario a través de una interfaz web limpia, compatible con varios formatos de audio
  • Aprovechar la tecnología Whisper de OpenAI para transcribir con precisión el mensaje de audio a texto, manejando diferentes acentos e idiomas
  • Utilizar las capacidades avanzadas de procesamiento del lenguaje natural de GPT-4o para analizar el texto transcrito, comprendiendo el contexto, la intención y los temas clave
  • Emplear las sofisticadas capacidades de generación de imágenes de DALL·E 3 para crear visuales relevantes y de alta calidad basados en el contexto comprendido
  • Presentar una experiencia de usuario cohesiva mostrando tanto la transcripción original como la imagen generada por IA en una única página web bien diseñada

Este proyecto sirve como un excelente ejemplo de un asistente multimodal que combina perfectamente tres tipos diferentes de procesamiento de IA: procesamiento de audio, comprensión del lenguaje natural y generación de imágenes. Al procesar la entrada de audio, extraer contexto significativo y crear representaciones visuales, demuestra el potencial de las tecnologías de IA integradas. Esta base abre emocionantes posibilidades para varias aplicaciones del mundo real, como:

  • Instrucciones de diseño habladas para creadores visuales - permitiendo a artistas y diseñadores describir verbalmente su visión y verla renderizada al instante
  • Diario en audio con salida ilustrada - transformando entradas de diario habladas en memorias visuales con obras de arte generadas por IA correspondientes
  • Aplicaciones de narración controladas por voz - creando narrativas interactivas donde las palabras habladas cobran vida a través de la generación visual instantánea

5.5.2 Implementación Paso a Paso

Paso 1: Instalar Paquetes Requeridos

Descarga el audio de muestra: https://files.cuantum.tech/audio/audio-file-sample.mp3

Asegúrate de tener las bibliotecas de Python necesarias instaladas. Abre tu terminal y ejecuta el siguiente comando:

pip install flask openai python-dotenv

Este comando instala:

  • flask: Un micro framework web para construir la aplicación web.
  • openai: La biblioteca de Python de OpenAI para interactuar con las APIs de Whisper, Chat Completion y DALL·E 3.
  • python-dotenv: Una biblioteca para cargar variables de entorno desde un archivo .env.

Paso 2: Configurar la Estructura del Proyecto

Crea la siguiente estructura de carpetas para tu proyecto:

/multimodal_app

├── app.py
├── .env
└── templates/
    └── index.html
  • /multimodal_app: El directorio raíz de tu proyecto.
  • app.py: El archivo Python que contiene el código de la aplicación Flask.
  • .env: Un archivo para almacenar tu clave de API de OpenAI.
  • templates/: Un directorio para almacenar tus plantillas HTML.
  • templates/index.html: La plantilla HTML para la página principal de tu aplicación.

Paso 3: Crear la Aplicación Flask (app.py)

Crea un archivo Python llamado app.py en el directorio raíz de tu proyecto y agrega el siguiente código:

from flask import Flask, request, render_template, jsonify, make_response
import openai
import os
from dotenv import load_dotenv
import logging
from typing import Optional, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

app = Flask(__name__)

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

ALLOWED_EXTENSIONS = {'mp3', 'mp4', 'wav', 'm4a'}  # Allowed audio file extensions


def allowed_file(filename: str) -> bool:
    """
    Checks if the uploaded file has an allowed extension.

    Args:
        filename (str): The name of the file.

    Returns:
        bool: True if the file has an allowed extension, False otherwise.
    """
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


def transcribe_audio(file_path: str) -> Optional[str]:
    """
    Transcribes an audio file using OpenAI's Whisper API.

    Args:
        file_path (str): The path to the audio file.

    Returns:
        Optional[str]: The transcribed text, or None on error.
    """
    try:
        logger.info(f"Transcribing audio file: {file_path}")
        audio_file = open(file_path, "rb")
        response = openai.Audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
        )
        transcript = response.text
        logger.info(f"Transcription successful. Length: {len(transcript)} characters.")
        return transcript
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error during transcription: {e}")
        return None


def generate_image_prompt(text: str) -> Optional[str]:
    """
    Generates a prompt for DALL·E 3 based on the transcribed text using GPT-4o.

    Args:
        text (str): The transcribed text.

    Returns:
        Optional[str]: The generated image prompt, or None on error.
    """
    try:
        logger.info("Generating image prompt using GPT-4o")
        response = openai.chat.completions.create(
            model="gpt-4o",  # You can also experiment with other chat models
            messages=[
                {
                    "role": "system",
                    "content": "You are a creative assistant. Your task is to create a vivid and detailed text description of a scene that could be used to generate an image with an AI image generation model. Focus on capturing the essence and key visual elements of the audio content.  Do not include any phrases like 'based on the audio' or 'from the user audio'.",
                },
                {"role": "user", "content": text},
            ],
        )
        prompt = response.choices[0].message.content
        logger.info(f"Generated image prompt: {prompt}")
        return prompt
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image prompt: {e}")
        return None


def generate_image(prompt: str, model: str = "dall-e-3", size: str = "1024x1024", response_format: str = "url") -> Optional[str]:
    """
    Generates an image using OpenAI's DALL·E API.

    Args:
        prompt (str): The text prompt to generate the image from.
        model (str, optional): The DALL·E model to use. Defaults to "dall-e-3".
        size (str, optional): The size of the generated image. Defaults to "1024x1024".
        response_format (str, optional): The format of the response. Defaults to "url".

    Returns:
        Optional[str]: The URL of the generated image, or None on error.
    """
    try:
        logger.info(f"Generating image with prompt: {prompt}, model: {model}, size: {size}, format: {response_format}")
        response = openai.Image.create(
            prompt=prompt,
            model=model,
            size=size,
            response_format=response_format,
        )
        image_url = response.data[0].url
        logger.info(f"Image URL: {image_url}")
        return image_url
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image: {e}")
        return None



@app.route("/", methods=["GET", "POST"])
def index():
    """
    Handles the main route for the web application.
    Processes audio uploads, transcribes them, generates image prompts, and displays images.
    """
    transcript = None
    image_url = None
    prompt_summary = None
    error_message = None

    if request.method == "POST":
        if 'audio_file' not in request.files:
            error_message = "No file part"
            logger.warning(error_message)
            return render_template("index.html", error=error_message)
        file = request.files['audio_file']
        if file.filename == '':
            error_message = "No file selected"
            logger.warning(error_message)
            return render_template("index.html", error=error_message)

        if file and allowed_file(file.filename):
            try:
                # Securely save the uploaded file to a temporary location
                temp_file_path = os.path.join(app.root_path, "temp_audio." + file.filename.rsplit('.', 1)[1].lower())
                file.save(temp_file_path)

                transcript = transcribe_audio(temp_file_path)  # Transcribe audio
                if not transcript:
                    error_message = "Audio transcription failed. Please try again."
                    return render_template("index.html", error=error_message)

                prompt_summary = generate_image_prompt(transcript)  # Generate prompt
                if not prompt_summary:
                    error_message = "Failed to generate image prompt. Please try again."
                    return render_template("index.html", error=error_message)

                image_url = generate_image(prompt_summary)  # Generate image
                if not image_url:
                    error_message = "Failed to generate image. Please try again."
                    return render_template("index.html", error=error_message)

                # Optionally, delete the temporary file after processing
                os.remove(temp_file_path)

            except Exception as e:
                error_message = f"An error occurred: {e}"
                logger.error(error_message)
                return render_template("index.html", error=error_message)
        else:
            error_message = "Invalid file type. Please upload a valid audio file (MP3, MP4, WAV, M4A)."
            logger.warning(error_message)
            return render_template("index.html", error=error_message)

    return render_template("index.html", transcript=transcript, image_url=image_url, prompt_summary=prompt_summary, error=error_message)



@app.errorhandler(500)
def internal_server_error(e):
    """Handles internal server errors."""
    logger.error(f"Internal Server Error: {e}")
    return render_template("error.html", error="Internal Server Error"), 500


if __name__ == "__main__":
    app.run(debug=True)

Desglose del Código:

  • Declaraciones de Importación: Importa los módulos necesarios de Flask, la biblioteca OpenAI, osdotenvlogging, y Optional y Dict para la tipificación.
  • Variables de Entorno: Carga la clave API de OpenAI desde el archivo .env.
  • Aplicación Flask: Crea una instancia de la aplicación Flask.
  • Configuración de Registro: Configura el sistema de registro.
  • Función allowed_file: Verifica si el archivo subido tiene una extensión de audio permitida.
  • Función transcribe_audio:
    • Recibe la ruta del archivo de audio como entrada.
    • Abre el archivo de audio en modo binario ("rb").
    • Llama al método openai.Audio.transcribe() de la API de OpenAI para transcribir el audio.
    • Extrae el texto transcrito de la respuesta.
    • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
    • Incluye manejo de errores para errores de la API de OpenAI y otras excepciones.
  • Función generate_image_prompt:
    • Recibe el texto transcrito como entrada.
    • Utiliza la API de Chat Completion de OpenAI (openai.chat.completions.create()) con el modelo gpt-4o para generar un texto apropiado para la generación de imágenes.
    • El mensaje del sistema instruye al modelo para actuar como un asistente creativo y proporcionar una descripción vívida de una escena basada en el audio.
    • Extrae el texto generado de la respuesta de la API.
    • Incluye manejo de errores.
  • Función generate_image:
    • Recibe el texto para generar la imagen como entrada.
    • Llama al método openai.Image.create() de la API de OpenAI para generar una imagen usando DALL·E 3.
    • Extrae la URL de la imagen de la respuesta de la API.
    • Incluye manejo de errores.
  • Ruta index:
    • Maneja las peticiones GET y POST.
    • Para peticiones GET, renderiza la página HTML inicial.
    • Para peticiones POST (cuando el usuario sube un archivo de audio):
      • Valida el archivo subido.
      • Guarda el archivo subido temporalmente.
      • Llama a transcribe_audio() para transcribir el audio.
      • Llama a generate_image_prompt() para generar un texto para la imagen a partir de la transcripción.
      • Llama a generate_image() para generar una imagen a partir del texto.
      • Renderiza la plantilla index.html, pasando el texto de la transcripción y la URL de la imagen.
    • Incluye manejo exhaustivo de errores para capturar posibles problemas durante la subida de archivos, transcripción, generación de texto y generación de imágenes.
  • @app.errorhandler(500): Maneja errores HTTP 500 (Error Interno del Servidor) registrando el error y mostrando una página de error amigable para el usuario.
  • if __name__ == "__main__":: Inicia el servidor de desarrollo Flask si el script se ejecuta directamente.

Paso 4: Crear Plantilla HTML (templates/index.html)

Crea una carpeta llamada templates en el mismo directorio que app.py. Dentro de la carpeta templates, crea un archivo llamado index.html con el siguiente código HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Multimodal Assistant</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
    <style>
        /* --- General Styles --- */
        body {
            font-family: 'Inter', sans-serif;
            padding: 40px;
            background-color: #f9fafb; /* Tailwind's gray-50 */
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            color: #374151; /* Tailwind's gray-700 */
        }
        .container {
            max-width: 800px; /* Increased max-width */
            width: 95%; /* Take up most of the viewport */
            background-color: #fff;
            padding: 2rem;
            border-radius: 0.75rem; /* Tailwind's rounded-lg */
            box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.05); /* Tailwind's shadow-xl */
            text-align: center;
        }
        h2 {
            font-size: 2.25rem; /* Tailwind's text-3xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1.5rem; /* Tailwind's mb-6 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        p{
            color: #6b7280; /* Tailwind's gray-500 */
            margin-bottom: 1rem;
        }

        /* --- Form Styles --- */
        form {
            margin-top: 1rem; /* Tailwind's mt-4 */
            display: flex;
            flex-direction: column;
            align-items: center; /* Center form elements */
            gap: 0.5rem; /* Tailwind's gap-2 */
        }
        label {
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600;  /* Tailwind's font-semibold */
            color: #4b5563; /* Tailwind's gray-600 */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            display: block; /* Ensure label takes full width */
            text-align: left;
            width: 100%;
            max-width: 400px; /* Added max-width for label */
            margin-left: auto;
            margin-right: auto;
        }
        input[type="file"] {
            width: 100%;
            max-width: 400px; /* Added max-width for file input */
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            font-size: 1rem; /* Tailwind's text-base */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            margin-left: auto;
            margin-right: auto;

        }
        input[type="submit"] {
            padding: 0.75rem 1.5rem; /* Tailwind's px-6 py-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #4f46e5; /* Tailwind's bg-indigo-500 */
            color: #fff;
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600; /* Tailwind's font-semibold */
            cursor: pointer;
            transition: background-color 0.3s ease; /* Smooth transition */
            border: none;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Subtle shadow */
            margin-top: 1rem;
        }
        input[type="submit"]:hover {
            background-color: #4338ca; /* Tailwind's bg-indigo-700 on hover */
        }
        input[type="submit"]:focus {
            outline: none;
            box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3); /* Tailwind's ring-indigo-500 */
        }

        /* --- Result Styles --- */
        .result-container {
            margin-top: 2rem; /* Tailwind's mt-8 */
            padding: 1.5rem; /* Tailwind's p-6 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #f8fafc; /* Tailwind's bg-gray-50 */
            border: 1px solid #e2e8f0; /* Tailwind's border-gray-200 */
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */
        }
        .transcript-container{
            margin-top: 2rem; /* Tailwind's mt-8 */
            padding: 1.5rem; /* Tailwind's p-6 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #f8fafc; /* Tailwind's bg-gray-50 */
            border: 1px solid #e2e8f0; /* Tailwind's border-gray-200 */
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */

        }

        h3 {
            font-size: 1.5rem; /* Tailwind's text-2xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1rem; /* Tailwind's mb-4 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        textarea {
            width: 100%;
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            resize: none;
            font-size: 1rem; /* Tailwind's text-base */
            line-height: 1.5rem; /* Tailwind's leading-relaxed */
            margin-top: 0.5rem; /* Tailwind's mt-2 */
            margin-bottom: 0;
            box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); /* Inner shadow */
        }
        textarea:focus {
            outline: none;
            border-color: #3b82f6; /* Tailwind's border-blue-500 */
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); /* Tailwind's ring-blue-500 */
        }
        img {
            max-width: 100%;
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            margin-top: 1.5rem; /* Tailwind's mt-6 */
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* Tailwind's shadow-md */
        }

        /* --- Error Styles --- */
        .error-message {
            color: #dc2626; /* Tailwind's text-red-600 */
            margin-top: 1rem; /* Tailwind's mt-4 */
            padding: 0.75rem;
            background-color: #fee2e2; /* Tailwind's bg-red-100 */
            border-radius: 0.375rem; /* Tailwind's rounded-md */
            border: 1px solid #fecaca; /* Tailwind's border-red-300 */
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="container">
        <h2>🎤🧠🎨 Multimodal Assistant</h2>
        <p> Upload an audio file to transcribe and generate a corresponding image. </p>
        <form method="POST" enctype="multipart/form-data">
            <label for="audio_file">Upload your voice note:</label><br>
            <input type="file" name="audio_file" accept="audio/*" required><br><br>
            <input type="submit" value="Generate Visual Response">
        </form>

        {% if transcript %}
            <div class = "transcript-container">
                <h3>📝 Transcript:</h3>
                <textarea readonly>{{ transcript }}</textarea>
            </div>
        {% endif %}

        {% if prompt_summary %}
            <div class = "result-container">
                <h3>🎯 Prompt Used for Image:</h3>
                <p>{{ prompt_summary }}</p>
            </div>
        {% endif %}

        {% if image_url %}
            <div class = "result-container">
                <h3>🖼️ Generated Image:</h3>
                <img src="{{ image_url }}" alt="Generated image">
            </div>
        {% endif %}
        {% if error %}
            <div class="error-message">{{ error }}</div>
        {% endif %}
    </div>
</body>
</html>

Elementos clave en la plantilla HTML:

  • Estructura HTML:
    • La sección <head> define el título, enlaza una hoja de estilos CSS y establece el viewport para hacer el diseño adaptable.
    • El <body> contiene el contenido visible, incluyendo un formulario para subir archivos de audio y secciones para mostrar la transcripción y la imagen generada.
  • Estilo CSS:
    • Diseño moderno: El CSS se ha actualizado con un diseño moderno, similar al estilo de Tailwind CSS.
    • Diseño responsivo: El diseño es más adaptable, especialmente para pantallas pequeñas.
    • Experiencia de usuario: Mejora en el estilo del formulario y de los campos de entrada.
    • Visualización clara de errores: Los mensajes de error están estilizados para ser claramente visibles.
  • Formulario:
    • Se utiliza un <form> con enctype="multipart/form-data" para gestionar la carga de archivos.
    • Un <label> y un <input type="file"> permiten al usuario seleccionar un archivo de audio. El atributo accept="audio/*" restringe la selección a archivos de audio.
    • Un botón <input type="submit"> permite al usuario enviar el formulario.
  • Visualización de la transcripción e imagen:
    • La plantilla usa Jinja2 para mostrar condicionalmente el texto transcrito y la imagen generada, si están disponibles. La transcripción se muestra en un textarea y la imagen mediante una etiqueta <img>.
  • Manejo de errores:
    • Un <div class="error-message"> se utiliza para mostrar cualquier mensaje de error al usuario.

Pruébalo

  1. Guarda los archivos como app.py y templates/index.html.
  2. Asegúrate de tener tu clave de API de OpenAI en el archivo .env.
  3. Ejecuta la aplicación:
    python app.py
  4. Abre http://localhost:5000 en tu navegador.
  5. Sube un archivo de audio (puedes usar el archivo de muestra .mp3 proporcionado).
  6. Visualiza la transcripción y la imagen generada en la página.

5.5.3 Cómo Funciona (Detrás de Escena)

Esto demuestra la orquestación multimodal en acción - un proceso sofisticado donde diferentes modelos de IA colaboran a través de la capa lógica de tu aplicación. Cada modelo se especializa en una forma diferente de procesamiento de datos (audio, texto e imagen), y juntos crean una experiencia fluida. La aplicación coordina estos modelos, manejando la transformación de datos entre cada paso y asegurando un flujo de comunicación adecuado.

Flujo de Ejemplo: Una Explicación Detallada

Para entender mejor cómo funciona este sistema multimodal, analicemos un ejemplo completo. Cuando un usuario sube una nota de voz diciendo:

"Tuve la mañana más tranquila — sentado junto a un lago con pájaros cantando y el sol saliendo detrás de los árboles."

El sistema procesa esta entrada a través de tres etapas distintas:

  1. Procesamiento de Audio con Transcripción: Primero, Whisper convierte el audio a texto, manteniendo la precisión incluso con ruido de fondo o variaciones de acento. Resultado: "Tuve la mañana más tranquila..."
  2. Análisis y Mejora de la Escena: GPT-4o analiza el texto transcrito, identificando elementos visuales clave y relaciones espaciales para crear un prompt optimizado para la imagen. Resultado: "Un amanecer sobre un lago tranquilo, con pájaros en el cielo y árboles reflejándose en el agua"
  3. Creación Visual: DALL·E toma este prompt refinado y genera una imagen fotorrealista, equilibrando cuidadosamente todos los elementos descritos en una escena coherente

El Poder de la Integración

En esta sección final del capítulo, creaste un mini-asistente multimodal que demuestra la integración perfecta de tres capacidades distintas de IA:

  • Whisper: Reconocimiento de voz avanzado que maneja varios acentos, idiomas y calidades de audio con notable precisión
  • GPT-4o: Procesamiento sofisticado del lenguaje que comprende el contexto, la emoción y la composición de la escena para crear descripciones detalladas de imágenes
  • DALL·E: Generación de imágenes de última generación que traduce descripciones textuales en escenas visuales vívidas y coherentes

Esta integración muestra el futuro de las aplicaciones de IA donde múltiples modelos trabajan en conjunto para procesar, comprender y responder a la entrada del usuario de formas ricas y significativas. Al orquestar estos modelos juntos, has creado una interfaz intuitiva que reduce la brecha entre la comunicación humana y las capacidades de la IA.

5.5 Integración Básica de Múltiples Modalidades

En esta sección, exploraremos cómo combinar múltiples modalidades de IA - habla, comprensión del lenguaje y generación de imágenes - en una única aplicación cohesiva. Mientras que las secciones anteriores se centraron en trabajar con tecnologías individuales, aquí aprenderemos cómo estas poderosas herramientas pueden trabajar juntas para crear experiencias de usuario más sofisticadas y atractivas.

Esta integración representa un avance significativo en el desarrollo de aplicaciones de IA, yendo más allá de simples herramientas de propósito único para crear sistemas que pueden procesar y responder a través de diferentes formas de comunicación. Al combinar las capacidades de transcripción de audio de Whisper, la comprensión del lenguaje natural de GPT-4o y la generación de imágenes de DALL·E, podemos construir aplicaciones que verdaderamente demuestran el potencial de las tecnologías modernas de IA.

El proyecto que construiremos sirve como una excelente introducción a la integración multimodal de IA, demostrando cómo diferentes modelos de IA pueden ser orquestados para crear una experiencia fluida. Este enfoque abre posibilidades emocionantes para desarrolladores que buscan crear interacciones humano-IA más naturales e intuitivas.

5.5.1 Lo que Construirás

En esta sección, crearás una aplicación web sofisticada basada en Flask que funciona como un asistente multimodal básico. Esta aplicación demuestra la integración perfecta de múltiples tecnologías de IA para crear una experiencia de usuario interactiva e inteligente. El asistente es capaz de:

  • Aceptar un mensaje de audio del usuario a través de una interfaz web limpia, compatible con varios formatos de audio
  • Aprovechar la tecnología Whisper de OpenAI para transcribir con precisión el mensaje de audio a texto, manejando diferentes acentos e idiomas
  • Utilizar las capacidades avanzadas de procesamiento del lenguaje natural de GPT-4o para analizar el texto transcrito, comprendiendo el contexto, la intención y los temas clave
  • Emplear las sofisticadas capacidades de generación de imágenes de DALL·E 3 para crear visuales relevantes y de alta calidad basados en el contexto comprendido
  • Presentar una experiencia de usuario cohesiva mostrando tanto la transcripción original como la imagen generada por IA en una única página web bien diseñada

Este proyecto sirve como un excelente ejemplo de un asistente multimodal que combina perfectamente tres tipos diferentes de procesamiento de IA: procesamiento de audio, comprensión del lenguaje natural y generación de imágenes. Al procesar la entrada de audio, extraer contexto significativo y crear representaciones visuales, demuestra el potencial de las tecnologías de IA integradas. Esta base abre emocionantes posibilidades para varias aplicaciones del mundo real, como:

  • Instrucciones de diseño habladas para creadores visuales - permitiendo a artistas y diseñadores describir verbalmente su visión y verla renderizada al instante
  • Diario en audio con salida ilustrada - transformando entradas de diario habladas en memorias visuales con obras de arte generadas por IA correspondientes
  • Aplicaciones de narración controladas por voz - creando narrativas interactivas donde las palabras habladas cobran vida a través de la generación visual instantánea

5.5.2 Implementación Paso a Paso

Paso 1: Instalar Paquetes Requeridos

Descarga el audio de muestra: https://files.cuantum.tech/audio/audio-file-sample.mp3

Asegúrate de tener las bibliotecas de Python necesarias instaladas. Abre tu terminal y ejecuta el siguiente comando:

pip install flask openai python-dotenv

Este comando instala:

  • flask: Un micro framework web para construir la aplicación web.
  • openai: La biblioteca de Python de OpenAI para interactuar con las APIs de Whisper, Chat Completion y DALL·E 3.
  • python-dotenv: Una biblioteca para cargar variables de entorno desde un archivo .env.

Paso 2: Configurar la Estructura del Proyecto

Crea la siguiente estructura de carpetas para tu proyecto:

/multimodal_app

├── app.py
├── .env
└── templates/
    └── index.html
  • /multimodal_app: El directorio raíz de tu proyecto.
  • app.py: El archivo Python que contiene el código de la aplicación Flask.
  • .env: Un archivo para almacenar tu clave de API de OpenAI.
  • templates/: Un directorio para almacenar tus plantillas HTML.
  • templates/index.html: La plantilla HTML para la página principal de tu aplicación.

Paso 3: Crear la Aplicación Flask (app.py)

Crea un archivo Python llamado app.py en el directorio raíz de tu proyecto y agrega el siguiente código:

from flask import Flask, request, render_template, jsonify, make_response
import openai
import os
from dotenv import load_dotenv
import logging
from typing import Optional, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

app = Flask(__name__)

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

ALLOWED_EXTENSIONS = {'mp3', 'mp4', 'wav', 'm4a'}  # Allowed audio file extensions


def allowed_file(filename: str) -> bool:
    """
    Checks if the uploaded file has an allowed extension.

    Args:
        filename (str): The name of the file.

    Returns:
        bool: True if the file has an allowed extension, False otherwise.
    """
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


def transcribe_audio(file_path: str) -> Optional[str]:
    """
    Transcribes an audio file using OpenAI's Whisper API.

    Args:
        file_path (str): The path to the audio file.

    Returns:
        Optional[str]: The transcribed text, or None on error.
    """
    try:
        logger.info(f"Transcribing audio file: {file_path}")
        audio_file = open(file_path, "rb")
        response = openai.Audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
        )
        transcript = response.text
        logger.info(f"Transcription successful. Length: {len(transcript)} characters.")
        return transcript
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error during transcription: {e}")
        return None


def generate_image_prompt(text: str) -> Optional[str]:
    """
    Generates a prompt for DALL·E 3 based on the transcribed text using GPT-4o.

    Args:
        text (str): The transcribed text.

    Returns:
        Optional[str]: The generated image prompt, or None on error.
    """
    try:
        logger.info("Generating image prompt using GPT-4o")
        response = openai.chat.completions.create(
            model="gpt-4o",  # You can also experiment with other chat models
            messages=[
                {
                    "role": "system",
                    "content": "You are a creative assistant. Your task is to create a vivid and detailed text description of a scene that could be used to generate an image with an AI image generation model. Focus on capturing the essence and key visual elements of the audio content.  Do not include any phrases like 'based on the audio' or 'from the user audio'.",
                },
                {"role": "user", "content": text},
            ],
        )
        prompt = response.choices[0].message.content
        logger.info(f"Generated image prompt: {prompt}")
        return prompt
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image prompt: {e}")
        return None


def generate_image(prompt: str, model: str = "dall-e-3", size: str = "1024x1024", response_format: str = "url") -> Optional[str]:
    """
    Generates an image using OpenAI's DALL·E API.

    Args:
        prompt (str): The text prompt to generate the image from.
        model (str, optional): The DALL·E model to use. Defaults to "dall-e-3".
        size (str, optional): The size of the generated image. Defaults to "1024x1024".
        response_format (str, optional): The format of the response. Defaults to "url".

    Returns:
        Optional[str]: The URL of the generated image, or None on error.
    """
    try:
        logger.info(f"Generating image with prompt: {prompt}, model: {model}, size: {size}, format: {response_format}")
        response = openai.Image.create(
            prompt=prompt,
            model=model,
            size=size,
            response_format=response_format,
        )
        image_url = response.data[0].url
        logger.info(f"Image URL: {image_url}")
        return image_url
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image: {e}")
        return None



@app.route("/", methods=["GET", "POST"])
def index():
    """
    Handles the main route for the web application.
    Processes audio uploads, transcribes them, generates image prompts, and displays images.
    """
    transcript = None
    image_url = None
    prompt_summary = None
    error_message = None

    if request.method == "POST":
        if 'audio_file' not in request.files:
            error_message = "No file part"
            logger.warning(error_message)
            return render_template("index.html", error=error_message)
        file = request.files['audio_file']
        if file.filename == '':
            error_message = "No file selected"
            logger.warning(error_message)
            return render_template("index.html", error=error_message)

        if file and allowed_file(file.filename):
            try:
                # Securely save the uploaded file to a temporary location
                temp_file_path = os.path.join(app.root_path, "temp_audio." + file.filename.rsplit('.', 1)[1].lower())
                file.save(temp_file_path)

                transcript = transcribe_audio(temp_file_path)  # Transcribe audio
                if not transcript:
                    error_message = "Audio transcription failed. Please try again."
                    return render_template("index.html", error=error_message)

                prompt_summary = generate_image_prompt(transcript)  # Generate prompt
                if not prompt_summary:
                    error_message = "Failed to generate image prompt. Please try again."
                    return render_template("index.html", error=error_message)

                image_url = generate_image(prompt_summary)  # Generate image
                if not image_url:
                    error_message = "Failed to generate image. Please try again."
                    return render_template("index.html", error=error_message)

                # Optionally, delete the temporary file after processing
                os.remove(temp_file_path)

            except Exception as e:
                error_message = f"An error occurred: {e}"
                logger.error(error_message)
                return render_template("index.html", error=error_message)
        else:
            error_message = "Invalid file type. Please upload a valid audio file (MP3, MP4, WAV, M4A)."
            logger.warning(error_message)
            return render_template("index.html", error=error_message)

    return render_template("index.html", transcript=transcript, image_url=image_url, prompt_summary=prompt_summary, error=error_message)



@app.errorhandler(500)
def internal_server_error(e):
    """Handles internal server errors."""
    logger.error(f"Internal Server Error: {e}")
    return render_template("error.html", error="Internal Server Error"), 500


if __name__ == "__main__":
    app.run(debug=True)

Desglose del Código:

  • Declaraciones de Importación: Importa los módulos necesarios de Flask, la biblioteca OpenAI, osdotenvlogging, y Optional y Dict para la tipificación.
  • Variables de Entorno: Carga la clave API de OpenAI desde el archivo .env.
  • Aplicación Flask: Crea una instancia de la aplicación Flask.
  • Configuración de Registro: Configura el sistema de registro.
  • Función allowed_file: Verifica si el archivo subido tiene una extensión de audio permitida.
  • Función transcribe_audio:
    • Recibe la ruta del archivo de audio como entrada.
    • Abre el archivo de audio en modo binario ("rb").
    • Llama al método openai.Audio.transcribe() de la API de OpenAI para transcribir el audio.
    • Extrae el texto transcrito de la respuesta.
    • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
    • Incluye manejo de errores para errores de la API de OpenAI y otras excepciones.
  • Función generate_image_prompt:
    • Recibe el texto transcrito como entrada.
    • Utiliza la API de Chat Completion de OpenAI (openai.chat.completions.create()) con el modelo gpt-4o para generar un texto apropiado para la generación de imágenes.
    • El mensaje del sistema instruye al modelo para actuar como un asistente creativo y proporcionar una descripción vívida de una escena basada en el audio.
    • Extrae el texto generado de la respuesta de la API.
    • Incluye manejo de errores.
  • Función generate_image:
    • Recibe el texto para generar la imagen como entrada.
    • Llama al método openai.Image.create() de la API de OpenAI para generar una imagen usando DALL·E 3.
    • Extrae la URL de la imagen de la respuesta de la API.
    • Incluye manejo de errores.
  • Ruta index:
    • Maneja las peticiones GET y POST.
    • Para peticiones GET, renderiza la página HTML inicial.
    • Para peticiones POST (cuando el usuario sube un archivo de audio):
      • Valida el archivo subido.
      • Guarda el archivo subido temporalmente.
      • Llama a transcribe_audio() para transcribir el audio.
      • Llama a generate_image_prompt() para generar un texto para la imagen a partir de la transcripción.
      • Llama a generate_image() para generar una imagen a partir del texto.
      • Renderiza la plantilla index.html, pasando el texto de la transcripción y la URL de la imagen.
    • Incluye manejo exhaustivo de errores para capturar posibles problemas durante la subida de archivos, transcripción, generación de texto y generación de imágenes.
  • @app.errorhandler(500): Maneja errores HTTP 500 (Error Interno del Servidor) registrando el error y mostrando una página de error amigable para el usuario.
  • if __name__ == "__main__":: Inicia el servidor de desarrollo Flask si el script se ejecuta directamente.

Paso 4: Crear Plantilla HTML (templates/index.html)

Crea una carpeta llamada templates en el mismo directorio que app.py. Dentro de la carpeta templates, crea un archivo llamado index.html con el siguiente código HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Multimodal Assistant</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
    <style>
        /* --- General Styles --- */
        body {
            font-family: 'Inter', sans-serif;
            padding: 40px;
            background-color: #f9fafb; /* Tailwind's gray-50 */
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            color: #374151; /* Tailwind's gray-700 */
        }
        .container {
            max-width: 800px; /* Increased max-width */
            width: 95%; /* Take up most of the viewport */
            background-color: #fff;
            padding: 2rem;
            border-radius: 0.75rem; /* Tailwind's rounded-lg */
            box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.05); /* Tailwind's shadow-xl */
            text-align: center;
        }
        h2 {
            font-size: 2.25rem; /* Tailwind's text-3xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1.5rem; /* Tailwind's mb-6 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        p{
            color: #6b7280; /* Tailwind's gray-500 */
            margin-bottom: 1rem;
        }

        /* --- Form Styles --- */
        form {
            margin-top: 1rem; /* Tailwind's mt-4 */
            display: flex;
            flex-direction: column;
            align-items: center; /* Center form elements */
            gap: 0.5rem; /* Tailwind's gap-2 */
        }
        label {
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600;  /* Tailwind's font-semibold */
            color: #4b5563; /* Tailwind's gray-600 */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            display: block; /* Ensure label takes full width */
            text-align: left;
            width: 100%;
            max-width: 400px; /* Added max-width for label */
            margin-left: auto;
            margin-right: auto;
        }
        input[type="file"] {
            width: 100%;
            max-width: 400px; /* Added max-width for file input */
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            font-size: 1rem; /* Tailwind's text-base */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            margin-left: auto;
            margin-right: auto;

        }
        input[type="submit"] {
            padding: 0.75rem 1.5rem; /* Tailwind's px-6 py-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #4f46e5; /* Tailwind's bg-indigo-500 */
            color: #fff;
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600; /* Tailwind's font-semibold */
            cursor: pointer;
            transition: background-color 0.3s ease; /* Smooth transition */
            border: none;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Subtle shadow */
            margin-top: 1rem;
        }
        input[type="submit"]:hover {
            background-color: #4338ca; /* Tailwind's bg-indigo-700 on hover */
        }
        input[type="submit"]:focus {
            outline: none;
            box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3); /* Tailwind's ring-indigo-500 */
        }

        /* --- Result Styles --- */
        .result-container {
            margin-top: 2rem; /* Tailwind's mt-8 */
            padding: 1.5rem; /* Tailwind's p-6 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #f8fafc; /* Tailwind's bg-gray-50 */
            border: 1px solid #e2e8f0; /* Tailwind's border-gray-200 */
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */
        }
        .transcript-container{
            margin-top: 2rem; /* Tailwind's mt-8 */
            padding: 1.5rem; /* Tailwind's p-6 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #f8fafc; /* Tailwind's bg-gray-50 */
            border: 1px solid #e2e8f0; /* Tailwind's border-gray-200 */
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */

        }

        h3 {
            font-size: 1.5rem; /* Tailwind's text-2xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1rem; /* Tailwind's mb-4 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        textarea {
            width: 100%;
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            resize: none;
            font-size: 1rem; /* Tailwind's text-base */
            line-height: 1.5rem; /* Tailwind's leading-relaxed */
            margin-top: 0.5rem; /* Tailwind's mt-2 */
            margin-bottom: 0;
            box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); /* Inner shadow */
        }
        textarea:focus {
            outline: none;
            border-color: #3b82f6; /* Tailwind's border-blue-500 */
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); /* Tailwind's ring-blue-500 */
        }
        img {
            max-width: 100%;
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            margin-top: 1.5rem; /* Tailwind's mt-6 */
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* Tailwind's shadow-md */
        }

        /* --- Error Styles --- */
        .error-message {
            color: #dc2626; /* Tailwind's text-red-600 */
            margin-top: 1rem; /* Tailwind's mt-4 */
            padding: 0.75rem;
            background-color: #fee2e2; /* Tailwind's bg-red-100 */
            border-radius: 0.375rem; /* Tailwind's rounded-md */
            border: 1px solid #fecaca; /* Tailwind's border-red-300 */
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="container">
        <h2>🎤🧠🎨 Multimodal Assistant</h2>
        <p> Upload an audio file to transcribe and generate a corresponding image. </p>
        <form method="POST" enctype="multipart/form-data">
            <label for="audio_file">Upload your voice note:</label><br>
            <input type="file" name="audio_file" accept="audio/*" required><br><br>
            <input type="submit" value="Generate Visual Response">
        </form>

        {% if transcript %}
            <div class = "transcript-container">
                <h3>📝 Transcript:</h3>
                <textarea readonly>{{ transcript }}</textarea>
            </div>
        {% endif %}

        {% if prompt_summary %}
            <div class = "result-container">
                <h3>🎯 Prompt Used for Image:</h3>
                <p>{{ prompt_summary }}</p>
            </div>
        {% endif %}

        {% if image_url %}
            <div class = "result-container">
                <h3>🖼️ Generated Image:</h3>
                <img src="{{ image_url }}" alt="Generated image">
            </div>
        {% endif %}
        {% if error %}
            <div class="error-message">{{ error }}</div>
        {% endif %}
    </div>
</body>
</html>

Elementos clave en la plantilla HTML:

  • Estructura HTML:
    • La sección <head> define el título, enlaza una hoja de estilos CSS y establece el viewport para hacer el diseño adaptable.
    • El <body> contiene el contenido visible, incluyendo un formulario para subir archivos de audio y secciones para mostrar la transcripción y la imagen generada.
  • Estilo CSS:
    • Diseño moderno: El CSS se ha actualizado con un diseño moderno, similar al estilo de Tailwind CSS.
    • Diseño responsivo: El diseño es más adaptable, especialmente para pantallas pequeñas.
    • Experiencia de usuario: Mejora en el estilo del formulario y de los campos de entrada.
    • Visualización clara de errores: Los mensajes de error están estilizados para ser claramente visibles.
  • Formulario:
    • Se utiliza un <form> con enctype="multipart/form-data" para gestionar la carga de archivos.
    • Un <label> y un <input type="file"> permiten al usuario seleccionar un archivo de audio. El atributo accept="audio/*" restringe la selección a archivos de audio.
    • Un botón <input type="submit"> permite al usuario enviar el formulario.
  • Visualización de la transcripción e imagen:
    • La plantilla usa Jinja2 para mostrar condicionalmente el texto transcrito y la imagen generada, si están disponibles. La transcripción se muestra en un textarea y la imagen mediante una etiqueta <img>.
  • Manejo de errores:
    • Un <div class="error-message"> se utiliza para mostrar cualquier mensaje de error al usuario.

Pruébalo

  1. Guarda los archivos como app.py y templates/index.html.
  2. Asegúrate de tener tu clave de API de OpenAI en el archivo .env.
  3. Ejecuta la aplicación:
    python app.py
  4. Abre http://localhost:5000 en tu navegador.
  5. Sube un archivo de audio (puedes usar el archivo de muestra .mp3 proporcionado).
  6. Visualiza la transcripción y la imagen generada en la página.

5.5.3 Cómo Funciona (Detrás de Escena)

Esto demuestra la orquestación multimodal en acción - un proceso sofisticado donde diferentes modelos de IA colaboran a través de la capa lógica de tu aplicación. Cada modelo se especializa en una forma diferente de procesamiento de datos (audio, texto e imagen), y juntos crean una experiencia fluida. La aplicación coordina estos modelos, manejando la transformación de datos entre cada paso y asegurando un flujo de comunicación adecuado.

Flujo de Ejemplo: Una Explicación Detallada

Para entender mejor cómo funciona este sistema multimodal, analicemos un ejemplo completo. Cuando un usuario sube una nota de voz diciendo:

"Tuve la mañana más tranquila — sentado junto a un lago con pájaros cantando y el sol saliendo detrás de los árboles."

El sistema procesa esta entrada a través de tres etapas distintas:

  1. Procesamiento de Audio con Transcripción: Primero, Whisper convierte el audio a texto, manteniendo la precisión incluso con ruido de fondo o variaciones de acento. Resultado: "Tuve la mañana más tranquila..."
  2. Análisis y Mejora de la Escena: GPT-4o analiza el texto transcrito, identificando elementos visuales clave y relaciones espaciales para crear un prompt optimizado para la imagen. Resultado: "Un amanecer sobre un lago tranquilo, con pájaros en el cielo y árboles reflejándose en el agua"
  3. Creación Visual: DALL·E toma este prompt refinado y genera una imagen fotorrealista, equilibrando cuidadosamente todos los elementos descritos en una escena coherente

El Poder de la Integración

En esta sección final del capítulo, creaste un mini-asistente multimodal que demuestra la integración perfecta de tres capacidades distintas de IA:

  • Whisper: Reconocimiento de voz avanzado que maneja varios acentos, idiomas y calidades de audio con notable precisión
  • GPT-4o: Procesamiento sofisticado del lenguaje que comprende el contexto, la emoción y la composición de la escena para crear descripciones detalladas de imágenes
  • DALL·E: Generación de imágenes de última generación que traduce descripciones textuales en escenas visuales vívidas y coherentes

Esta integración muestra el futuro de las aplicaciones de IA donde múltiples modelos trabajan en conjunto para procesar, comprender y responder a la entrada del usuario de formas ricas y significativas. Al orquestar estos modelos juntos, has creado una interfaz intuitiva que reduce la brecha entre la comunicación humana y las capacidades de la IA.

5.5 Integración Básica de Múltiples Modalidades

En esta sección, exploraremos cómo combinar múltiples modalidades de IA - habla, comprensión del lenguaje y generación de imágenes - en una única aplicación cohesiva. Mientras que las secciones anteriores se centraron en trabajar con tecnologías individuales, aquí aprenderemos cómo estas poderosas herramientas pueden trabajar juntas para crear experiencias de usuario más sofisticadas y atractivas.

Esta integración representa un avance significativo en el desarrollo de aplicaciones de IA, yendo más allá de simples herramientas de propósito único para crear sistemas que pueden procesar y responder a través de diferentes formas de comunicación. Al combinar las capacidades de transcripción de audio de Whisper, la comprensión del lenguaje natural de GPT-4o y la generación de imágenes de DALL·E, podemos construir aplicaciones que verdaderamente demuestran el potencial de las tecnologías modernas de IA.

El proyecto que construiremos sirve como una excelente introducción a la integración multimodal de IA, demostrando cómo diferentes modelos de IA pueden ser orquestados para crear una experiencia fluida. Este enfoque abre posibilidades emocionantes para desarrolladores que buscan crear interacciones humano-IA más naturales e intuitivas.

5.5.1 Lo que Construirás

En esta sección, crearás una aplicación web sofisticada basada en Flask que funciona como un asistente multimodal básico. Esta aplicación demuestra la integración perfecta de múltiples tecnologías de IA para crear una experiencia de usuario interactiva e inteligente. El asistente es capaz de:

  • Aceptar un mensaje de audio del usuario a través de una interfaz web limpia, compatible con varios formatos de audio
  • Aprovechar la tecnología Whisper de OpenAI para transcribir con precisión el mensaje de audio a texto, manejando diferentes acentos e idiomas
  • Utilizar las capacidades avanzadas de procesamiento del lenguaje natural de GPT-4o para analizar el texto transcrito, comprendiendo el contexto, la intención y los temas clave
  • Emplear las sofisticadas capacidades de generación de imágenes de DALL·E 3 para crear visuales relevantes y de alta calidad basados en el contexto comprendido
  • Presentar una experiencia de usuario cohesiva mostrando tanto la transcripción original como la imagen generada por IA en una única página web bien diseñada

Este proyecto sirve como un excelente ejemplo de un asistente multimodal que combina perfectamente tres tipos diferentes de procesamiento de IA: procesamiento de audio, comprensión del lenguaje natural y generación de imágenes. Al procesar la entrada de audio, extraer contexto significativo y crear representaciones visuales, demuestra el potencial de las tecnologías de IA integradas. Esta base abre emocionantes posibilidades para varias aplicaciones del mundo real, como:

  • Instrucciones de diseño habladas para creadores visuales - permitiendo a artistas y diseñadores describir verbalmente su visión y verla renderizada al instante
  • Diario en audio con salida ilustrada - transformando entradas de diario habladas en memorias visuales con obras de arte generadas por IA correspondientes
  • Aplicaciones de narración controladas por voz - creando narrativas interactivas donde las palabras habladas cobran vida a través de la generación visual instantánea

5.5.2 Implementación Paso a Paso

Paso 1: Instalar Paquetes Requeridos

Descarga el audio de muestra: https://files.cuantum.tech/audio/audio-file-sample.mp3

Asegúrate de tener las bibliotecas de Python necesarias instaladas. Abre tu terminal y ejecuta el siguiente comando:

pip install flask openai python-dotenv

Este comando instala:

  • flask: Un micro framework web para construir la aplicación web.
  • openai: La biblioteca de Python de OpenAI para interactuar con las APIs de Whisper, Chat Completion y DALL·E 3.
  • python-dotenv: Una biblioteca para cargar variables de entorno desde un archivo .env.

Paso 2: Configurar la Estructura del Proyecto

Crea la siguiente estructura de carpetas para tu proyecto:

/multimodal_app

├── app.py
├── .env
└── templates/
    └── index.html
  • /multimodal_app: El directorio raíz de tu proyecto.
  • app.py: El archivo Python que contiene el código de la aplicación Flask.
  • .env: Un archivo para almacenar tu clave de API de OpenAI.
  • templates/: Un directorio para almacenar tus plantillas HTML.
  • templates/index.html: La plantilla HTML para la página principal de tu aplicación.

Paso 3: Crear la Aplicación Flask (app.py)

Crea un archivo Python llamado app.py en el directorio raíz de tu proyecto y agrega el siguiente código:

from flask import Flask, request, render_template, jsonify, make_response
import openai
import os
from dotenv import load_dotenv
import logging
from typing import Optional, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

app = Flask(__name__)

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

ALLOWED_EXTENSIONS = {'mp3', 'mp4', 'wav', 'm4a'}  # Allowed audio file extensions


def allowed_file(filename: str) -> bool:
    """
    Checks if the uploaded file has an allowed extension.

    Args:
        filename (str): The name of the file.

    Returns:
        bool: True if the file has an allowed extension, False otherwise.
    """
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


def transcribe_audio(file_path: str) -> Optional[str]:
    """
    Transcribes an audio file using OpenAI's Whisper API.

    Args:
        file_path (str): The path to the audio file.

    Returns:
        Optional[str]: The transcribed text, or None on error.
    """
    try:
        logger.info(f"Transcribing audio file: {file_path}")
        audio_file = open(file_path, "rb")
        response = openai.Audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
        )
        transcript = response.text
        logger.info(f"Transcription successful. Length: {len(transcript)} characters.")
        return transcript
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error during transcription: {e}")
        return None


def generate_image_prompt(text: str) -> Optional[str]:
    """
    Generates a prompt for DALL·E 3 based on the transcribed text using GPT-4o.

    Args:
        text (str): The transcribed text.

    Returns:
        Optional[str]: The generated image prompt, or None on error.
    """
    try:
        logger.info("Generating image prompt using GPT-4o")
        response = openai.chat.completions.create(
            model="gpt-4o",  # You can also experiment with other chat models
            messages=[
                {
                    "role": "system",
                    "content": "You are a creative assistant. Your task is to create a vivid and detailed text description of a scene that could be used to generate an image with an AI image generation model. Focus on capturing the essence and key visual elements of the audio content.  Do not include any phrases like 'based on the audio' or 'from the user audio'.",
                },
                {"role": "user", "content": text},
            ],
        )
        prompt = response.choices[0].message.content
        logger.info(f"Generated image prompt: {prompt}")
        return prompt
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image prompt: {e}")
        return None


def generate_image(prompt: str, model: str = "dall-e-3", size: str = "1024x1024", response_format: str = "url") -> Optional[str]:
    """
    Generates an image using OpenAI's DALL·E API.

    Args:
        prompt (str): The text prompt to generate the image from.
        model (str, optional): The DALL·E model to use. Defaults to "dall-e-3".
        size (str, optional): The size of the generated image. Defaults to "1024x1024".
        response_format (str, optional): The format of the response. Defaults to "url".

    Returns:
        Optional[str]: The URL of the generated image, or None on error.
    """
    try:
        logger.info(f"Generating image with prompt: {prompt}, model: {model}, size: {size}, format: {response_format}")
        response = openai.Image.create(
            prompt=prompt,
            model=model,
            size=size,
            response_format=response_format,
        )
        image_url = response.data[0].url
        logger.info(f"Image URL: {image_url}")
        return image_url
    except openai.error.OpenAIError as e:
        logger.error(f"OpenAI API Error: {e}")
        return None
    except Exception as e:
        logger.error(f"Error generating image: {e}")
        return None



@app.route("/", methods=["GET", "POST"])
def index():
    """
    Handles the main route for the web application.
    Processes audio uploads, transcribes them, generates image prompts, and displays images.
    """
    transcript = None
    image_url = None
    prompt_summary = None
    error_message = None

    if request.method == "POST":
        if 'audio_file' not in request.files:
            error_message = "No file part"
            logger.warning(error_message)
            return render_template("index.html", error=error_message)
        file = request.files['audio_file']
        if file.filename == '':
            error_message = "No file selected"
            logger.warning(error_message)
            return render_template("index.html", error=error_message)

        if file and allowed_file(file.filename):
            try:
                # Securely save the uploaded file to a temporary location
                temp_file_path = os.path.join(app.root_path, "temp_audio." + file.filename.rsplit('.', 1)[1].lower())
                file.save(temp_file_path)

                transcript = transcribe_audio(temp_file_path)  # Transcribe audio
                if not transcript:
                    error_message = "Audio transcription failed. Please try again."
                    return render_template("index.html", error=error_message)

                prompt_summary = generate_image_prompt(transcript)  # Generate prompt
                if not prompt_summary:
                    error_message = "Failed to generate image prompt. Please try again."
                    return render_template("index.html", error=error_message)

                image_url = generate_image(prompt_summary)  # Generate image
                if not image_url:
                    error_message = "Failed to generate image. Please try again."
                    return render_template("index.html", error=error_message)

                # Optionally, delete the temporary file after processing
                os.remove(temp_file_path)

            except Exception as e:
                error_message = f"An error occurred: {e}"
                logger.error(error_message)
                return render_template("index.html", error=error_message)
        else:
            error_message = "Invalid file type. Please upload a valid audio file (MP3, MP4, WAV, M4A)."
            logger.warning(error_message)
            return render_template("index.html", error=error_message)

    return render_template("index.html", transcript=transcript, image_url=image_url, prompt_summary=prompt_summary, error=error_message)



@app.errorhandler(500)
def internal_server_error(e):
    """Handles internal server errors."""
    logger.error(f"Internal Server Error: {e}")
    return render_template("error.html", error="Internal Server Error"), 500


if __name__ == "__main__":
    app.run(debug=True)

Desglose del Código:

  • Declaraciones de Importación: Importa los módulos necesarios de Flask, la biblioteca OpenAI, osdotenvlogging, y Optional y Dict para la tipificación.
  • Variables de Entorno: Carga la clave API de OpenAI desde el archivo .env.
  • Aplicación Flask: Crea una instancia de la aplicación Flask.
  • Configuración de Registro: Configura el sistema de registro.
  • Función allowed_file: Verifica si el archivo subido tiene una extensión de audio permitida.
  • Función transcribe_audio:
    • Recibe la ruta del archivo de audio como entrada.
    • Abre el archivo de audio en modo binario ("rb").
    • Llama al método openai.Audio.transcribe() de la API de OpenAI para transcribir el audio.
    • Extrae el texto transcrito de la respuesta.
    • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
    • Incluye manejo de errores para errores de la API de OpenAI y otras excepciones.
  • Función generate_image_prompt:
    • Recibe el texto transcrito como entrada.
    • Utiliza la API de Chat Completion de OpenAI (openai.chat.completions.create()) con el modelo gpt-4o para generar un texto apropiado para la generación de imágenes.
    • El mensaje del sistema instruye al modelo para actuar como un asistente creativo y proporcionar una descripción vívida de una escena basada en el audio.
    • Extrae el texto generado de la respuesta de la API.
    • Incluye manejo de errores.
  • Función generate_image:
    • Recibe el texto para generar la imagen como entrada.
    • Llama al método openai.Image.create() de la API de OpenAI para generar una imagen usando DALL·E 3.
    • Extrae la URL de la imagen de la respuesta de la API.
    • Incluye manejo de errores.
  • Ruta index:
    • Maneja las peticiones GET y POST.
    • Para peticiones GET, renderiza la página HTML inicial.
    • Para peticiones POST (cuando el usuario sube un archivo de audio):
      • Valida el archivo subido.
      • Guarda el archivo subido temporalmente.
      • Llama a transcribe_audio() para transcribir el audio.
      • Llama a generate_image_prompt() para generar un texto para la imagen a partir de la transcripción.
      • Llama a generate_image() para generar una imagen a partir del texto.
      • Renderiza la plantilla index.html, pasando el texto de la transcripción y la URL de la imagen.
    • Incluye manejo exhaustivo de errores para capturar posibles problemas durante la subida de archivos, transcripción, generación de texto y generación de imágenes.
  • @app.errorhandler(500): Maneja errores HTTP 500 (Error Interno del Servidor) registrando el error y mostrando una página de error amigable para el usuario.
  • if __name__ == "__main__":: Inicia el servidor de desarrollo Flask si el script se ejecuta directamente.

Paso 4: Crear Plantilla HTML (templates/index.html)

Crea una carpeta llamada templates en el mismo directorio que app.py. Dentro de la carpeta templates, crea un archivo llamado index.html con el siguiente código HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Multimodal Assistant</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
    <style>
        /* --- General Styles --- */
        body {
            font-family: 'Inter', sans-serif;
            padding: 40px;
            background-color: #f9fafb; /* Tailwind's gray-50 */
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            color: #374151; /* Tailwind's gray-700 */
        }
        .container {
            max-width: 800px; /* Increased max-width */
            width: 95%; /* Take up most of the viewport */
            background-color: #fff;
            padding: 2rem;
            border-radius: 0.75rem; /* Tailwind's rounded-lg */
            box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.05); /* Tailwind's shadow-xl */
            text-align: center;
        }
        h2 {
            font-size: 2.25rem; /* Tailwind's text-3xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1.5rem; /* Tailwind's mb-6 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        p{
            color: #6b7280; /* Tailwind's gray-500 */
            margin-bottom: 1rem;
        }

        /* --- Form Styles --- */
        form {
            margin-top: 1rem; /* Tailwind's mt-4 */
            display: flex;
            flex-direction: column;
            align-items: center; /* Center form elements */
            gap: 0.5rem; /* Tailwind's gap-2 */
        }
        label {
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600;  /* Tailwind's font-semibold */
            color: #4b5563; /* Tailwind's gray-600 */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            display: block; /* Ensure label takes full width */
            text-align: left;
            width: 100%;
            max-width: 400px; /* Added max-width for label */
            margin-left: auto;
            margin-right: auto;
        }
        input[type="file"] {
            width: 100%;
            max-width: 400px; /* Added max-width for file input */
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            font-size: 1rem; /* Tailwind's text-base */
            margin-bottom: 0.25rem; /* Tailwind's mb-1 */
            margin-left: auto;
            margin-right: auto;

        }
        input[type="submit"] {
            padding: 0.75rem 1.5rem; /* Tailwind's px-6 py-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #4f46e5; /* Tailwind's bg-indigo-500 */
            color: #fff;
            font-size: 1rem; /* Tailwind's text-base */
            font-weight: 600; /* Tailwind's font-semibold */
            cursor: pointer;
            transition: background-color 0.3s ease; /* Smooth transition */
            border: none;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Subtle shadow */
            margin-top: 1rem;
        }
        input[type="submit"]:hover {
            background-color: #4338ca; /* Tailwind's bg-indigo-700 on hover */
        }
        input[type="submit"]:focus {
            outline: none;
            box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3); /* Tailwind's ring-indigo-500 */
        }

        /* --- Result Styles --- */
        .result-container {
            margin-top: 2rem; /* Tailwind's mt-8 */
            padding: 1.5rem; /* Tailwind's p-6 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #f8fafc; /* Tailwind's bg-gray-50 */
            border: 1px solid #e2e8f0; /* Tailwind's border-gray-200 */
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */
        }
        .transcript-container{
            margin-top: 2rem; /* Tailwind's mt-8 */
            padding: 1.5rem; /* Tailwind's p-6 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            background-color: #f8fafc; /* Tailwind's bg-gray-50 */
            border: 1px solid #e2e8f0; /* Tailwind's border-gray-200 */
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Subtle shadow */

        }

        h3 {
            font-size: 1.5rem; /* Tailwind's text-2xl */
            font-weight: 600;  /* Tailwind's font-semibold */
            margin-bottom: 1rem; /* Tailwind's mb-4 */
            color: #1e293b; /* Tailwind's gray-900 */
        }
        textarea {
            width: 100%;
            padding: 0.75rem; /* Tailwind's p-3 */
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            border: 1px solid #d1d5db; /* Tailwind's border-gray-300 */
            resize: none;
            font-size: 1rem; /* Tailwind's text-base */
            line-height: 1.5rem; /* Tailwind's leading-relaxed */
            margin-top: 0.5rem; /* Tailwind's mt-2 */
            margin-bottom: 0;
            box-shadow: inset 0 2px 4px rgba(0,0,0,0.06); /* Inner shadow */
        }
        textarea:focus {
            outline: none;
            border-color: #3b82f6; /* Tailwind's border-blue-500 */
            box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); /* Tailwind's ring-blue-500 */
        }
        img {
            max-width: 100%;
            border-radius: 0.5rem; /* Tailwind's rounded-md */
            margin-top: 1.5rem; /* Tailwind's mt-6 */
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* Tailwind's shadow-md */
        }

        /* --- Error Styles --- */
        .error-message {
            color: #dc2626; /* Tailwind's text-red-600 */
            margin-top: 1rem; /* Tailwind's mt-4 */
            padding: 0.75rem;
            background-color: #fee2e2; /* Tailwind's bg-red-100 */
            border-radius: 0.375rem; /* Tailwind's rounded-md */
            border: 1px solid #fecaca; /* Tailwind's border-red-300 */
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="container">
        <h2>🎤🧠🎨 Multimodal Assistant</h2>
        <p> Upload an audio file to transcribe and generate a corresponding image. </p>
        <form method="POST" enctype="multipart/form-data">
            <label for="audio_file">Upload your voice note:</label><br>
            <input type="file" name="audio_file" accept="audio/*" required><br><br>
            <input type="submit" value="Generate Visual Response">
        </form>

        {% if transcript %}
            <div class = "transcript-container">
                <h3>📝 Transcript:</h3>
                <textarea readonly>{{ transcript }}</textarea>
            </div>
        {% endif %}

        {% if prompt_summary %}
            <div class = "result-container">
                <h3>🎯 Prompt Used for Image:</h3>
                <p>{{ prompt_summary }}</p>
            </div>
        {% endif %}

        {% if image_url %}
            <div class = "result-container">
                <h3>🖼️ Generated Image:</h3>
                <img src="{{ image_url }}" alt="Generated image">
            </div>
        {% endif %}
        {% if error %}
            <div class="error-message">{{ error }}</div>
        {% endif %}
    </div>
</body>
</html>

Elementos clave en la plantilla HTML:

  • Estructura HTML:
    • La sección <head> define el título, enlaza una hoja de estilos CSS y establece el viewport para hacer el diseño adaptable.
    • El <body> contiene el contenido visible, incluyendo un formulario para subir archivos de audio y secciones para mostrar la transcripción y la imagen generada.
  • Estilo CSS:
    • Diseño moderno: El CSS se ha actualizado con un diseño moderno, similar al estilo de Tailwind CSS.
    • Diseño responsivo: El diseño es más adaptable, especialmente para pantallas pequeñas.
    • Experiencia de usuario: Mejora en el estilo del formulario y de los campos de entrada.
    • Visualización clara de errores: Los mensajes de error están estilizados para ser claramente visibles.
  • Formulario:
    • Se utiliza un <form> con enctype="multipart/form-data" para gestionar la carga de archivos.
    • Un <label> y un <input type="file"> permiten al usuario seleccionar un archivo de audio. El atributo accept="audio/*" restringe la selección a archivos de audio.
    • Un botón <input type="submit"> permite al usuario enviar el formulario.
  • Visualización de la transcripción e imagen:
    • La plantilla usa Jinja2 para mostrar condicionalmente el texto transcrito y la imagen generada, si están disponibles. La transcripción se muestra en un textarea y la imagen mediante una etiqueta <img>.
  • Manejo de errores:
    • Un <div class="error-message"> se utiliza para mostrar cualquier mensaje de error al usuario.

Pruébalo

  1. Guarda los archivos como app.py y templates/index.html.
  2. Asegúrate de tener tu clave de API de OpenAI en el archivo .env.
  3. Ejecuta la aplicación:
    python app.py
  4. Abre http://localhost:5000 en tu navegador.
  5. Sube un archivo de audio (puedes usar el archivo de muestra .mp3 proporcionado).
  6. Visualiza la transcripción y la imagen generada en la página.

5.5.3 Cómo Funciona (Detrás de Escena)

Esto demuestra la orquestación multimodal en acción - un proceso sofisticado donde diferentes modelos de IA colaboran a través de la capa lógica de tu aplicación. Cada modelo se especializa en una forma diferente de procesamiento de datos (audio, texto e imagen), y juntos crean una experiencia fluida. La aplicación coordina estos modelos, manejando la transformación de datos entre cada paso y asegurando un flujo de comunicación adecuado.

Flujo de Ejemplo: Una Explicación Detallada

Para entender mejor cómo funciona este sistema multimodal, analicemos un ejemplo completo. Cuando un usuario sube una nota de voz diciendo:

"Tuve la mañana más tranquila — sentado junto a un lago con pájaros cantando y el sol saliendo detrás de los árboles."

El sistema procesa esta entrada a través de tres etapas distintas:

  1. Procesamiento de Audio con Transcripción: Primero, Whisper convierte el audio a texto, manteniendo la precisión incluso con ruido de fondo o variaciones de acento. Resultado: "Tuve la mañana más tranquila..."
  2. Análisis y Mejora de la Escena: GPT-4o analiza el texto transcrito, identificando elementos visuales clave y relaciones espaciales para crear un prompt optimizado para la imagen. Resultado: "Un amanecer sobre un lago tranquilo, con pájaros en el cielo y árboles reflejándose en el agua"
  3. Creación Visual: DALL·E toma este prompt refinado y genera una imagen fotorrealista, equilibrando cuidadosamente todos los elementos descritos en una escena coherente

El Poder de la Integración

En esta sección final del capítulo, creaste un mini-asistente multimodal que demuestra la integración perfecta de tres capacidades distintas de IA:

  • Whisper: Reconocimiento de voz avanzado que maneja varios acentos, idiomas y calidades de audio con notable precisión
  • GPT-4o: Procesamiento sofisticado del lenguaje que comprende el contexto, la emoción y la composición de la escena para crear descripciones detalladas de imágenes
  • DALL·E: Generación de imágenes de última generación que traduce descripciones textuales en escenas visuales vívidas y coherentes

Esta integración muestra el futuro de las aplicaciones de IA donde múltiples modelos trabajan en conjunto para procesar, comprender y responder a la entrada del usuario de formas ricas y significativas. Al orquestar estos modelos juntos, has creado una interfaz intuitiva que reduce la brecha entre la comunicación humana y las capacidades de la IA.