Menu iconMenu icon
OpenAI API Biblia Volumen 2

Capítulo 6: Conjuntos de IA de Modelo Cruzado

6.1 Combinando GPT + DALL·E + Whisper

En capítulos anteriores, has explorado las capacidades individuales de tres poderosos modelos de IA: GPT para el procesamiento del lenguaje natural y la generación de texto, DALL·E para crear imágenes detalladas a partir de descripciones textuales, y Whisper para la conversión precisa de voz a texto. Has aprendido a construir aplicaciones independientes como chatbots que mantienen conversaciones naturales, generadores visuales que dan vida a las ideas y herramientas de transcripción que convierten palabras habladas en texto. Incluso has experimentado con aplicaciones multimodales simples que utilizan dos de estas tecnologías en conjunto.

Ahora, daremos un salto significativo: aprenderás a crear suites de IA cohesivas — sistemas sofisticados que integran perfectamente el reconocimiento de voz, el procesamiento de texto y la generación de imágenes en aplicaciones unificadas y potentes. Estas suites pueden manejar flujos de trabajo complejos, como convertir una descripción hablada en una narrativa escrita y luego transformar esa narrativa en una representación visual, todo en un proceso fluido.

Considera este capítulo como tu avance de ser un desarrollador competente en APIs a convertirte en un arquitecto de flujos de trabajo de IA modulares y orquestados. Esta es la misma tecnología que impulsa muchas de las principales soluciones de software actuales. Las grandes empresas implementan estos sistemas integrados de IA en sus herramientas de productividad (como procesadores de documentos avanzados), plataformas de experiencia del cliente (como sistemas de soporte inteligente) y aplicaciones creativas (incluyendo herramientas de diseño potenciadas por IA) que sirven a bases masivas de usuarios que van desde miles hasta millones.

A lo largo de este capítulo, dominarás varias habilidades cruciales:

  • Encadenar modelos con lógica dinámica - Aprende a crear árboles de decisión inteligentes que determinen cómo deben interactuar los diferentes modelos de IA y cuándo invocar capacidades específicas
  • Manejar entrada en múltiples formatos - Desarrollar sistemas robustos que puedan procesar y validar varios tipos de entrada, desde archivos de audio hasta indicaciones de texto y datos de imagen
  • Devolver resultados significativos en múltiples modalidades - Crear un manejo sofisticado de respuestas que pueda entregar resultados en múltiples formatos mientras mantiene el contexto y la coherencia
  • Construir canales en tiempo real o casi real - Optimizar tus aplicaciones para el rendimiento, asegurando tiempos de respuesta rápidos incluso cuando múltiples modelos de IA trabajan juntos

Y comenzamos con una sección fundamental:

La construcción de aplicaciones sofisticadas de IA requiere la integración cuidadosa de múltiples modelos especializados para crear una experiencia inteligente y fluida. Cada modelo de IA sirve como maestro en su dominio: GPT sobresale en la comprensión y generación de texto similar al humano, DALL·E se especializa en crear impresionantes obras de arte visuales a partir de descripciones textuales, y Whisper demuestra una precisión notable en la conversión de palabras habladas a texto. Sin embargo, la verdadera innovación surge cuando estos poderosos elementos individuales se orquestan para trabajar juntos en perfecta armonía.

Esta integración sofisticada permite la creación de aplicaciones que reflejan los procesos cognitivos humanos al manejar múltiples tipos de información simultáneamente. Considera el flujo natural de la comunicación humana: hablamos, entendemos el contexto y visualizamos conceptos sin esfuerzo. Ahora imagina un sistema de IA que coincida con este proceso natural: describes una escena verbalmente, el sistema procesa tu discurso en texto, comprende el contexto y los detalles de tu descripción, y luego transforma esa comprensión en una representación visual - todo fluyendo suavemente de un paso al siguiente, tal como tu cerebro procesaría la misma información.

En esta sección, exploraremos las complejidades de construir tal canal multi-modelo. Dominarás conceptos esenciales como la transformación eficiente de datos entre modelos (asegurando que la salida de un modelo esté óptimamente formateada para el siguiente), la gestión sofisticada de procesos asíncronos (permitiendo que múltiples modelos trabajen simultáneamente cuando sea posible), y la implementación de una arquitectura de código limpia y mantenible. Profundizaremos en el manejo de casos extremos, la gestión de peculiaridades específicas de cada modelo y la garantía de un flujo de datos fluido a través de todo el canal. A través de un ejemplo práctico completo, obtendrás experiencia práctica con estos conceptos, preparándote para arquitectar y desplegar tus propias aplicaciones sofisticadas de IA multi-modelo que puedan escalar eficientemente y mantener un alto rendimiento en condiciones del mundo real.

En esta sección, crearás una aplicación web basada en Flask que integra múltiples modelos de IA para procesar entrada de audio y generar una imagen correspondiente. Específicamente, la aplicación:

  • Aceptará un archivo de audio subido por el usuario.
  • Transcribirá el contenido del audio a texto usando la API Whisper de OpenAI.
  • Analizará el texto transcrito usando GPT-4o para extraer una representación descriptiva de la escena.
  • Generará una imagen basada en la descripción de la escena usando la API DALL·E 3 de OpenAI.
  • Mostrará tanto la transcripción del texto como la imagen generada en una sola página web.

Este proyecto demuestra un canal básico de IA multimodal, combinando la conversión de voz a texto y la generación de texto a imagen. Establece una base para construir aplicaciones más sofisticadas.

6.1.1 Implementación Paso a Paso

Paso 1: Configurar la Estructura del Proyecto

Descarga el archivo de ejemplo: https://files.cuantum.tech/audio/gpt-dalle-whisper-sample.mp3

Organiza tus archivos del proyecto de la siguiente manera:

/multimodal_app

├── app.py
├── .env
└── templates/
    └── index.html
└── utils/
    ├── transcribe.py
    ├── generate_prompt.py
    ├── generate_image.py
    └── audio_analysis.py  # New module for audio analysis
  • /multimodal_app: El directorio raíz de tu proyecto.
  • app.py: El archivo principal de la aplicación Flask.
  • .env: Un archivo para almacenar tu clave API de OpenAI.
  • templates/: Un directorio para plantillas HTML.
  • templates/index.html: La plantilla HTML para la interfaz de usuario.
  • utils/: Un directorio para módulos Python que contienen funciones reutilizables.
    • transcribe.py: Contiene la función para transcribir audio usando Whisper.
    • generate_prompt.py: Contiene la función para generar un prompt de imagen usando GPT-4o.
    • generate_image.py: Contiene la función para generar una imagen con DALL·E 3.
    • audio_analysis.py: Nuevo módulo para analizar audio.

Paso 2: Instalar Paquetes Requeridos

Instala las bibliotecas Python necesarias:

pip install flask openai python-dotenv

Paso 3: Crear Módulos de Utilidad

Crea los siguientes archivos Python en el directorio utils/:

utils/transcribe.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


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_path}")
        audio_file = open(file_path, "rb")
        response = openai.audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
        )
        transcript = response.text
        audio_file.close()
        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
  • Este módulo define la función transcribe_audio, que toma como entrada la ruta a un archivo de audio y utiliza la API Whisper de OpenAI para generar una transcripción de texto.
  • La función abre el archivo de audio en modo de lectura binaria ("rb").
  • Llama a openai.audio.transcriptions.create() para realizar la transcripción, especificando el modelo "whisper-1".
  • Extrae el texto transcrito de la respuesta de la API.
  • Incluye manejo de errores utilizando un bloque try...except para capturar posibles excepciones openai.error.OpenAIError (específicas de OpenAI) y Exception general para otros errores. Si ocurre un error, registra el error y devuelve None.
  • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
  • El archivo de audio se cierra después de la transcripción.

utils/generate_prompt.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def create_image_prompt(transcription: str) -> Optional[str]:
    """
    Generates a detailed image prompt from a text transcription using OpenAI's Chat Completion API.

    Args:
        transcription (str): The text transcription of the audio.

    Returns:
        Optional[str]: A detailed text prompt suitable for image generation, or None on error.
    """
    try:
        logger.info("Generating image prompt from transcription")
        response = openai.chat.completions.create(
            model="gpt-4o",  #  Use a powerful chat model
            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'.  Incorporate scene lighting, time of day, weather, and camera angle into the description.",
                },
                {"role": "user", "content": transcription},
            ],
        )
        prompt = response.choices[0].message.content
        logger.info(f"Generated 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
  • Este módulo define la función create_image_prompt, que toma el texto transcrito como entrada y utiliza la API de Chat Completion de OpenAI para generar un prompt detallado para la generación de imágenes.
  • El mensaje del sistema instruye al modelo a actuar como un asistente creativo y generar una descripción vívida de la escena. El prompt del sistema es crucial para guiar al LLM en la generación de un prompt de alta calidad. Instruimos al LLM para que se enfoque en elementos visuales e incorpore detalles como la iluminación, hora del día, clima y ángulo de la cámara.
  • El mensaje del usuario proporciona el texto transcrito como contenido para que el modelo trabaje.
  • La función extrae el prompt generado de la respuesta de la API.
  • Incluye manejo de errores.

utils/generate_image.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def generate_dalle_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.images.generate(
            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
  • Este módulo define la función generate_dalle_image, que toma un prompt de texto como entrada y utiliza la API DALL·E de OpenAI para generar una imagen.
  • Llama al método openai.images.generate() para generar la imagen.
  • Extrae la URL de la imagen generada de la respuesta de la API.
  • Incluye manejo de errores.

Paso 4: Crear la Aplicación Principal (app.py)

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

from flask import Flask, request, render_template, jsonify, make_response, redirect, url_for
import os
from dotenv import load_dotenv
import logging
from typing import Optional
from werkzeug.utils import secure_filename
from werkzeug.datastructures import FileStorage

# Import the utility functions from the utils directory
from utils.transcribe import transcribe_audio
from utils.generate_prompt import create_image_prompt
from utils.generate_image import generate_dalle_image

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

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'  # Store uploaded files
app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024  # 25MB max file size - increased for larger audio files
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)  # Create the upload folder if it doesn't exist

# 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


@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, error_message=error_message)

        file: FileStorage = request.files['audio_file']  # Use type hinting
        if file.filename == '':
            error_message = "No file selected"
            logger.warning(error_message)
            return render_template("index.html", error=error_message, error_message=error_message)

        if file and allowed_file(file.filename):
            try:
                # Secure the filename and construct a safe path
                filename = secure_filename(file.filename)
                file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
                file.save(file_path)  # Save the uploaded file

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

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

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

                # Optionally, delete the uploaded file after processing
                os.remove(file_path)
                logger.info(f"Successfully processed audio file and generated image.")
                return render_template("index.html", transcript=transcript, image_url=image_url, prompt_summary=prompt_summary)

            except Exception as e:
                error_message = f"An error occurred: {e}"
                logger.error(error_message)
                return render_template("index.html", error=error_message, error_message=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, error_message=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, osdotenvloggingOptional y Dict para el tipado de datos, y secure_filename
  • Variables de Entorno: Carga la clave API de OpenAI desde el archivo .env.
  • Aplicación Flask:
    • Crea una instancia de la aplicación Flask.
    • Configura una carpeta de subida y el tamaño máximo de archivo. El UPLOAD_FOLDER se establece como 'uploads', y MAX_CONTENT_LENGTH se establece en 25MB. La carpeta de subida se crea si no existe.
  • 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.transcriptions.create() de la API de OpenAI para transcribir el audio.
    • Extrae el texto transcrito de la respuesta de la API.
    • 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. El archivo de audio se cierra después de la transcripción.
  • 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 prompt de texto adecuado para la generación de imágenes.
    • El mensaje del sistema instruye al modelo para que actúe como un asistente creativo y proporcione una descripción vívida y detallada de una escena que pueda utilizarse para generar una imagen con un modelo de generación de imágenes de IA.
    • Extrae el prompt generado de la respuesta de la API.
    • Incluye manejo de errores.
  • Función generate_image:
    • Recibe el prompt de 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 solicitudes GET y POST.
    • Para solicitudes GET, renderiza la página HTML inicial.
    • Para solicitudes POST (cuando el usuario sube un archivo de audio):
      • Valida el archivo subido:
        • Verifica si existe la parte del archivo en la solicitud.
        • Verifica si se seleccionó un archivo.
        • Verifica si el tipo de archivo está permitido usando la función allowed_file.
      • Guarda el archivo subido en una ubicación temporal usando un nombre de archivo seguro.
      • Llama a las funciones de utilidad para:
        • Transcribir el audio usando transcribe_audio().
        • Generar un prompt de imagen a partir de la transcripción usando create_image_prompt().
        • Generar una imagen a partir del prompt usando generate_dalle_image().
      • Maneja los errores que puedan ocurrir durante cualquiera de estos pasos, registrando el error y renderizando la plantilla index.html con un mensaje de error apropiado.
      • Si todos los pasos son exitosos, renderiza la plantilla index.html, pasando el texto de transcripción, URL de la imagen y prompt generado para ser mostrados.
      • Elimina el archivo subido después del procesamiento
  • @app.errorhandler(500): Maneja errores HTTP 500 (Error Interno del Servidor) registrando el error y renderizando 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 5: Crear la 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 AI 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 */
            margin-bottom: 1.5rem;
            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 */
            text-align: left;
        }
        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 */
            min-height: 100px;
        }
        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="result-container">
                <h3>📝 Transcript:</h3>
                <textarea readonly>{{ transcript }}</textarea>
            </div>
        {% endif %}

        {% if prompt_summary %}
            <div class="result-container">
                <h3>🎯 Scene Prompt:</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 &lt;head&gt; define el título, enlaza una hoja de estilo CSS y establece el viewport para la capacidad de respuesta.
    • El &lt;body&gt; contiene el contenido visible, incluyendo un formulario para cargar audio y secciones para mostrar la transcripción y la imagen generada.
  • Estilizado CSS:
    • Diseño moderno y adaptable.
    • Elementos de formulario con estilo.
    • Presentación clara de resultados (transcripción e imagen).
    • Visualización de mensajes de error amigable para el usuario.
  • Formulario:
    • Se utiliza un &lt;form&gt; con enctype="multipart/form-data" para manejar la carga de archivos.
    • Un &lt;label&gt; y &lt;input type="file"&gt; permiten al usuario seleccionar un archivo de audio. El atributo accept="audio/*" restringe al usuario a cargar solo archivos de audio.
    • Un botón &lt;input type="submit"&gt; permite al usuario enviar el formulario.
  • Visualización de Transcripción e Imagen:
    • La plantilla utiliza el sistema de plantillas Jinja2 para mostrar condicionalmente el texto de la transcripción y la imagen generada si están disponibles. La transcripción se muestra en un textarea, y la imagen se muestra usando una etiqueta &lt;img&gt;.
  • Manejo de Errores:
    • Se utiliza un &lt;div class="error-message"&gt; para mostrar mensajes de error al usuario.

En esta sección, has adquirido conocimientos valiosos sobre técnicas avanzadas de integración de IA. Analicemos lo que has aprendido:

  • Organizar la lógica multimodal en módulos reutilizables
    • Crear estructuras de código limpias y mantenibles
    • Desarrollar componentes modulares que pueden actualizarse y reutilizarse fácilmente
    • Implementar manejo de errores y registro adecuados
  • Encadenar audio ➝ texto ➝ prompt ➝ imagen de manera eficiente
    • Procesar entradas de audio usando Whisper para una transcripción precisa
    • Transformar texto transcrito en prompts significativos con GPT
    • Generar imágenes relevantes usando DALL·E basadas en el texto procesado
  • Construir una aplicación Flask profesional que utiliza los tres modelos principales de OpenAI en un solo flujo
    • Configurar el enrutamiento y manejo de solicitudes adecuado
    • Gestionar las interacciones con la API de manera eficiente
    • Crear una interfaz de usuario intuitiva para una interacción fluida

Ahora comprendes el poder de encadenar modelos para crear experiencias sofisticadas de IA. Este conocimiento abre innumerables posibilidades para la innovación. Ya sea que estés construyendo una herramienta de diario con IA que convierte notas de voz en entradas ilustradas, una aplicación de diseño controlada por voz que transforma descripciones habladas en arte visual, o un asistente de contenido multimodal que ayuda a crear contenido multimedia rico, este flujo de trabajo fundamental puede llevarte lejos. Las habilidades que has aprendido aquí forman la base para crear aplicaciones de IA complejas y fáciles de usar que combinan múltiples modalidades de manera efectiva.

6.1 Combinando GPT + DALL·E + Whisper

En capítulos anteriores, has explorado las capacidades individuales de tres poderosos modelos de IA: GPT para el procesamiento del lenguaje natural y la generación de texto, DALL·E para crear imágenes detalladas a partir de descripciones textuales, y Whisper para la conversión precisa de voz a texto. Has aprendido a construir aplicaciones independientes como chatbots que mantienen conversaciones naturales, generadores visuales que dan vida a las ideas y herramientas de transcripción que convierten palabras habladas en texto. Incluso has experimentado con aplicaciones multimodales simples que utilizan dos de estas tecnologías en conjunto.

Ahora, daremos un salto significativo: aprenderás a crear suites de IA cohesivas — sistemas sofisticados que integran perfectamente el reconocimiento de voz, el procesamiento de texto y la generación de imágenes en aplicaciones unificadas y potentes. Estas suites pueden manejar flujos de trabajo complejos, como convertir una descripción hablada en una narrativa escrita y luego transformar esa narrativa en una representación visual, todo en un proceso fluido.

Considera este capítulo como tu avance de ser un desarrollador competente en APIs a convertirte en un arquitecto de flujos de trabajo de IA modulares y orquestados. Esta es la misma tecnología que impulsa muchas de las principales soluciones de software actuales. Las grandes empresas implementan estos sistemas integrados de IA en sus herramientas de productividad (como procesadores de documentos avanzados), plataformas de experiencia del cliente (como sistemas de soporte inteligente) y aplicaciones creativas (incluyendo herramientas de diseño potenciadas por IA) que sirven a bases masivas de usuarios que van desde miles hasta millones.

A lo largo de este capítulo, dominarás varias habilidades cruciales:

  • Encadenar modelos con lógica dinámica - Aprende a crear árboles de decisión inteligentes que determinen cómo deben interactuar los diferentes modelos de IA y cuándo invocar capacidades específicas
  • Manejar entrada en múltiples formatos - Desarrollar sistemas robustos que puedan procesar y validar varios tipos de entrada, desde archivos de audio hasta indicaciones de texto y datos de imagen
  • Devolver resultados significativos en múltiples modalidades - Crear un manejo sofisticado de respuestas que pueda entregar resultados en múltiples formatos mientras mantiene el contexto y la coherencia
  • Construir canales en tiempo real o casi real - Optimizar tus aplicaciones para el rendimiento, asegurando tiempos de respuesta rápidos incluso cuando múltiples modelos de IA trabajan juntos

Y comenzamos con una sección fundamental:

La construcción de aplicaciones sofisticadas de IA requiere la integración cuidadosa de múltiples modelos especializados para crear una experiencia inteligente y fluida. Cada modelo de IA sirve como maestro en su dominio: GPT sobresale en la comprensión y generación de texto similar al humano, DALL·E se especializa en crear impresionantes obras de arte visuales a partir de descripciones textuales, y Whisper demuestra una precisión notable en la conversión de palabras habladas a texto. Sin embargo, la verdadera innovación surge cuando estos poderosos elementos individuales se orquestan para trabajar juntos en perfecta armonía.

Esta integración sofisticada permite la creación de aplicaciones que reflejan los procesos cognitivos humanos al manejar múltiples tipos de información simultáneamente. Considera el flujo natural de la comunicación humana: hablamos, entendemos el contexto y visualizamos conceptos sin esfuerzo. Ahora imagina un sistema de IA que coincida con este proceso natural: describes una escena verbalmente, el sistema procesa tu discurso en texto, comprende el contexto y los detalles de tu descripción, y luego transforma esa comprensión en una representación visual - todo fluyendo suavemente de un paso al siguiente, tal como tu cerebro procesaría la misma información.

En esta sección, exploraremos las complejidades de construir tal canal multi-modelo. Dominarás conceptos esenciales como la transformación eficiente de datos entre modelos (asegurando que la salida de un modelo esté óptimamente formateada para el siguiente), la gestión sofisticada de procesos asíncronos (permitiendo que múltiples modelos trabajen simultáneamente cuando sea posible), y la implementación de una arquitectura de código limpia y mantenible. Profundizaremos en el manejo de casos extremos, la gestión de peculiaridades específicas de cada modelo y la garantía de un flujo de datos fluido a través de todo el canal. A través de un ejemplo práctico completo, obtendrás experiencia práctica con estos conceptos, preparándote para arquitectar y desplegar tus propias aplicaciones sofisticadas de IA multi-modelo que puedan escalar eficientemente y mantener un alto rendimiento en condiciones del mundo real.

En esta sección, crearás una aplicación web basada en Flask que integra múltiples modelos de IA para procesar entrada de audio y generar una imagen correspondiente. Específicamente, la aplicación:

  • Aceptará un archivo de audio subido por el usuario.
  • Transcribirá el contenido del audio a texto usando la API Whisper de OpenAI.
  • Analizará el texto transcrito usando GPT-4o para extraer una representación descriptiva de la escena.
  • Generará una imagen basada en la descripción de la escena usando la API DALL·E 3 de OpenAI.
  • Mostrará tanto la transcripción del texto como la imagen generada en una sola página web.

Este proyecto demuestra un canal básico de IA multimodal, combinando la conversión de voz a texto y la generación de texto a imagen. Establece una base para construir aplicaciones más sofisticadas.

6.1.1 Implementación Paso a Paso

Paso 1: Configurar la Estructura del Proyecto

Descarga el archivo de ejemplo: https://files.cuantum.tech/audio/gpt-dalle-whisper-sample.mp3

Organiza tus archivos del proyecto de la siguiente manera:

/multimodal_app

├── app.py
├── .env
└── templates/
    └── index.html
└── utils/
    ├── transcribe.py
    ├── generate_prompt.py
    ├── generate_image.py
    └── audio_analysis.py  # New module for audio analysis
  • /multimodal_app: El directorio raíz de tu proyecto.
  • app.py: El archivo principal de la aplicación Flask.
  • .env: Un archivo para almacenar tu clave API de OpenAI.
  • templates/: Un directorio para plantillas HTML.
  • templates/index.html: La plantilla HTML para la interfaz de usuario.
  • utils/: Un directorio para módulos Python que contienen funciones reutilizables.
    • transcribe.py: Contiene la función para transcribir audio usando Whisper.
    • generate_prompt.py: Contiene la función para generar un prompt de imagen usando GPT-4o.
    • generate_image.py: Contiene la función para generar una imagen con DALL·E 3.
    • audio_analysis.py: Nuevo módulo para analizar audio.

Paso 2: Instalar Paquetes Requeridos

Instala las bibliotecas Python necesarias:

pip install flask openai python-dotenv

Paso 3: Crear Módulos de Utilidad

Crea los siguientes archivos Python en el directorio utils/:

utils/transcribe.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


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_path}")
        audio_file = open(file_path, "rb")
        response = openai.audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
        )
        transcript = response.text
        audio_file.close()
        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
  • Este módulo define la función transcribe_audio, que toma como entrada la ruta a un archivo de audio y utiliza la API Whisper de OpenAI para generar una transcripción de texto.
  • La función abre el archivo de audio en modo de lectura binaria ("rb").
  • Llama a openai.audio.transcriptions.create() para realizar la transcripción, especificando el modelo "whisper-1".
  • Extrae el texto transcrito de la respuesta de la API.
  • Incluye manejo de errores utilizando un bloque try...except para capturar posibles excepciones openai.error.OpenAIError (específicas de OpenAI) y Exception general para otros errores. Si ocurre un error, registra el error y devuelve None.
  • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
  • El archivo de audio se cierra después de la transcripción.

utils/generate_prompt.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def create_image_prompt(transcription: str) -> Optional[str]:
    """
    Generates a detailed image prompt from a text transcription using OpenAI's Chat Completion API.

    Args:
        transcription (str): The text transcription of the audio.

    Returns:
        Optional[str]: A detailed text prompt suitable for image generation, or None on error.
    """
    try:
        logger.info("Generating image prompt from transcription")
        response = openai.chat.completions.create(
            model="gpt-4o",  #  Use a powerful chat model
            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'.  Incorporate scene lighting, time of day, weather, and camera angle into the description.",
                },
                {"role": "user", "content": transcription},
            ],
        )
        prompt = response.choices[0].message.content
        logger.info(f"Generated 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
  • Este módulo define la función create_image_prompt, que toma el texto transcrito como entrada y utiliza la API de Chat Completion de OpenAI para generar un prompt detallado para la generación de imágenes.
  • El mensaje del sistema instruye al modelo a actuar como un asistente creativo y generar una descripción vívida de la escena. El prompt del sistema es crucial para guiar al LLM en la generación de un prompt de alta calidad. Instruimos al LLM para que se enfoque en elementos visuales e incorpore detalles como la iluminación, hora del día, clima y ángulo de la cámara.
  • El mensaje del usuario proporciona el texto transcrito como contenido para que el modelo trabaje.
  • La función extrae el prompt generado de la respuesta de la API.
  • Incluye manejo de errores.

utils/generate_image.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def generate_dalle_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.images.generate(
            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
  • Este módulo define la función generate_dalle_image, que toma un prompt de texto como entrada y utiliza la API DALL·E de OpenAI para generar una imagen.
  • Llama al método openai.images.generate() para generar la imagen.
  • Extrae la URL de la imagen generada de la respuesta de la API.
  • Incluye manejo de errores.

Paso 4: Crear la Aplicación Principal (app.py)

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

from flask import Flask, request, render_template, jsonify, make_response, redirect, url_for
import os
from dotenv import load_dotenv
import logging
from typing import Optional
from werkzeug.utils import secure_filename
from werkzeug.datastructures import FileStorage

# Import the utility functions from the utils directory
from utils.transcribe import transcribe_audio
from utils.generate_prompt import create_image_prompt
from utils.generate_image import generate_dalle_image

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

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'  # Store uploaded files
app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024  # 25MB max file size - increased for larger audio files
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)  # Create the upload folder if it doesn't exist

# 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


@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, error_message=error_message)

        file: FileStorage = request.files['audio_file']  # Use type hinting
        if file.filename == '':
            error_message = "No file selected"
            logger.warning(error_message)
            return render_template("index.html", error=error_message, error_message=error_message)

        if file and allowed_file(file.filename):
            try:
                # Secure the filename and construct a safe path
                filename = secure_filename(file.filename)
                file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
                file.save(file_path)  # Save the uploaded file

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

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

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

                # Optionally, delete the uploaded file after processing
                os.remove(file_path)
                logger.info(f"Successfully processed audio file and generated image.")
                return render_template("index.html", transcript=transcript, image_url=image_url, prompt_summary=prompt_summary)

            except Exception as e:
                error_message = f"An error occurred: {e}"
                logger.error(error_message)
                return render_template("index.html", error=error_message, error_message=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, error_message=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, osdotenvloggingOptional y Dict para el tipado de datos, y secure_filename
  • Variables de Entorno: Carga la clave API de OpenAI desde el archivo .env.
  • Aplicación Flask:
    • Crea una instancia de la aplicación Flask.
    • Configura una carpeta de subida y el tamaño máximo de archivo. El UPLOAD_FOLDER se establece como 'uploads', y MAX_CONTENT_LENGTH se establece en 25MB. La carpeta de subida se crea si no existe.
  • 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.transcriptions.create() de la API de OpenAI para transcribir el audio.
    • Extrae el texto transcrito de la respuesta de la API.
    • 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. El archivo de audio se cierra después de la transcripción.
  • 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 prompt de texto adecuado para la generación de imágenes.
    • El mensaje del sistema instruye al modelo para que actúe como un asistente creativo y proporcione una descripción vívida y detallada de una escena que pueda utilizarse para generar una imagen con un modelo de generación de imágenes de IA.
    • Extrae el prompt generado de la respuesta de la API.
    • Incluye manejo de errores.
  • Función generate_image:
    • Recibe el prompt de 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 solicitudes GET y POST.
    • Para solicitudes GET, renderiza la página HTML inicial.
    • Para solicitudes POST (cuando el usuario sube un archivo de audio):
      • Valida el archivo subido:
        • Verifica si existe la parte del archivo en la solicitud.
        • Verifica si se seleccionó un archivo.
        • Verifica si el tipo de archivo está permitido usando la función allowed_file.
      • Guarda el archivo subido en una ubicación temporal usando un nombre de archivo seguro.
      • Llama a las funciones de utilidad para:
        • Transcribir el audio usando transcribe_audio().
        • Generar un prompt de imagen a partir de la transcripción usando create_image_prompt().
        • Generar una imagen a partir del prompt usando generate_dalle_image().
      • Maneja los errores que puedan ocurrir durante cualquiera de estos pasos, registrando el error y renderizando la plantilla index.html con un mensaje de error apropiado.
      • Si todos los pasos son exitosos, renderiza la plantilla index.html, pasando el texto de transcripción, URL de la imagen y prompt generado para ser mostrados.
      • Elimina el archivo subido después del procesamiento
  • @app.errorhandler(500): Maneja errores HTTP 500 (Error Interno del Servidor) registrando el error y renderizando 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 5: Crear la 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 AI 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 */
            margin-bottom: 1.5rem;
            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 */
            text-align: left;
        }
        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 */
            min-height: 100px;
        }
        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="result-container">
                <h3>📝 Transcript:</h3>
                <textarea readonly>{{ transcript }}</textarea>
            </div>
        {% endif %}

        {% if prompt_summary %}
            <div class="result-container">
                <h3>🎯 Scene Prompt:</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 &lt;head&gt; define el título, enlaza una hoja de estilo CSS y establece el viewport para la capacidad de respuesta.
    • El &lt;body&gt; contiene el contenido visible, incluyendo un formulario para cargar audio y secciones para mostrar la transcripción y la imagen generada.
  • Estilizado CSS:
    • Diseño moderno y adaptable.
    • Elementos de formulario con estilo.
    • Presentación clara de resultados (transcripción e imagen).
    • Visualización de mensajes de error amigable para el usuario.
  • Formulario:
    • Se utiliza un &lt;form&gt; con enctype="multipart/form-data" para manejar la carga de archivos.
    • Un &lt;label&gt; y &lt;input type="file"&gt; permiten al usuario seleccionar un archivo de audio. El atributo accept="audio/*" restringe al usuario a cargar solo archivos de audio.
    • Un botón &lt;input type="submit"&gt; permite al usuario enviar el formulario.
  • Visualización de Transcripción e Imagen:
    • La plantilla utiliza el sistema de plantillas Jinja2 para mostrar condicionalmente el texto de la transcripción y la imagen generada si están disponibles. La transcripción se muestra en un textarea, y la imagen se muestra usando una etiqueta &lt;img&gt;.
  • Manejo de Errores:
    • Se utiliza un &lt;div class="error-message"&gt; para mostrar mensajes de error al usuario.

En esta sección, has adquirido conocimientos valiosos sobre técnicas avanzadas de integración de IA. Analicemos lo que has aprendido:

  • Organizar la lógica multimodal en módulos reutilizables
    • Crear estructuras de código limpias y mantenibles
    • Desarrollar componentes modulares que pueden actualizarse y reutilizarse fácilmente
    • Implementar manejo de errores y registro adecuados
  • Encadenar audio ➝ texto ➝ prompt ➝ imagen de manera eficiente
    • Procesar entradas de audio usando Whisper para una transcripción precisa
    • Transformar texto transcrito en prompts significativos con GPT
    • Generar imágenes relevantes usando DALL·E basadas en el texto procesado
  • Construir una aplicación Flask profesional que utiliza los tres modelos principales de OpenAI en un solo flujo
    • Configurar el enrutamiento y manejo de solicitudes adecuado
    • Gestionar las interacciones con la API de manera eficiente
    • Crear una interfaz de usuario intuitiva para una interacción fluida

Ahora comprendes el poder de encadenar modelos para crear experiencias sofisticadas de IA. Este conocimiento abre innumerables posibilidades para la innovación. Ya sea que estés construyendo una herramienta de diario con IA que convierte notas de voz en entradas ilustradas, una aplicación de diseño controlada por voz que transforma descripciones habladas en arte visual, o un asistente de contenido multimodal que ayuda a crear contenido multimedia rico, este flujo de trabajo fundamental puede llevarte lejos. Las habilidades que has aprendido aquí forman la base para crear aplicaciones de IA complejas y fáciles de usar que combinan múltiples modalidades de manera efectiva.

6.1 Combinando GPT + DALL·E + Whisper

En capítulos anteriores, has explorado las capacidades individuales de tres poderosos modelos de IA: GPT para el procesamiento del lenguaje natural y la generación de texto, DALL·E para crear imágenes detalladas a partir de descripciones textuales, y Whisper para la conversión precisa de voz a texto. Has aprendido a construir aplicaciones independientes como chatbots que mantienen conversaciones naturales, generadores visuales que dan vida a las ideas y herramientas de transcripción que convierten palabras habladas en texto. Incluso has experimentado con aplicaciones multimodales simples que utilizan dos de estas tecnologías en conjunto.

Ahora, daremos un salto significativo: aprenderás a crear suites de IA cohesivas — sistemas sofisticados que integran perfectamente el reconocimiento de voz, el procesamiento de texto y la generación de imágenes en aplicaciones unificadas y potentes. Estas suites pueden manejar flujos de trabajo complejos, como convertir una descripción hablada en una narrativa escrita y luego transformar esa narrativa en una representación visual, todo en un proceso fluido.

Considera este capítulo como tu avance de ser un desarrollador competente en APIs a convertirte en un arquitecto de flujos de trabajo de IA modulares y orquestados. Esta es la misma tecnología que impulsa muchas de las principales soluciones de software actuales. Las grandes empresas implementan estos sistemas integrados de IA en sus herramientas de productividad (como procesadores de documentos avanzados), plataformas de experiencia del cliente (como sistemas de soporte inteligente) y aplicaciones creativas (incluyendo herramientas de diseño potenciadas por IA) que sirven a bases masivas de usuarios que van desde miles hasta millones.

A lo largo de este capítulo, dominarás varias habilidades cruciales:

  • Encadenar modelos con lógica dinámica - Aprende a crear árboles de decisión inteligentes que determinen cómo deben interactuar los diferentes modelos de IA y cuándo invocar capacidades específicas
  • Manejar entrada en múltiples formatos - Desarrollar sistemas robustos que puedan procesar y validar varios tipos de entrada, desde archivos de audio hasta indicaciones de texto y datos de imagen
  • Devolver resultados significativos en múltiples modalidades - Crear un manejo sofisticado de respuestas que pueda entregar resultados en múltiples formatos mientras mantiene el contexto y la coherencia
  • Construir canales en tiempo real o casi real - Optimizar tus aplicaciones para el rendimiento, asegurando tiempos de respuesta rápidos incluso cuando múltiples modelos de IA trabajan juntos

Y comenzamos con una sección fundamental:

La construcción de aplicaciones sofisticadas de IA requiere la integración cuidadosa de múltiples modelos especializados para crear una experiencia inteligente y fluida. Cada modelo de IA sirve como maestro en su dominio: GPT sobresale en la comprensión y generación de texto similar al humano, DALL·E se especializa en crear impresionantes obras de arte visuales a partir de descripciones textuales, y Whisper demuestra una precisión notable en la conversión de palabras habladas a texto. Sin embargo, la verdadera innovación surge cuando estos poderosos elementos individuales se orquestan para trabajar juntos en perfecta armonía.

Esta integración sofisticada permite la creación de aplicaciones que reflejan los procesos cognitivos humanos al manejar múltiples tipos de información simultáneamente. Considera el flujo natural de la comunicación humana: hablamos, entendemos el contexto y visualizamos conceptos sin esfuerzo. Ahora imagina un sistema de IA que coincida con este proceso natural: describes una escena verbalmente, el sistema procesa tu discurso en texto, comprende el contexto y los detalles de tu descripción, y luego transforma esa comprensión en una representación visual - todo fluyendo suavemente de un paso al siguiente, tal como tu cerebro procesaría la misma información.

En esta sección, exploraremos las complejidades de construir tal canal multi-modelo. Dominarás conceptos esenciales como la transformación eficiente de datos entre modelos (asegurando que la salida de un modelo esté óptimamente formateada para el siguiente), la gestión sofisticada de procesos asíncronos (permitiendo que múltiples modelos trabajen simultáneamente cuando sea posible), y la implementación de una arquitectura de código limpia y mantenible. Profundizaremos en el manejo de casos extremos, la gestión de peculiaridades específicas de cada modelo y la garantía de un flujo de datos fluido a través de todo el canal. A través de un ejemplo práctico completo, obtendrás experiencia práctica con estos conceptos, preparándote para arquitectar y desplegar tus propias aplicaciones sofisticadas de IA multi-modelo que puedan escalar eficientemente y mantener un alto rendimiento en condiciones del mundo real.

En esta sección, crearás una aplicación web basada en Flask que integra múltiples modelos de IA para procesar entrada de audio y generar una imagen correspondiente. Específicamente, la aplicación:

  • Aceptará un archivo de audio subido por el usuario.
  • Transcribirá el contenido del audio a texto usando la API Whisper de OpenAI.
  • Analizará el texto transcrito usando GPT-4o para extraer una representación descriptiva de la escena.
  • Generará una imagen basada en la descripción de la escena usando la API DALL·E 3 de OpenAI.
  • Mostrará tanto la transcripción del texto como la imagen generada en una sola página web.

Este proyecto demuestra un canal básico de IA multimodal, combinando la conversión de voz a texto y la generación de texto a imagen. Establece una base para construir aplicaciones más sofisticadas.

6.1.1 Implementación Paso a Paso

Paso 1: Configurar la Estructura del Proyecto

Descarga el archivo de ejemplo: https://files.cuantum.tech/audio/gpt-dalle-whisper-sample.mp3

Organiza tus archivos del proyecto de la siguiente manera:

/multimodal_app

├── app.py
├── .env
└── templates/
    └── index.html
└── utils/
    ├── transcribe.py
    ├── generate_prompt.py
    ├── generate_image.py
    └── audio_analysis.py  # New module for audio analysis
  • /multimodal_app: El directorio raíz de tu proyecto.
  • app.py: El archivo principal de la aplicación Flask.
  • .env: Un archivo para almacenar tu clave API de OpenAI.
  • templates/: Un directorio para plantillas HTML.
  • templates/index.html: La plantilla HTML para la interfaz de usuario.
  • utils/: Un directorio para módulos Python que contienen funciones reutilizables.
    • transcribe.py: Contiene la función para transcribir audio usando Whisper.
    • generate_prompt.py: Contiene la función para generar un prompt de imagen usando GPT-4o.
    • generate_image.py: Contiene la función para generar una imagen con DALL·E 3.
    • audio_analysis.py: Nuevo módulo para analizar audio.

Paso 2: Instalar Paquetes Requeridos

Instala las bibliotecas Python necesarias:

pip install flask openai python-dotenv

Paso 3: Crear Módulos de Utilidad

Crea los siguientes archivos Python en el directorio utils/:

utils/transcribe.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


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_path}")
        audio_file = open(file_path, "rb")
        response = openai.audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
        )
        transcript = response.text
        audio_file.close()
        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
  • Este módulo define la función transcribe_audio, que toma como entrada la ruta a un archivo de audio y utiliza la API Whisper de OpenAI para generar una transcripción de texto.
  • La función abre el archivo de audio en modo de lectura binaria ("rb").
  • Llama a openai.audio.transcriptions.create() para realizar la transcripción, especificando el modelo "whisper-1".
  • Extrae el texto transcrito de la respuesta de la API.
  • Incluye manejo de errores utilizando un bloque try...except para capturar posibles excepciones openai.error.OpenAIError (específicas de OpenAI) y Exception general para otros errores. Si ocurre un error, registra el error y devuelve None.
  • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
  • El archivo de audio se cierra después de la transcripción.

utils/generate_prompt.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def create_image_prompt(transcription: str) -> Optional[str]:
    """
    Generates a detailed image prompt from a text transcription using OpenAI's Chat Completion API.

    Args:
        transcription (str): The text transcription of the audio.

    Returns:
        Optional[str]: A detailed text prompt suitable for image generation, or None on error.
    """
    try:
        logger.info("Generating image prompt from transcription")
        response = openai.chat.completions.create(
            model="gpt-4o",  #  Use a powerful chat model
            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'.  Incorporate scene lighting, time of day, weather, and camera angle into the description.",
                },
                {"role": "user", "content": transcription},
            ],
        )
        prompt = response.choices[0].message.content
        logger.info(f"Generated 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
  • Este módulo define la función create_image_prompt, que toma el texto transcrito como entrada y utiliza la API de Chat Completion de OpenAI para generar un prompt detallado para la generación de imágenes.
  • El mensaje del sistema instruye al modelo a actuar como un asistente creativo y generar una descripción vívida de la escena. El prompt del sistema es crucial para guiar al LLM en la generación de un prompt de alta calidad. Instruimos al LLM para que se enfoque en elementos visuales e incorpore detalles como la iluminación, hora del día, clima y ángulo de la cámara.
  • El mensaje del usuario proporciona el texto transcrito como contenido para que el modelo trabaje.
  • La función extrae el prompt generado de la respuesta de la API.
  • Incluye manejo de errores.

utils/generate_image.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def generate_dalle_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.images.generate(
            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
  • Este módulo define la función generate_dalle_image, que toma un prompt de texto como entrada y utiliza la API DALL·E de OpenAI para generar una imagen.
  • Llama al método openai.images.generate() para generar la imagen.
  • Extrae la URL de la imagen generada de la respuesta de la API.
  • Incluye manejo de errores.

Paso 4: Crear la Aplicación Principal (app.py)

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

from flask import Flask, request, render_template, jsonify, make_response, redirect, url_for
import os
from dotenv import load_dotenv
import logging
from typing import Optional
from werkzeug.utils import secure_filename
from werkzeug.datastructures import FileStorage

# Import the utility functions from the utils directory
from utils.transcribe import transcribe_audio
from utils.generate_prompt import create_image_prompt
from utils.generate_image import generate_dalle_image

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

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'  # Store uploaded files
app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024  # 25MB max file size - increased for larger audio files
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)  # Create the upload folder if it doesn't exist

# 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


@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, error_message=error_message)

        file: FileStorage = request.files['audio_file']  # Use type hinting
        if file.filename == '':
            error_message = "No file selected"
            logger.warning(error_message)
            return render_template("index.html", error=error_message, error_message=error_message)

        if file and allowed_file(file.filename):
            try:
                # Secure the filename and construct a safe path
                filename = secure_filename(file.filename)
                file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
                file.save(file_path)  # Save the uploaded file

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

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

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

                # Optionally, delete the uploaded file after processing
                os.remove(file_path)
                logger.info(f"Successfully processed audio file and generated image.")
                return render_template("index.html", transcript=transcript, image_url=image_url, prompt_summary=prompt_summary)

            except Exception as e:
                error_message = f"An error occurred: {e}"
                logger.error(error_message)
                return render_template("index.html", error=error_message, error_message=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, error_message=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, osdotenvloggingOptional y Dict para el tipado de datos, y secure_filename
  • Variables de Entorno: Carga la clave API de OpenAI desde el archivo .env.
  • Aplicación Flask:
    • Crea una instancia de la aplicación Flask.
    • Configura una carpeta de subida y el tamaño máximo de archivo. El UPLOAD_FOLDER se establece como 'uploads', y MAX_CONTENT_LENGTH se establece en 25MB. La carpeta de subida se crea si no existe.
  • 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.transcriptions.create() de la API de OpenAI para transcribir el audio.
    • Extrae el texto transcrito de la respuesta de la API.
    • 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. El archivo de audio se cierra después de la transcripción.
  • 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 prompt de texto adecuado para la generación de imágenes.
    • El mensaje del sistema instruye al modelo para que actúe como un asistente creativo y proporcione una descripción vívida y detallada de una escena que pueda utilizarse para generar una imagen con un modelo de generación de imágenes de IA.
    • Extrae el prompt generado de la respuesta de la API.
    • Incluye manejo de errores.
  • Función generate_image:
    • Recibe el prompt de 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 solicitudes GET y POST.
    • Para solicitudes GET, renderiza la página HTML inicial.
    • Para solicitudes POST (cuando el usuario sube un archivo de audio):
      • Valida el archivo subido:
        • Verifica si existe la parte del archivo en la solicitud.
        • Verifica si se seleccionó un archivo.
        • Verifica si el tipo de archivo está permitido usando la función allowed_file.
      • Guarda el archivo subido en una ubicación temporal usando un nombre de archivo seguro.
      • Llama a las funciones de utilidad para:
        • Transcribir el audio usando transcribe_audio().
        • Generar un prompt de imagen a partir de la transcripción usando create_image_prompt().
        • Generar una imagen a partir del prompt usando generate_dalle_image().
      • Maneja los errores que puedan ocurrir durante cualquiera de estos pasos, registrando el error y renderizando la plantilla index.html con un mensaje de error apropiado.
      • Si todos los pasos son exitosos, renderiza la plantilla index.html, pasando el texto de transcripción, URL de la imagen y prompt generado para ser mostrados.
      • Elimina el archivo subido después del procesamiento
  • @app.errorhandler(500): Maneja errores HTTP 500 (Error Interno del Servidor) registrando el error y renderizando 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 5: Crear la 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 AI 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 */
            margin-bottom: 1.5rem;
            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 */
            text-align: left;
        }
        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 */
            min-height: 100px;
        }
        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="result-container">
                <h3>📝 Transcript:</h3>
                <textarea readonly>{{ transcript }}</textarea>
            </div>
        {% endif %}

        {% if prompt_summary %}
            <div class="result-container">
                <h3>🎯 Scene Prompt:</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 &lt;head&gt; define el título, enlaza una hoja de estilo CSS y establece el viewport para la capacidad de respuesta.
    • El &lt;body&gt; contiene el contenido visible, incluyendo un formulario para cargar audio y secciones para mostrar la transcripción y la imagen generada.
  • Estilizado CSS:
    • Diseño moderno y adaptable.
    • Elementos de formulario con estilo.
    • Presentación clara de resultados (transcripción e imagen).
    • Visualización de mensajes de error amigable para el usuario.
  • Formulario:
    • Se utiliza un &lt;form&gt; con enctype="multipart/form-data" para manejar la carga de archivos.
    • Un &lt;label&gt; y &lt;input type="file"&gt; permiten al usuario seleccionar un archivo de audio. El atributo accept="audio/*" restringe al usuario a cargar solo archivos de audio.
    • Un botón &lt;input type="submit"&gt; permite al usuario enviar el formulario.
  • Visualización de Transcripción e Imagen:
    • La plantilla utiliza el sistema de plantillas Jinja2 para mostrar condicionalmente el texto de la transcripción y la imagen generada si están disponibles. La transcripción se muestra en un textarea, y la imagen se muestra usando una etiqueta &lt;img&gt;.
  • Manejo de Errores:
    • Se utiliza un &lt;div class="error-message"&gt; para mostrar mensajes de error al usuario.

En esta sección, has adquirido conocimientos valiosos sobre técnicas avanzadas de integración de IA. Analicemos lo que has aprendido:

  • Organizar la lógica multimodal en módulos reutilizables
    • Crear estructuras de código limpias y mantenibles
    • Desarrollar componentes modulares que pueden actualizarse y reutilizarse fácilmente
    • Implementar manejo de errores y registro adecuados
  • Encadenar audio ➝ texto ➝ prompt ➝ imagen de manera eficiente
    • Procesar entradas de audio usando Whisper para una transcripción precisa
    • Transformar texto transcrito en prompts significativos con GPT
    • Generar imágenes relevantes usando DALL·E basadas en el texto procesado
  • Construir una aplicación Flask profesional que utiliza los tres modelos principales de OpenAI en un solo flujo
    • Configurar el enrutamiento y manejo de solicitudes adecuado
    • Gestionar las interacciones con la API de manera eficiente
    • Crear una interfaz de usuario intuitiva para una interacción fluida

Ahora comprendes el poder de encadenar modelos para crear experiencias sofisticadas de IA. Este conocimiento abre innumerables posibilidades para la innovación. Ya sea que estés construyendo una herramienta de diario con IA que convierte notas de voz en entradas ilustradas, una aplicación de diseño controlada por voz que transforma descripciones habladas en arte visual, o un asistente de contenido multimodal que ayuda a crear contenido multimedia rico, este flujo de trabajo fundamental puede llevarte lejos. Las habilidades que has aprendido aquí forman la base para crear aplicaciones de IA complejas y fáciles de usar que combinan múltiples modalidades de manera efectiva.

6.1 Combinando GPT + DALL·E + Whisper

En capítulos anteriores, has explorado las capacidades individuales de tres poderosos modelos de IA: GPT para el procesamiento del lenguaje natural y la generación de texto, DALL·E para crear imágenes detalladas a partir de descripciones textuales, y Whisper para la conversión precisa de voz a texto. Has aprendido a construir aplicaciones independientes como chatbots que mantienen conversaciones naturales, generadores visuales que dan vida a las ideas y herramientas de transcripción que convierten palabras habladas en texto. Incluso has experimentado con aplicaciones multimodales simples que utilizan dos de estas tecnologías en conjunto.

Ahora, daremos un salto significativo: aprenderás a crear suites de IA cohesivas — sistemas sofisticados que integran perfectamente el reconocimiento de voz, el procesamiento de texto y la generación de imágenes en aplicaciones unificadas y potentes. Estas suites pueden manejar flujos de trabajo complejos, como convertir una descripción hablada en una narrativa escrita y luego transformar esa narrativa en una representación visual, todo en un proceso fluido.

Considera este capítulo como tu avance de ser un desarrollador competente en APIs a convertirte en un arquitecto de flujos de trabajo de IA modulares y orquestados. Esta es la misma tecnología que impulsa muchas de las principales soluciones de software actuales. Las grandes empresas implementan estos sistemas integrados de IA en sus herramientas de productividad (como procesadores de documentos avanzados), plataformas de experiencia del cliente (como sistemas de soporte inteligente) y aplicaciones creativas (incluyendo herramientas de diseño potenciadas por IA) que sirven a bases masivas de usuarios que van desde miles hasta millones.

A lo largo de este capítulo, dominarás varias habilidades cruciales:

  • Encadenar modelos con lógica dinámica - Aprende a crear árboles de decisión inteligentes que determinen cómo deben interactuar los diferentes modelos de IA y cuándo invocar capacidades específicas
  • Manejar entrada en múltiples formatos - Desarrollar sistemas robustos que puedan procesar y validar varios tipos de entrada, desde archivos de audio hasta indicaciones de texto y datos de imagen
  • Devolver resultados significativos en múltiples modalidades - Crear un manejo sofisticado de respuestas que pueda entregar resultados en múltiples formatos mientras mantiene el contexto y la coherencia
  • Construir canales en tiempo real o casi real - Optimizar tus aplicaciones para el rendimiento, asegurando tiempos de respuesta rápidos incluso cuando múltiples modelos de IA trabajan juntos

Y comenzamos con una sección fundamental:

La construcción de aplicaciones sofisticadas de IA requiere la integración cuidadosa de múltiples modelos especializados para crear una experiencia inteligente y fluida. Cada modelo de IA sirve como maestro en su dominio: GPT sobresale en la comprensión y generación de texto similar al humano, DALL·E se especializa en crear impresionantes obras de arte visuales a partir de descripciones textuales, y Whisper demuestra una precisión notable en la conversión de palabras habladas a texto. Sin embargo, la verdadera innovación surge cuando estos poderosos elementos individuales se orquestan para trabajar juntos en perfecta armonía.

Esta integración sofisticada permite la creación de aplicaciones que reflejan los procesos cognitivos humanos al manejar múltiples tipos de información simultáneamente. Considera el flujo natural de la comunicación humana: hablamos, entendemos el contexto y visualizamos conceptos sin esfuerzo. Ahora imagina un sistema de IA que coincida con este proceso natural: describes una escena verbalmente, el sistema procesa tu discurso en texto, comprende el contexto y los detalles de tu descripción, y luego transforma esa comprensión en una representación visual - todo fluyendo suavemente de un paso al siguiente, tal como tu cerebro procesaría la misma información.

En esta sección, exploraremos las complejidades de construir tal canal multi-modelo. Dominarás conceptos esenciales como la transformación eficiente de datos entre modelos (asegurando que la salida de un modelo esté óptimamente formateada para el siguiente), la gestión sofisticada de procesos asíncronos (permitiendo que múltiples modelos trabajen simultáneamente cuando sea posible), y la implementación de una arquitectura de código limpia y mantenible. Profundizaremos en el manejo de casos extremos, la gestión de peculiaridades específicas de cada modelo y la garantía de un flujo de datos fluido a través de todo el canal. A través de un ejemplo práctico completo, obtendrás experiencia práctica con estos conceptos, preparándote para arquitectar y desplegar tus propias aplicaciones sofisticadas de IA multi-modelo que puedan escalar eficientemente y mantener un alto rendimiento en condiciones del mundo real.

En esta sección, crearás una aplicación web basada en Flask que integra múltiples modelos de IA para procesar entrada de audio y generar una imagen correspondiente. Específicamente, la aplicación:

  • Aceptará un archivo de audio subido por el usuario.
  • Transcribirá el contenido del audio a texto usando la API Whisper de OpenAI.
  • Analizará el texto transcrito usando GPT-4o para extraer una representación descriptiva de la escena.
  • Generará una imagen basada en la descripción de la escena usando la API DALL·E 3 de OpenAI.
  • Mostrará tanto la transcripción del texto como la imagen generada en una sola página web.

Este proyecto demuestra un canal básico de IA multimodal, combinando la conversión de voz a texto y la generación de texto a imagen. Establece una base para construir aplicaciones más sofisticadas.

6.1.1 Implementación Paso a Paso

Paso 1: Configurar la Estructura del Proyecto

Descarga el archivo de ejemplo: https://files.cuantum.tech/audio/gpt-dalle-whisper-sample.mp3

Organiza tus archivos del proyecto de la siguiente manera:

/multimodal_app

├── app.py
├── .env
└── templates/
    └── index.html
└── utils/
    ├── transcribe.py
    ├── generate_prompt.py
    ├── generate_image.py
    └── audio_analysis.py  # New module for audio analysis
  • /multimodal_app: El directorio raíz de tu proyecto.
  • app.py: El archivo principal de la aplicación Flask.
  • .env: Un archivo para almacenar tu clave API de OpenAI.
  • templates/: Un directorio para plantillas HTML.
  • templates/index.html: La plantilla HTML para la interfaz de usuario.
  • utils/: Un directorio para módulos Python que contienen funciones reutilizables.
    • transcribe.py: Contiene la función para transcribir audio usando Whisper.
    • generate_prompt.py: Contiene la función para generar un prompt de imagen usando GPT-4o.
    • generate_image.py: Contiene la función para generar una imagen con DALL·E 3.
    • audio_analysis.py: Nuevo módulo para analizar audio.

Paso 2: Instalar Paquetes Requeridos

Instala las bibliotecas Python necesarias:

pip install flask openai python-dotenv

Paso 3: Crear Módulos de Utilidad

Crea los siguientes archivos Python en el directorio utils/:

utils/transcribe.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


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_path}")
        audio_file = open(file_path, "rb")
        response = openai.audio.transcriptions.create(
            model="whisper-1",
            file=audio_file,
        )
        transcript = response.text
        audio_file.close()
        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
  • Este módulo define la función transcribe_audio, que toma como entrada la ruta a un archivo de audio y utiliza la API Whisper de OpenAI para generar una transcripción de texto.
  • La función abre el archivo de audio en modo de lectura binaria ("rb").
  • Llama a openai.audio.transcriptions.create() para realizar la transcripción, especificando el modelo "whisper-1".
  • Extrae el texto transcrito de la respuesta de la API.
  • Incluye manejo de errores utilizando un bloque try...except para capturar posibles excepciones openai.error.OpenAIError (específicas de OpenAI) y Exception general para otros errores. Si ocurre un error, registra el error y devuelve None.
  • Registra la ruta del archivo antes de la transcripción y la longitud del texto transcrito después de una transcripción exitosa.
  • El archivo de audio se cierra después de la transcripción.

utils/generate_prompt.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def create_image_prompt(transcription: str) -> Optional[str]:
    """
    Generates a detailed image prompt from a text transcription using OpenAI's Chat Completion API.

    Args:
        transcription (str): The text transcription of the audio.

    Returns:
        Optional[str]: A detailed text prompt suitable for image generation, or None on error.
    """
    try:
        logger.info("Generating image prompt from transcription")
        response = openai.chat.completions.create(
            model="gpt-4o",  #  Use a powerful chat model
            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'.  Incorporate scene lighting, time of day, weather, and camera angle into the description.",
                },
                {"role": "user", "content": transcription},
            ],
        )
        prompt = response.choices[0].message.content
        logger.info(f"Generated 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
  • Este módulo define la función create_image_prompt, que toma el texto transcrito como entrada y utiliza la API de Chat Completion de OpenAI para generar un prompt detallado para la generación de imágenes.
  • El mensaje del sistema instruye al modelo a actuar como un asistente creativo y generar una descripción vívida de la escena. El prompt del sistema es crucial para guiar al LLM en la generación de un prompt de alta calidad. Instruimos al LLM para que se enfoque en elementos visuales e incorpore detalles como la iluminación, hora del día, clima y ángulo de la cámara.
  • El mensaje del usuario proporciona el texto transcrito como contenido para que el modelo trabaje.
  • La función extrae el prompt generado de la respuesta de la API.
  • Incluye manejo de errores.

utils/generate_image.py:

import openai
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def generate_dalle_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.images.generate(
            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
  • Este módulo define la función generate_dalle_image, que toma un prompt de texto como entrada y utiliza la API DALL·E de OpenAI para generar una imagen.
  • Llama al método openai.images.generate() para generar la imagen.
  • Extrae la URL de la imagen generada de la respuesta de la API.
  • Incluye manejo de errores.

Paso 4: Crear la Aplicación Principal (app.py)

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

from flask import Flask, request, render_template, jsonify, make_response, redirect, url_for
import os
from dotenv import load_dotenv
import logging
from typing import Optional
from werkzeug.utils import secure_filename
from werkzeug.datastructures import FileStorage

# Import the utility functions from the utils directory
from utils.transcribe import transcribe_audio
from utils.generate_prompt import create_image_prompt
from utils.generate_image import generate_dalle_image

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

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'  # Store uploaded files
app.config['MAX_CONTENT_LENGTH'] = 25 * 1024 * 1024  # 25MB max file size - increased for larger audio files
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)  # Create the upload folder if it doesn't exist

# 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


@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, error_message=error_message)

        file: FileStorage = request.files['audio_file']  # Use type hinting
        if file.filename == '':
            error_message = "No file selected"
            logger.warning(error_message)
            return render_template("index.html", error=error_message, error_message=error_message)

        if file and allowed_file(file.filename):
            try:
                # Secure the filename and construct a safe path
                filename = secure_filename(file.filename)
                file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
                file.save(file_path)  # Save the uploaded file

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

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

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

                # Optionally, delete the uploaded file after processing
                os.remove(file_path)
                logger.info(f"Successfully processed audio file and generated image.")
                return render_template("index.html", transcript=transcript, image_url=image_url, prompt_summary=prompt_summary)

            except Exception as e:
                error_message = f"An error occurred: {e}"
                logger.error(error_message)
                return render_template("index.html", error=error_message, error_message=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, error_message=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, osdotenvloggingOptional y Dict para el tipado de datos, y secure_filename
  • Variables de Entorno: Carga la clave API de OpenAI desde el archivo .env.
  • Aplicación Flask:
    • Crea una instancia de la aplicación Flask.
    • Configura una carpeta de subida y el tamaño máximo de archivo. El UPLOAD_FOLDER se establece como 'uploads', y MAX_CONTENT_LENGTH se establece en 25MB. La carpeta de subida se crea si no existe.
  • 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.transcriptions.create() de la API de OpenAI para transcribir el audio.
    • Extrae el texto transcrito de la respuesta de la API.
    • 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. El archivo de audio se cierra después de la transcripción.
  • 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 prompt de texto adecuado para la generación de imágenes.
    • El mensaje del sistema instruye al modelo para que actúe como un asistente creativo y proporcione una descripción vívida y detallada de una escena que pueda utilizarse para generar una imagen con un modelo de generación de imágenes de IA.
    • Extrae el prompt generado de la respuesta de la API.
    • Incluye manejo de errores.
  • Función generate_image:
    • Recibe el prompt de 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 solicitudes GET y POST.
    • Para solicitudes GET, renderiza la página HTML inicial.
    • Para solicitudes POST (cuando el usuario sube un archivo de audio):
      • Valida el archivo subido:
        • Verifica si existe la parte del archivo en la solicitud.
        • Verifica si se seleccionó un archivo.
        • Verifica si el tipo de archivo está permitido usando la función allowed_file.
      • Guarda el archivo subido en una ubicación temporal usando un nombre de archivo seguro.
      • Llama a las funciones de utilidad para:
        • Transcribir el audio usando transcribe_audio().
        • Generar un prompt de imagen a partir de la transcripción usando create_image_prompt().
        • Generar una imagen a partir del prompt usando generate_dalle_image().
      • Maneja los errores que puedan ocurrir durante cualquiera de estos pasos, registrando el error y renderizando la plantilla index.html con un mensaje de error apropiado.
      • Si todos los pasos son exitosos, renderiza la plantilla index.html, pasando el texto de transcripción, URL de la imagen y prompt generado para ser mostrados.
      • Elimina el archivo subido después del procesamiento
  • @app.errorhandler(500): Maneja errores HTTP 500 (Error Interno del Servidor) registrando el error y renderizando 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 5: Crear la 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 AI 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 */
            margin-bottom: 1.5rem;
            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 */
            text-align: left;
        }
        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 */
            min-height: 100px;
        }
        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="result-container">
                <h3>📝 Transcript:</h3>
                <textarea readonly>{{ transcript }}</textarea>
            </div>
        {% endif %}

        {% if prompt_summary %}
            <div class="result-container">
                <h3>🎯 Scene Prompt:</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 &lt;head&gt; define el título, enlaza una hoja de estilo CSS y establece el viewport para la capacidad de respuesta.
    • El &lt;body&gt; contiene el contenido visible, incluyendo un formulario para cargar audio y secciones para mostrar la transcripción y la imagen generada.
  • Estilizado CSS:
    • Diseño moderno y adaptable.
    • Elementos de formulario con estilo.
    • Presentación clara de resultados (transcripción e imagen).
    • Visualización de mensajes de error amigable para el usuario.
  • Formulario:
    • Se utiliza un &lt;form&gt; con enctype="multipart/form-data" para manejar la carga de archivos.
    • Un &lt;label&gt; y &lt;input type="file"&gt; permiten al usuario seleccionar un archivo de audio. El atributo accept="audio/*" restringe al usuario a cargar solo archivos de audio.
    • Un botón &lt;input type="submit"&gt; permite al usuario enviar el formulario.
  • Visualización de Transcripción e Imagen:
    • La plantilla utiliza el sistema de plantillas Jinja2 para mostrar condicionalmente el texto de la transcripción y la imagen generada si están disponibles. La transcripción se muestra en un textarea, y la imagen se muestra usando una etiqueta &lt;img&gt;.
  • Manejo de Errores:
    • Se utiliza un &lt;div class="error-message"&gt; para mostrar mensajes de error al usuario.

En esta sección, has adquirido conocimientos valiosos sobre técnicas avanzadas de integración de IA. Analicemos lo que has aprendido:

  • Organizar la lógica multimodal en módulos reutilizables
    • Crear estructuras de código limpias y mantenibles
    • Desarrollar componentes modulares que pueden actualizarse y reutilizarse fácilmente
    • Implementar manejo de errores y registro adecuados
  • Encadenar audio ➝ texto ➝ prompt ➝ imagen de manera eficiente
    • Procesar entradas de audio usando Whisper para una transcripción precisa
    • Transformar texto transcrito en prompts significativos con GPT
    • Generar imágenes relevantes usando DALL·E basadas en el texto procesado
  • Construir una aplicación Flask profesional que utiliza los tres modelos principales de OpenAI en un solo flujo
    • Configurar el enrutamiento y manejo de solicitudes adecuado
    • Gestionar las interacciones con la API de manera eficiente
    • Crear una interfaz de usuario intuitiva para una interacción fluida

Ahora comprendes el poder de encadenar modelos para crear experiencias sofisticadas de IA. Este conocimiento abre innumerables posibilidades para la innovación. Ya sea que estés construyendo una herramienta de diario con IA que convierte notas de voz en entradas ilustradas, una aplicación de diseño controlada por voz que transforma descripciones habladas en arte visual, o un asistente de contenido multimodal que ayuda a crear contenido multimedia rico, este flujo de trabajo fundamental puede llevarte lejos. Las habilidades que has aprendido aquí forman la base para crear aplicaciones de IA complejas y fáciles de usar que combinan múltiples modalidades de manera efectiva.