Cambiar tema

Guía de desarrollo de aplicaciones de IA multimodal: de la selección de modelos al despliegue en producción

Easton editorial illustration: multi-tenant AI service platform

Quizá uses GPT-4 o Claude para escribir código o pulir textos, pero cuando el requisito pasa a ser “analizar los datos de esta captura” o “entender el vídeo que subió el usuario”, los modelos solo de texto se quedan cortos. La IA multimodal resuelve eso: el modelo entiende texto y también “ve” imágenes y vídeo.

En el último año, la IA multimodal avanzó más rápido de lo esperado. GPT-4o, Claude Vision y Gemini 1.5 Pro ampliaron el límite de lo posible. Para desarrolladores, la pregunta real no es “qué tan potente es”, sino “cómo usarla, qué modelo elegir y cómo controlar el costo”. Este artículo desglosa eso desde la práctica.


1. Conceptos clave de la IA multimodal

1.1 ¿Qué es la IA multimodal?

En pocas palabras, la IA multimodal procesa varios tipos de datos a la vez. Los modelos de solo texto consumen texto; los multimodales consumen texto, imágenes, audio y vídeo y devuelven el resultado que necesitas.

Ejemplo: subes una foto de producto y preguntas “¿Dónde está la etiqueta de precio? ¿Cuánto cuesta?”. El modelo entiende la imagen, localiza la etiqueta, lee el número y responde. Antes hacían falta detección de objetos, OCR y comprensión de texto; ahora basta una llamada multimodal.

1.2 Evolución de arquitectura: de “piezas sueltas” a fusión nativa

Las primeras soluciones eran tipo “lego”: encoders visuales (CLIP, ViT) convertían la imagen en vectores y se los pasaban al LLM. GPT-4V sigue esa línea, con un adaptador visual sobre GPT-4.

El problema: la visión “añadida” se siente desconectada. Al interpretar imágenes, el modelo en la práctica “adivina” con lógica de lenguaje y falla en razonamiento visual profundo.

Los modelos multimodales nativos lo corrigen. GPT-4o y Gemini nacieron multimodales: texto, imagen y audio se procesan de forma unificada en la base. La diferencia se nota en tareas como comparar dos imágenes o sacar conclusiones de un gráfico.

1.3 Tendencias tecnológicas 2025-2026

2025 se llama el “año del Agent”: la multimodalidad pasó de extra a imprescindible. Tendencias claras:

Contexto largo. Gemini 1.5 Pro admite más de 1M tokens y puede procesar más de una hora de vídeo de una vez. Antes había que analizar fotograma a fotograma; ahora el modelo puede “verlo” entero antes de responder.

Costos a la baja. Los modelos open source avanzan rápido. Qwen2-VL y GLM-4V se acercan al cerrado en varias tareas. En escenarios sensibles al costo, el despliegue privado ya es viable.

Agents multimodales. Ya no solo “describen” imágenes: actúan según lo que ven. Por ejemplo, “en esta captura, haz clic en Iniciar sesión” exige visión + herramientas + planificación.


2. Comparativa y selección de modelos multimodales

Al elegir modelo, no mires solo benchmarks. En desarrollo real importan estabilidad de la API, costo, facilidad de integración y cumplimiento normativo.

2.1 OpenAI: GPT-4V y GPT-4o

GPT-4V fue la primera solución multimodal de OpenAI: un adaptador visual le dio “ojos” a GPT-4. GPT-4o es la versión nativa posterior, con capacidades globales superiores.

¿Cuándo elegir GPT-4o?

  • Razonamiento visual (sacar conclusiones de imágenes, comparar diferencias)
  • Conversaciones multimodales de varias vueltas (la imagen sigue en contexto)
  • Máxima precisión

¿Cuándo elegir GPT-4V?

  • Descripción o clasificación simple de imágenes
  • Aplicaciones sensibles a la latencia (a veces GPT-4V responde más rápido)
  • Compatibilidad con sistemas antiguos

En llamadas API, ambos modelos son esencialmente iguales:

from openai import OpenAI

client = OpenAI()

# Método 1: URL de imagen
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "¿Qué hay en esta imagen?"},
            {"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}}
        ]
    }]
)

# Método 2: codificación Base64
import base64

with open("image.png", "rb") as f:
    image_data = base64.b64encode(f.read()).decode("utf-8")

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Analiza esta imagen"},
            {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_data}"}}
        ]
    }]
)

print(response.choices[0].message.content)

2.2 Anthropic: Claude Vision

Claude Vision destaca en análisis de documentos y extracción de detalle. Para sacar datos estructurados de PDF, gráficos o capturas, Claude es una buena opción.

Escenarios fuertes de Claude Vision:

  • Análisis de documentos (PDF, escaneos, tablas complejas)
  • Extracción de detalle (más “meticuloso” que otros modelos)
  • Documentos largos (contexto 200K)

La llamada difiere un poco: Claude trata las imágenes como bloques de contenido independientes:

from anthropic import Anthropic
import base64

client = Anthropic()

# Leer imagen y convertir a Base64
with open("document.png", "rb") as f:
    image_data = base64.b64encode(f.read()).decode("utf-8")

response = client.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=1024,
    messages=[{
        "role": "user",
        "content": [
            {
                "type": "image",
                "source": {
                    "type": "base64",
                    "media_type": "image/png",
                    "data": image_data
                }
            },
            {"type": "text", "text": "Extrae todos los datos de tablas del documento y devuélvelos en JSON"}
        ]
    }]
)

print(response.content[0].text)

2.3 Google: serie Gemini

La baza de Gemini es el contexto largo. Gemini 1.5 Pro admite más de 1M tokens: vídeos muy largos y análisis multi-documento. Si hay mucho contenido visual, vale la pena probarlo.

Escenarios aplicables:

  • Análisis de vídeo largo (más de 10 minutos)
  • Procesamiento por lotes de varios documentos
  • Tareas que relacionan varios contenidos visuales

2.4 Open source: Qwen2-VL, GLM-4V

En escenarios sensibles al costo, a los datos o con despliegue privado, el open source es una opción realista.

Qwen2-VL: open source de Alibaba, optimizado para chino, imágenes hasta 4K. Estable en empresa; costo de llamada ~1/10 del cerrado.

GLM-4V: open source de Zhipu, adecuado para cumplimiento en China; arquitectura MoE reduce costo de inferencia.

2.5 Matriz de decisión

¿Cómo elegir? Según el caso:

EscenarioModelo recomendadoMotivo
Prototipo rápido, MVPGPT-4oAPI madura, documentación amplia, fácil depuración
Análisis de documentos, extracciónClaude VisionDetalle fino, tablas precisas
Vídeo largoGemini 1.5 ProContexto muy largo, razonamiento multimodal
Costo bajo, alta concurrenciaQwen2-VLOpen source controlable, bajo costo
Datos sensibles, despliegue privadoGLM-4VLocal, datos en tu perímetro
Escenarios en chino, presupuesto ajustadoQwen2-VLOptimizado para chino, buena relación costo-beneficio

3. Comprensión y procesamiento de imágenes en la práctica

3.1 Fundamentos de llamadas API

El núcleo de las API multimodales es el formato de mensaje correcto. OpenAI o Anthropic: imágenes y texto como partes distintas del mensaje.

Cuida el tamaño de imagen: los tokens dependen de píxeles. GPT-4o escala automáticamente, pero para controlar el costo conviene preprocesar antes de subir.

3.2 Descripción y preguntas sobre imágenes

El caso más básico: describir la imagen o responder preguntas. Aquí un envoltorio completo de Q&A:

from openai import OpenAI
import base64
from pathlib import Path

class ImageAnalyzer:
    def __init__(self, model="gpt-4o"):
        self.client = OpenAI()
        self.model = model

    def analyze(self, image_path: str, question: str) -> str:
        """Analizar imagen y responder pregunta"""
        # Leer imagen
        with open(image_path, "rb") as f:
            image_data = base64.b64encode(f.read()).decode("utf-8")

        # Determinar tipo de imagen
        suffix = Path(image_path).suffix.lower()
        media_type = {
            ".jpg": "image/jpeg",
            ".jpeg": "image/jpeg",
            ".png": "image/png",
            ".gif": "image/gif",
            ".webp": "image/webp"
        }.get(suffix, "image/jpeg")

        # Construir solicitud
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[{
                "role": "user",
                "content": [
                    {"type": "text", "text": question},
                    {"type": "image_url", "image_url": {
                        "url": f"data:{media_type};base64,{image_data}"
                    }}
                ]
            }],
            max_tokens=1000
        )

        return response.choices[0].message.content

# Ejemplo de uso
analyzer = ImageAnalyzer()
result = analyzer.analyze("product.jpg", "¿Cuál es la marca de este producto? ¿Cuál es el precio?")
print(result)

3.3 Análisis de documentos (PDF/gráficos)

Con PDF, convierte cada página en imagen y analiza página a página. Parser práctico:

import fitz  # PyMuPDF
from PIL import Image
import io
import base64
from openai import OpenAI

def pdf_to_images(pdf_path: str, dpi: int = 150) -> list:
    """Convertir PDF en lista de imágenes"""
    doc = fitz.open(pdf_path)
    images = []

    for page_num in range(len(doc)):
        page = doc[page_num]
        # Renderizar página como imagen
        mat = fitz.Matrix(dpi / 72, dpi / 72)
        pix = page.get_pixmap(matrix=mat)

        # Convertir a PIL Image
        img_data = pix.tobytes("png")
        img = Image.open(io.BytesIO(img_data))
        images.append(img)

    doc.close()
    return images

def extract_table_from_page(image: Image.Image, client: OpenAI) -> dict:
    """Extraer tablas de una página"""
    # Convertir a base64
    buffer = io.BytesIO()
    image.save(buffer, format="PNG")
    image_data = base64.b64encode(buffer.getvalue()).decode("utf-8")

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "user",
            "content": [
                {"type": "text", "text": """
                Extrae los datos de tablas de la imagen y devuélvelos en JSON.
                Si hay varias tablas, usa un array.
                Ejemplo: {"tables": [{"headers": [...], "rows": [...]}]}
                """},
                {"type": "image_url", "image_url": {
                    "url": f"data:image/png;base64,{image_data}"
                }}
            ]
        }],
        response_format={"type": "json_object"}
    )

    import json
    return json.loads(response.choices[0].message.content)

# Flujo completo
images = pdf_to_images("report.pdf")
for i, img in enumerate(images):
    print(f"Procesando página {i+1}...")
    tables = extract_table_from_page(img, OpenAI())
    print(f"Se extrajeron {len(tables.get('tables', []))} tablas")

3.4 Procesamiento por lotes de imágenes

Con muchas imágenes, el control de concurrencia es crítico. Las API tienen límites; concurrencia a ciegas te limita:

import asyncio
from openai import AsyncOpenAI
import aiofiles
import base64

class BatchImageProcessor:
    def __init__(self, model="gpt-4o", max_concurrent=5):
        self.client = AsyncOpenAI()
        self.model = model
        self.semaphore = asyncio.Semaphore(max_concurrent)

    async def process_single(self, image_path: str, prompt: str) -> dict:
        """Procesar una imagen"""
        async with self.semaphore:
            try:
                async with aiofiles.open(image_path, "rb") as f:
                    image_bytes = await f.read()
                image_data = base64.b64encode(image_bytes).decode("utf-8")

                response = await self.client.chat.completions.create(
                    model=self.model,
                    messages=[{
                        "role": "user",
                        "content": [
                            {"type": "text", "text": prompt},
                            {"type": "image_url", "image_url": {
                                "url": f"data:image/jpeg;base64,{image_data}"
                            }}
                        ]
                    }]
                )

                return {
                    "path": image_path,
                    "result": response.choices[0].message.content,
                    "success": True
                }
            except Exception as e:
                return {
                    "path": image_path,
                    "error": str(e),
                    "success": False
                }

    async def process_batch(self, image_paths: list, prompt: str) -> list:
        """Procesar imágenes en lote"""
        tasks = [self.process_single(p, prompt) for p in image_paths]
        return await asyncio.gather(*tasks)

# Ejemplo de uso
async def main():
    processor = BatchImageProcessor(max_concurrent=3)
    results = await processor.process_batch(
        ["img1.jpg", "img2.jpg", "img3.jpg"],
        "Describe esta imagen en no más de 50 palabras"
    )
    for r in results:
        print(f"{r['path']}: {r.get('result', r.get('error'))}")

asyncio.run(main())

4. Comprensión de vídeo en la práctica

El núcleo del vídeo es “reducir dimensiones”: fotogramas clave discretos y análisis por lotes. El reto es equilibrar información y costo.

4.1 Extracción y procesamiento de fotogramas

import cv2
import base64
from pathlib import Path

class VideoProcessor:
    def __init__(self, video_path: str):
        self.video_path = video_path
        self.cap = cv2.VideoCapture(video_path)
        self.fps = self.cap.get(cv2.CAP_PROP_FPS)
        self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
        self.duration = self.total_frames / self.fps

    def extract_frames(self, strategy="interval", **kwargs):
        """Extraer fotogramas de vídeo

        Args:
            strategy: Estrategia de extracción
                - interval: un fotograma cada N segundos
                - scene: en cambios de escena
                - uniform: N fotogramas uniformes
        """
        frames = []

        if strategy == "interval":
            interval_sec = kwargs.get("interval", 1.0)
            interval_frames = int(interval_sec * self.fps)

            frame_idx = 0
            while self.cap.isOpened():
                ret, frame = self.cap.read()
                if not ret:
                    break
                if frame_idx % interval_frames == 0:
                    frames.append((frame_idx / self.fps, frame))
                frame_idx += 1

        elif strategy == "uniform":
            num_frames = kwargs.get("num_frames", 10)
            interval = max(1, self.total_frames // num_frames)

            for i in range(num_frames):
                self.cap.set(cv2.CAP_PROP_POS_FRAMES, i * interval)
                ret, frame = self.cap.read()
                if ret:
                    frames.append((i * interval / self.fps, frame))

        self.cap.release()
        return frames

    def frame_to_base64(self, frame) -> str:
        """Convertir fotograma a base64"""
        _, buffer = cv2.imencode('.jpg', frame)
        return base64.b64encode(buffer).decode('utf-8')

# Ejemplo de uso
processor = VideoProcessor("demo.mp4")
print(f"Duración del vídeo: {processor.duration:.1f} segundos")

# Un fotograma cada 2 segundos
frames = processor.extract_frames(strategy="interval", interval=2.0)
print(f"Se extrajeron {len(frames)} fotogramas")

4.2 Estrategias para vídeos largos

En vídeos largos el costo sube rápido. Estrategias prácticas:

Procesamiento jerárquico: revisión rápida a baja resolución y fps para localizar segmentos clave; luego análisis fino.

Detección de escenas: solo fotogramas con cambio de plano; OpenCV ayuda.

Resumen primero: resumir cada segmento y luego sintetizar.

4.3 Caso práctico: resumen de vídeo

from openai import OpenAI

def generate_video_summary(frames: list, client: OpenAI) -> str:
    """Generar resumen de vídeo desde fotogramas clave"""
    # Lotes de máximo 5 fotogramas
    batch_size = 5
    segment_summaries = []

    for i in range(0, len(frames), batch_size):
        batch = frames[i:i+batch_size]

        # Construir contenido del mensaje
        content = [{"type": "text", "text": "Describe qué ocurre en estos fotogramas, de forma breve y clara"}]
        for timestamp, frame in batch:
            frame_base64 = VideoProcessor("").frame_to_base64(frame)
            content.append({
                "type": "image_url",
                "image_url": {"url": f"data:image/jpeg;base64,{frame_base64}"}
            })

        # Llamar API
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": content}]
        )

        segment_summaries.append(response.choices[0].message.content)

    # Sintetizar resúmenes de segmentos
    final_prompt = f"""
    Aquí están los resúmenes de cada segmento del vídeo:
    {chr(10).join(f'{i+1}. {s}' for i, s in enumerate(segment_summaries))}

    Sintetiza la información anterior y genera un resumen completo del vídeo, incluyendo:
    1. Contenido principal
    2. Eventos o información clave
    3. Tema general
    """

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": final_prompt}]
    )

    return response.choices[0].message.content

5. Optimización de costos y rendimiento

El costo multimodal viene sobre todo de tokens visuales. Una imagen 1024×1024 ~765 tokens; mal gestionado, una solicitud puede costar mucho.

5.1 Cálculo de tokens visuales

Reglas de tokens de GPT-4o:

Tamaño imagenModo baja resoluciónModo alta resolución
512×51285 tokens255 tokens
1024×1024170 tokens765 tokens
2048×2048255 tokens2550 tokens

Baja resolución: tipo de imagen o descripción general. Alta resolución: leer texto o ver detalle.

5.2 Compresión y preprocesado

Preprocesar antes de subir controla costos:

from PIL import Image
from pathlib import Path

def optimize_image(image_path: str, max_size: int = 1024, quality: int = 85) -> str:
    """Optimizar tamaño y calidad de imagen"""
    img = Image.open(image_path)

    # Redimensionar
    if max(img.size) > max_size:
        ratio = max_size / max(img.size)
        new_size = (int(img.size[0] * ratio), int(img.size[1] * ratio))
        img = img.resize(new_size, Image.Resampling.LANCZOS)

    # Recortar zona clave (si se conoce la posición)
    # img = img.crop((left, top, right, bottom))

    # Guardar imagen optimizada
    optimized_path = f"optimized_{Path(image_path).name}"
    img.save(optimized_path, "JPEG", quality=quality)

    return optimized_path

# Ejemplo de uso
optimized = optimize_image("screenshot.png", max_size=1024)
# Original ~2 MB, optimizada ~200 KB

5.3 Caché y procesamiento por lotes

Caché de resultados: misma imagen, mismo resultado; usa hash como clave:

import hashlib

def get_image_hash(image_path: str) -> str:
    """Calcular hash de imagen"""
    with open(image_path, "rb") as f:
        return hashlib.md5(f.read()).hexdigest()

# Lógica de caché
cache = {}
image_hash = get_image_hash("product.jpg")

if image_hash in cache:
    result = cache[image_hash]
else:
    result = analyzer.analyze("product.jpg", "Describe este producto")
    cache[image_hash] = result

Fusión en lote: varias imágenes relacionadas en una sola solicitud:

# No recomendado: varias solicitudes
for img in images:
    result = analyze_image(img, "Describe la imagen")

# Recomendado: una solicitud
all_images_content = [{"type": "text", "text": "Describe estas imágenes"}]
for img in images:
    all_images_content.append({
        "type": "image_url",
        "image_url": {"url": img_url}
    })

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": all_images_content}]
)

5.4 Estrategia híbrida de modelos

No todo requiere el modelo más fuerte. Puedes escalonar:

def smart_analyze(image_path: str, task_type: str):
    """Elegir modelo según tipo de tarea"""
    if task_type in ["classify", "detect"]:
        # Clasificación/detection: modelo pequeño
        model = "gpt-4o-mini"
    elif task_type in ["ocr", "extract"]:
        # OCR/extracción: modelo medio
        model = "gpt-4o"
    else:
        # Razonamiento complejo: modelo fuerte
        model = "gpt-4o"

    # ... lógica de llamada

6. Mejores prácticas de despliegue en producción

Del demo a producción hay mucha ingeniería por delante.

6.1 Errores y reintentos

Las API fallan: timeout, límite de tasa, error de servidor. Hace falta manejo robusto:

import time
from openai import APIError, RateLimitError, APIConnectionError

def robust_api_call(func, max_retries=3, backoff_factor=2):
    """Llamada API con reintentos"""
    for attempt in range(max_retries):
        try:
            return func()
        except RateLimitError:
            if attempt < max_retries - 1:
                wait_time = backoff_factor ** attempt
                print(f"Límite de tasa, esperando {wait_time} s antes de reintentar...")
                time.sleep(wait_time)
            else:
                raise
        except APIConnectionError as e:
            print(f"Error de conexión: {e}")
            if attempt < max_retries - 1:
                time.sleep(1)
            else:
                raise
        except APIError as e:
            print(f"Error de API: {e}")
            raise

6.2 Concurrencia y limitación de tasa

Los límites multimodales suelen ser más estrictos que en texto. Implementa token bucket:

import asyncio
import time

class RateLimiter:
    def __init__(self, requests_per_minute: int):
        self.interval = 60.0 / requests_per_minute
        self.last_request = 0
        self.lock = asyncio.Lock()

    async def acquire(self):
        async with self.lock:
            now = time.time()
            wait_time = self.last_request + self.interval - now
            if wait_time > 0:
                await asyncio.sleep(wait_time)
            self.last_request = time.time()

# Ejemplo de uso
limiter = RateLimiter(requests_per_minute=100)

async def process_with_limit(image_path):
    await limiter.acquire()
    return await async_analyze(image_path)

6.3 Monitorización y logs

Registra datos clave por llamada para depurar:

import logging
from datetime import datetime

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

def log_api_call(model: str, input_tokens: int, output_tokens: int, latency: float):
    logger.info(
        f"Llamada API - Modelo: {model}, "
        f"Tokens entrada: {input_tokens}, salida: {output_tokens}, "
        f"Latencia: {latency:.2f}s"
    )

# Registrar tras la llamada
start_time = time.time()
response = client.chat.completions.create(...)
latency = time.time() - start_time

log_api_call(
    model="gpt-4o",
    input_tokens=response.usage.prompt_tokens,
    output_tokens=response.usage.completion_tokens,
    latency=latency
)

6.4 Ejemplo de código completo

Integra lo anterior en una clase utilitaria lista para usar:

from openai import OpenAI
from pathlib import Path
import base64
import logging
import time
from typing import Optional, List, Dict

logger = logging.getLogger(__name__)

class MultimodalAnalyzer:
    """Clase utilitaria de análisis multimodal"""

    def __init__(
        self,
        model: str = "gpt-4o",
        max_retries: int = 3,
        requests_per_minute: int = 100
    ):
        self.client = OpenAI()
        self.model = model
        self.max_retries = max_retries
        self.min_interval = 60.0 / requests_per_minute
        self.last_request_time = 0

    def _wait_for_rate_limit(self):
        """Limitación de tasa"""
        now = time.time()
        wait_time = self.last_request_time + self.min_interval - now
        if wait_time > 0:
            time.sleep(wait_time)
        self.last_request_time = time.time()

    def _read_image(self, image_path: str) -> str:
        """Read image and convert to base64"""
        with open(image_path, "rb") as f:
            return base64.b64encode(f.read()).decode("utf-8")

    def _call_with_retry(self, messages: list) -> dict:
        """API call with retry"""
        for attempt in range(self.max_retries):
            try:
                self._wait_for_rate_limit()
                start_time = time.time()

                response = self.client.chat.completions.create(
                    model=self.model,
                    messages=messages,
                    max_tokens=1000
                )

                latency = time.time() - start_time
                logger.info(
                    f"Llamada API exitosa - tokens: {response.usage.total_tokens}, "
                    f"latencia: {latency:.2f}s"
                )

                return {
                    "content": response.choices[0].message.content,
                    "tokens": {
                        "prompt": response.usage.prompt_tokens,
                        "completion": response.usage.completion_tokens
                    }
                }

            except Exception as e:
                logger.error(f"Llamada API fallida (intento {attempt + 1}/{self.max_retries}): {e}")
                if attempt == self.max_retries - 1:
                    raise
                time.sleep(2 ** attempt)

    def analyze_image(
        self,
        image_path: str,
        prompt: str,
        detail: str = "auto"
    ) -> dict:
        """Analizar una imagen"""
        image_data = self._read_image(image_path)

        messages = [{
            "role": "user",
            "content": [
                {"type": "text", "text": prompt},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{image_data}",
                        "detail": detail
                    }
                }
            ]
        }]

        return self._call_with_retry(messages)

    def analyze_multiple_images(
        self,
        image_paths: List[str],
        prompt: str
    ) -> dict:
        """Analizar varias imágenes"""
        content = [{"type": "text", "text": prompt}]

        for path in image_paths:
            image_data = self._read_image(path)
            content.append({
                "type": "image_url",
                "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}
            })

        return self._call_with_retry([{"role": "user", "content": content}])

    def extract_text_from_image(self, image_path: str) -> str:
        """Extraer texto de imagen (OCR)"""
        result = self.analyze_image(
            image_path,
            "Extrae todo el texto de la imagen en el formato original"
        )
        return result["content"]

    def describe_image(self, image_path: str) -> str:
        """Generar descripción de imagen"""
        result = self.analyze_image(
            image_path,
            "Describe el contenido de esta imagen en un párrafo"
        )
        return result["content"]


# Ejemplo de uso
if __name__ == "__main__":
    analyzer = MultimodalAnalyzer()

    # Analizar una imagen
    result = analyzer.analyze_image(
        "product.jpg",
        "¿Cuál es la marca de este producto? ¿Cuál es el precio?"
    )
    print(result["content"])

    # OCR: extraer texto
    text = analyzer.extract_text_from_image("document.png")
    print(text)

Conclusión

La IA multimodal pasa de “juguete novedoso” a “herramienta útil”. Al elegir modelo, mira el escenario: Gemini para vídeo largo, Claude para documentos, GPT-4o para prototipos, open source si el costo aprieta.

En desarrollo, controla el costo: preprocesar, elegir resolución y cachear. En producción: reintentos, límites de tasa y logs.

Los límites siguen ampliándose. En 2025: más agents multimodales, contextos más largos y open source más maduro. Dominar estas bases te permite adaptarte rápido.


Referencias

FAQ

¿Debo elegir GPT-4o o GPT-4V?
Elige GPT-4o si necesitas razonamiento visual y conversaciones multimodales de varias vueltas; GPT-4V para descripciones simples, baja latencia o compatibilidad con sistemas antiguos.
¿Cómo controlar el costo de las API multimodales?
Tres estrategias clave:

• Preprocesar imágenes: comprimir tamaño y reducir resolución antes de subir
• Elegir resolución con criterio: modo baja resolución cuando no hagan falta detalles
• Implementar caché: guardar resultados de consultas sobre la misma imagen
¿Consejos para procesar vídeos largos?
Procesamiento jerárquico: primero revisar con baja tasa de fotogramas para localizar segmentos clave; detección de escenas: solo procesar cambios de plano; resumen primero: resumir segmentos antes de sintetizar. Gemini 1.5 Pro admite contexto muy largo para vídeos extensos.
¿Se pueden usar modelos multimodales open source?
Sí. Qwen2-VL está optimizado para chino; GLM-4V es adecuado para cumplimiento en China. En escenarios sensibles al costo o con despliegue privado, el open source es viable, con costo de llamada ~1/10 del cerrado.
¿Qué preparar para un entorno de producción?
Tres pilares: reintentos (timeouts de red, límites de tasa), control de concurrencia (token bucket para no ser bloqueado) y logs de monitorización (tokens y latencia por llamada).

15 min de lectura · Publicado el: 24 mar 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog