Módulo 2: Vision + LLMs

7. Multi-Image y Context

Descripción

Hasta ahora enviaste una imagen por request. Pero muchos casos de uso reales requieren que el modelo vea múltiples imágenes simultáneamente: comparar un diseño antes y después, analizar las 12 páginas de un contrato, catalogar 20 productos de un inventario, o detectar cambios entre fotos tomadas en diferentes fechas.

Enviar múltiples imágenes no es simplemente repetir N veces un request de una imagen. Cambia la estructura del request, la forma de referenciar cada imagen en el prompt, el consumo de tokens, y las estrategias cuando el conjunto excede el context window. Cada proveedor tiene su propio formato.

En esta cápsula vas a dominar los tres formatos, construir cuatro casos de uso completos, y aprender a gestionar el context window cuando las imágenes se acumulan. Los patrones de la cápsula 06 (descripción, OCR, comparación) se aplican aquí sobre conjuntos de imágenes, y las funciones Base64 de la cápsula 06 del Módulo 1 son prerrequisito.


Multi-Image con OpenAI

Estructura del content array

Para enviar múltiples imágenes con OpenAI, agregas varios objetos image_url al array content del mensaje. El modelo procesa todas las imágenes en orden y puede referenciarlas por posición.

import base64
from pathlib import Path
from openai import OpenAI

client = OpenAI()

MIME_TYPES = {
    ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
    ".png": "image/png", ".gif": "image/gif", ".webp": "image/webp",
}


def encode_image(path: str) -> str:
    p = Path(path)
    mime = MIME_TYPES[p.suffix.lower()]
    with open(p, "rb") as f:
        b64 = base64.b64encode(f.read()).decode("utf-8")
    return f"data:{mime};base64,{b64}"


def analyze_multiple_openai(
    image_paths: list[str],
    prompt: str,
    detail: str = "auto",
    model: str = "gpt-4o",
) -> str:
    content = [{"type": "text", "text": prompt}]

    for path in image_paths:
        content.append({
            "type": "image_url",
            "image_url": {
                "url": encode_image(path),
                "detail": detail,
            },
        })

    response = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": content}],
        max_tokens=2048,
    )
    return response.choices[0].message.content

Referenciar imágenes en el prompt

El modelo recibe las imágenes en el orden en que aparecen en el array. Referéncialas explícitamente por posición:

prompt = """Tienes 3 imágenes:
- Imagen 1: Diagrama de arquitectura
- Imagen 2: Captura de código
- Imagen 3: Screenshot del resultado

Describe la relación entre las tres. ¿El código implementa el diagrama?
¿El resultado es consistente con lo esperado?"""

result = analyze_multiple_openai(
    ["diagrama.png", "codigo.png", "resultado.png"],
    prompt,
    detail="high",
)

El parámetro detail por imagen

Puedes asignar diferente detail a cada imagen dentro del mismo request. Construye el array content manualmente en lugar de usar la función helper, y asigna "high" a imágenes que necesitan análisis fino (documentos, texto) y "low" a imágenes de contexto (logos, fotos de referencia). Esto optimiza el consumo de tokens sin sacrificar calidad donde importa.

Límites de OpenAI

  • Máximo práctico: ~10 imágenes por request (depende de resolución y detail)
  • Cada imagen en high consume 85 + 170×tiles tokens
  • El total de tokens (texto + imágenes) no puede exceder el context window de 128K tokens
  • En low detail, cada imagen consume solo 85 tokens — puedes enviar más imágenes

Multi-Image con Anthropic

Anthropic estructura multi-image con bloques de tipo image en el array content. Cada imagen va como Base64 con su media_type separado — no acepta URLs.

import anthropic

client_anthropic = anthropic.Anthropic()


def analyze_multiple_anthropic(
    image_paths: list[str],
    prompt: str,
    model: str = "claude-sonnet-4-20250514",
) -> str:
    content = []

    for i, path in enumerate(image_paths, 1):
        p = Path(path)
        mime = MIME_TYPES[p.suffix.lower()]
        with open(p, "rb") as f:
            b64 = base64.b64encode(f.read()).decode("utf-8")

        content.append({
            "type": "image",
            "source": {
                "type": "base64",
                "media_type": mime,
                "data": b64,
            },
        })

    content.append({"type": "text", "text": prompt})

    response = client_anthropic.messages.create(
        model=model,
        max_tokens=2048,
        messages=[{"role": "user", "content": content}],
    )
    return response.content[0].text


result = analyze_multiple_anthropic(
    ["foto_antes.jpg", "foto_despues.jpg"],
    "Compara la Imagen 1 y la Imagen 2. Lista similitudes y diferencias.",
)

Orden de los bloques

En Anthropic, los bloques image aparecen antes del bloque text en el array content. El modelo los procesa en orden secuencial: Imagen 1 es el primer bloque image, Imagen 2 el segundo, etc. Puedes referenciarlas por posición en el prompt igual que con OpenAI.

Límites de Anthropic

  • Cada imagen: máximo 5 MB (en Base64, que infla ~33%)
  • Archivo original no debe exceder ~3.75 MB para no superar el límite tras codificación
  • Context window: 200K tokens (Claude Sonnet)
  • Sin límite explícito de número de imágenes, pero el context window es el tope real

Multi-Image con Gemini

Gemini tiene la estructura más simple para multi-image: una lista plana donde mezclas texto e imágenes como elementos secuenciales.

import google.generativeai as genai
from PIL import Image

genai.configure()
gemini_model = genai.GenerativeModel("gemini-2.0-flash")


def analyze_multiple_gemini(
    image_paths: list[str],
    prompt: str,
) -> str:
    contents = [prompt]

    for path in image_paths:
        contents.append(Image.open(path))

    response = gemini_model.generate_content(contents)
    return response.text


result = analyze_multiple_gemini(
    ["dashboard_enero.png", "dashboard_febrero.png", "dashboard_marzo.png"],
    "Analiza la evolución de los 3 dashboards mensuales. "
    "Imagen 1 = enero, Imagen 2 = febrero, Imagen 3 = marzo. "
    "¿Qué métricas mejoraron y cuáles empeoraron?",
)

Ventaja: context window extendido

La ventaja principal de Gemini para multi-image es su context window masivo: 1M tokens en gemini-1.5-pro, hasta 2M en gemini-2.0-flash. Donde OpenAI limita a ~10 imágenes en high, Gemini puede recibir decenas de imágenes en un solo request. Para catálogos grandes, simplemente itera sobre un directorio y agrega cada imagen a la lista contents.

Límites de Gemini

  • Context window: hasta 2M tokens (modelo dependiente)
  • Imagen individual: máximo 20 MB
  • Formatos: PNG, JPEG, GIF, WebP, BMP
  • Acepta URLs HTTP, gs:// URIs, y objetos PIL directamente

Comparación de Formatos

AspectoOpenAIAnthropicGemini
EstructuraArray de image_url en contentArray de bloques image + text en contentLista plana [prompt, img1, img2, ...]
Formato de imagenData URI o URLBase64 + media_type separadosObjeto PIL, URL, o gs://
Orden texto/imágenesTexto primero, luego imágenesImágenes primero, texto al finalOrden libre
Parámetro de detalledetail: low, high, autoNo disponibleNo disponible
URLs directasNo (solo Base64)Sí (HTTP y gs://)
Context window128K tokens200K tokensHasta 2M tokens
Límite por imagen20 MB5 MB (Base64)20 MB
Imágenes prácticas/request~10 (high) / ~50+ (low)~20-30~50-100+

Pocas imágenes con alta precisión → OpenAI detail=high o Anthropic. Decenas de imágenes simultáneas → Gemini.


Caso de Uso 1: Comparación Antes/Después

Un patrón frecuente es enviar dos imágenes para generar un reporte de cambios. Aplica a: renovaciones, versiones de diseño, evolución de dashboards, control de calidad.

COMPARISON_PROMPT = """Tienes dos imágenes:
- Imagen 1: Estado ANTES
- Imagen 2: Estado DESPUÉS

Genera un reporte con: Similitudes, Diferencias, los 3 Cambios Principales,
y una Evaluación (dirección: mejora/deterioro/neutro, confianza: alta/media/baja,
resumen en una oración)."""


def compare_before_after(
    before_path: str,
    after_path: str,
    context: str = "",
    provider: str = "openai",
) -> str:
    prompt = COMPARISON_PROMPT
    if context:
        prompt += f"\n\nContexto adicional: {context}"

    if provider == "openai":
        return analyze_multiple_openai([before_path, after_path], prompt, detail="high")
    elif provider == "anthropic":
        return analyze_multiple_anthropic([before_path, after_path], prompt)
    else:
        return analyze_multiple_gemini([before_path, after_path], prompt)


report = compare_before_after(
    "oficina_2024.jpg",
    "oficina_2025.jpg",
    context="Remodelación de oficina corporativa",
    provider="openai",
)
print(report)

La clave es que el prompt asigne explícitamente roles ("Imagen 1 = antes", "Imagen 2 = después") para que el modelo no confunda el orden.


Caso de Uso 2: Documento Multi-Página

Convertir un PDF en imágenes y enviarlas como multi-image es una de las aplicaciones más prácticas. La decisión clave es todas las páginas a la vez vs. chunking por lotes.

Conversión de PDF a imágenes

from pdf2image import convert_from_path
import tempfile


def pdf_to_images(pdf_path: str, dpi: int = 200) -> list[str]:
    images = convert_from_path(pdf_path, dpi=dpi)
    paths = []

    with tempfile.TemporaryDirectory() as tmpdir:
        for i, img in enumerate(images):
            path = f"{tmpdir}/page_{i+1:03d}.jpg"
            img.save(path, "JPEG", quality=85)
            paths.append(path)

    return paths

Estrategia 1: Todas las páginas a la vez

Para documentos cortos (≤10 páginas), envía todo en un request:

def analyze_full_document(pdf_path: str, prompt: str) -> str:
    page_paths = pdf_to_images(pdf_path)

    numbered_prompt = f"Tienes un documento de {len(page_paths)} páginas.\n"
    for i in range(len(page_paths)):
        numbered_prompt += f"- Imagen {i+1}: Página {i+1}\n"
    numbered_prompt += f"\n{prompt}"

    return analyze_multiple_openai(page_paths, numbered_prompt, detail="high")


result = analyze_full_document(
    "contrato_5_paginas.pdf",
    "Extrae todas las cláusulas, montos, y fechas límite del contrato.",
)

Estrategia 2: Chunking por lotes

Para documentos largos, divide en chunks, analiza cada uno, y consolida:

def analyze_document_chunked(
    pdf_path: str,
    prompt: str,
    chunk_size: int = 5,
) -> str:
    page_paths = pdf_to_images(pdf_path)
    partial_results = []

    for start in range(0, len(page_paths), chunk_size):
        chunk = page_paths[start:start + chunk_size]
        first_page, last_page = start + 1, start + len(chunk)
        chunk_prompt = (
            f"Páginas {first_page}-{last_page} de {len(page_paths)}.\n\n{prompt}"
        )
        result = analyze_multiple_openai(chunk, chunk_prompt, detail="high")
        partial_results.append(f"### Páginas {first_page}-{last_page}\n{result}")

    consolidation = client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "user",
            "content": "Consolida estos análisis parciales en un resumen unificado. "
                "Elimina redundancias.\n\n" + "\n\n".join(partial_results),
        }],
        max_tokens=4096,
    )
    return consolidation.choices[0].message.content

Estimación de costo para multi-página

Una página típica de documento (1700×2200 a 200 DPI) consume ~1105 tokens en high detail. Para estimar el costo de un documento completo:

PáginasTokens de imagenCosto gpt-4oCosto gpt-4o-mini
5~5,525~$0.019~$0.001
20~22,100~$0.155~$0.009
50~55,250~$0.638~$0.038

El Ejercicio 3 al final de esta cápsula incluye una función completa para calcular estos costos con dimensiones reales.


Caso de Uso 3: Catálogo de Productos

Analizar un batch de imágenes de productos extrayendo datos estructurados de cada uno. La clave es que el prompt referencie cada imagen por número para que el modelo devuelva datos organizados.

import json

CATALOG_PROMPT = """Tienes {n} imágenes de productos.
{image_list}

Para CADA producto, extrae en JSON: image_number, product_name, category,
visible_price (null si no es visible), color, condition (nuevo/usado/no determinable),
description (una oración).

Responde ÚNICAMENTE con un array JSON válido."""


def analyze_product_catalog(
    image_paths: list[str],
    provider: str = "openai",
) -> list[dict]:
    image_list = "\n".join(
        f"- Imagen {i+1}: Producto {i+1}" for i in range(len(image_paths))
    )
    prompt = CATALOG_PROMPT.format(n=len(image_paths), image_list=image_list)

    if provider == "openai":
        raw = analyze_multiple_openai(image_paths, prompt, detail="high")
    elif provider == "anthropic":
        raw = analyze_multiple_anthropic(image_paths, prompt)
    else:
        raw = analyze_multiple_gemini(image_paths, prompt)

    cleaned = raw.strip()
    if cleaned.startswith("```"):
        cleaned = cleaned.split("\n", 1)[1].rsplit("```", 1)[0]

    return json.loads(cleaned)


products = analyze_product_catalog(
    ["zapato_1.jpg", "zapato_2.jpg", "zapato_3.jpg", "zapato_4.jpg"],
    provider="openai",
)

for p in products:
    print(f"Imagen {p['image_number']}: {p['product_name']} - {p['category']}")

Para catálogos grandes (>10 productos), procesa en lotes de 5-8 imágenes y concatena los arrays JSON.


Caso de Uso 4: Evolución Temporal

Enviar imágenes cronológicas permite detectar tendencias, progreso de construcción, evolución de métricas, o seguimiento médico.

TEMPORAL_PROMPT = """Tienes {n} imágenes ordenadas cronológicamente:
{timeline}

Analiza la evolución temporal:

1. **Tendencia general**: ¿Mejora, deterioro, estabilidad, o ciclo?
2. **Cambios entre cada par consecutivo**: Lista qué cambió entre imagen N e imagen N+1
3. **Punto de inflexión**: ¿Hay algún momento donde el patrón cambia?
4. **Predicción**: Basándote en la tendencia, ¿qué esperarías en la siguiente observación?"""


def analyze_temporal_evolution(
    image_paths: list[str],
    labels: list[str],
    provider: str = "openai",
) -> str:
    timeline = "\n".join(
        f"- Imagen {i+1}: {label}" for i, label in enumerate(labels)
    )
    prompt = TEMPORAL_PROMPT.format(n=len(image_paths), timeline=timeline)

    if provider == "openai":
        return analyze_multiple_openai(image_paths, prompt, detail="high")
    elif provider == "anthropic":
        return analyze_multiple_anthropic(image_paths, prompt)
    else:
        return analyze_multiple_gemini(image_paths, prompt)


report = analyze_temporal_evolution(
    ["dashboard_q1.png", "dashboard_q2.png", "dashboard_q3.png", "dashboard_q4.png"],
    ["Q1 2025", "Q2 2025", "Q3 2025", "Q4 2025"],
    provider="gemini",
)
print(report)

Gemini es ideal para evolución temporal: 12 meses con una imagen por mes cabe en un solo request, mientras que con OpenAI necesitarías chunking.


Gestión de Context Window

Cómo multi-image afecta los tokens

Cada imagen consume tokens según el proveedor y la resolución. El problema aparece cuando envías muchas imágenes y la suma total excede el context window.

import math

def estimate_multi_image_tokens(
    n_images: int, width: int = 1024, height: int = 1024, detail: str = "high",
) -> dict:
    if detail == "low":
        per_image = 85
    else:
        w, h = width, height
        if max(w, h) > 2048:
            scale = 2048 / max(w, h)
            w, h = int(w * scale), int(h * scale)
        if min(w, h) > 768:
            scale = 768 / min(w, h)
            w, h = int(w * scale), int(h * scale)
        per_image = 85 + 170 * (math.ceil(w / 512) * math.ceil(h / 512))

    total = per_image * n_images
    limits = {"openai": 128_000, "anthropic": 200_000, "gemini": 1_000_000}

    return {
        "tokens_per_image": per_image,
        "total_tokens": total,
        "fits_in": {p: total < lim for p, lim in limits.items()},
    }

for n in [5, 10, 20, 50, 100]:
    est = estimate_multi_image_tokens(n, detail="high")
    ok = [p for p, fits in est["fits_in"].items() if fits]
    print(f"{n:>3} imgs: {est['total_tokens']:>7,} tokens → {', '.join(ok)}")

Chunking cuando se excede el contexto

Cuando el total de tokens excede el context window, divide las imágenes en chunks. La función analyze_document_chunked del Caso de Uso 2 implementa este patrón: procesa lotes de N imágenes, recolecta resultados parciales, y consolida con una llamada de texto puro al final.

Estrategia de reducción de tokens

Antes de recurrir a chunking, reduce el consumo de tokens:

  1. Bajar detail a low: Reduce de ~765 tokens a 85 tokens por imagen (OpenAI)
  2. Redimensionar imágenes: Una imagen de 512×512 consume menos tiles que 4000×3000
  3. Usar gpt-4o-mini: Mismo context window, menor costo
  4. Filtrar imágenes irrelevantes: Envía solo las que necesita el análisis

Troubleshooting

Problema 1: Context window excedido

Síntoma: Error context_length_exceeded o max_tokens al enviar muchas imágenes.

Causa: La suma de tokens de todas las imágenes + prompt + output esperado excede el límite del modelo.

Solución: Usa estimate_multi_image_tokens() antes de enviar. Si excede, aplica chunking o reduce detail a low. Para volúmenes grandes, migra a Gemini (1-2M tokens de contexto).

Problema 2: El modelo confunde el orden de las imágenes

Síntoma: La respuesta atribuye contenido de la Imagen 2 a la Imagen 1, o mezcla información entre imágenes.

Causa: El prompt no asigna roles claros a cada imagen, o el prompt aparece después de muchas imágenes y el modelo pierde la referencia.

Solución: Siempre incluye una lista explícita al inicio del prompt: "Imagen 1 = X, Imagen 2 = Y". Con Anthropic, coloca las imágenes antes del texto para que el modelo las vea en contexto antes de leer las instrucciones.

Problema 3: Una imagen corrupta falla todo el request

Síntoma: Error de parsing o invalid_image al enviar un batch, aunque la mayoría de las imágenes son válidas.

Causa: Una sola imagen con formato incorrecto, tamaño excedido, o codificación Base64 inválida rompe el request completo.

Solución: Valida cada imagen antes de incluirla en el batch. Abre cada archivo con PIL.Image.open().verify(), verifica que el tamaño no exceda el límite del proveedor, y separa las imágenes válidas de las inválidas. Envía solo las válidas y reporta las que fallaron.

Problema 4: Costo inesperadamente alto

Síntoma: La factura es mucho mayor de lo esperado después de implementar multi-image.

Causa: Múltiples imágenes en detail=high × múltiples requests × modelo caro escalan rápido. 100 requests × 5 imágenes × gpt-4o high ≈ $1.50 solo en tokens de imagen.

Solución: Estima el costo antes de ejecutar un batch (ver Ejercicio 3). Usa gpt-4o-mini para filtrado inicial y reserva gpt-4o high solo para análisis que lo requiera.


Ejercicios

Ejercicio 1: Función de comparación con output estructurado (Fácil)

Crea una función compare_two_images(path1, path2) que envíe dos imágenes a OpenAI y devuelva un diccionario con las claves similarities (lista), differences (lista), y verdict (string: "iguales", "similares", o "diferentes").

Ver solución
import json
from openai import OpenAI

client = OpenAI()

def compare_two_images(path1: str, path2: str) -> dict:
    prompt = """Compara la Imagen 1 y la Imagen 2.

Responde ÚNICAMENTE con un JSON válido con esta estructura:
{
  "similarities": ["similitud 1", "similitud 2", ...],
  "differences": ["diferencia 1", "diferencia 2", ...],
  "verdict": "iguales | similares | diferentes"
}"""

    content = [
        {"type": "text", "text": prompt},
        {"type": "image_url", "image_url": {"url": encode_image(path1), "detail": "high"}},
        {"type": "image_url", "image_url": {"url": encode_image(path2), "detail": "high"}},
    ]

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

    raw = response.choices[0].message.content.strip()
    if raw.startswith("```"):
        raw = raw.split("\n", 1)[1].rsplit("```", 1)[0]
    return json.loads(raw)


result = compare_two_images("version_a.png", "version_b.png")
print(f"Veredicto: {result['verdict']}")
print(f"Similitudes: {len(result['similarities'])}")
print(f"Diferencias: {len(result['differences'])}")

Explicación: temperature=0 garantiza respuestas determinísticas. El parsing maneja el caso donde el modelo envuelve el JSON en bloques de código markdown.

Ejercicio 2: Analizador de PDF con chunking y consolidación (Medio)

Crea una función analyze_pdf(pdf_path, prompt, chunk_size=5) que convierta un PDF en imágenes, las procese en chunks, y devuelva un resultado consolidado.

Ver solución
from pdf2image import convert_from_path
from openai import OpenAI
import tempfile

client = OpenAI()

def analyze_pdf(pdf_path: str, prompt: str, chunk_size: int = 5) -> str:
    with tempfile.TemporaryDirectory() as tmpdir:
        images = convert_from_path(pdf_path, dpi=200)
        page_paths = []
        for i, img in enumerate(images):
            path = f"{tmpdir}/page_{i+1:03d}.jpg"
            img.save(path, "JPEG", quality=85)
            page_paths.append(path)

        if len(page_paths) <= chunk_size:
            return analyze_multiple_openai(page_paths, prompt, detail="high")

        partial_results = []
        for start in range(0, len(page_paths), chunk_size):
            chunk = page_paths[start:start + chunk_size]
            first_page, last_page = start + 1, start + len(chunk)
            chunk_prompt = (
                f"Páginas {first_page}-{last_page} de {len(page_paths)}.\n\n{prompt}"
            )
            result = analyze_multiple_openai(chunk, chunk_prompt, detail="high")
            partial_results.append(f"[Páginas {first_page}-{last_page}]\n{result}")

        consolidation = client.chat.completions.create(
            model="gpt-4o",
            messages=[{
                "role": "user",
                "content": "Consolida estos análisis parciales en un resumen unificado. "
                    "Elimina redundancias.\n\n" + "\n\n---\n\n".join(partial_results),
            }],
            max_tokens=4096,
        )
        return consolidation.choices[0].message.content

Explicación: Si el documento cabe en un chunk, envía todo directo. Si no, procesa por lotes y consolida con una llamada de solo texto.

Ejercicio 3: Estimador de costo multi-image (Medio)

Crea una función estimate_batch_cost(image_paths, detail, model) que lea las dimensiones reales de cada imagen, calcule los tokens exactos, y devuelva el costo total estimado antes de hacer cualquier llamada a la API.

Ver solución
import math
from PIL import Image
from pathlib import Path

PRICING = {
    "gpt-4o": {"input": 2.50, "output": 10.00},
    "gpt-4o-mini": {"input": 0.15, "output": 0.60},
}


def calculate_image_tokens(width: int, height: int, detail: str = "high") -> int:
    if detail == "low":
        return 85
    w, h = width, height
    if max(w, h) > 2048:
        scale = 2048 / max(w, h)
        w, h = int(w * scale), int(h * scale)
    if min(w, h) > 768:
        scale = 768 / min(w, h)
        w, h = int(w * scale), int(h * scale)
    tiles = math.ceil(w / 512) * math.ceil(h / 512)
    return 85 + 170 * tiles


def estimate_batch_cost(
    image_paths: list[str],
    detail: str = "high",
    model: str = "gpt-4o",
    output_tokens_per_image: int = 100,
) -> dict:
    total_tokens = 0
    per_image = []
    for path in image_paths:
        with Image.open(path) as img:
            w, h = img.size
        tokens = calculate_image_tokens(w, h, detail)
        total_tokens += tokens
        per_image.append({"path": Path(path).name, "size": f"{w}x{h}", "tokens": tokens})

    total_output = output_tokens_per_image * len(image_paths)
    p = PRICING[model]
    cost = (total_tokens / 1e6) * p["input"] + (total_output / 1e6) * p["output"]

    return {
        "n_images": len(image_paths),
        "total_image_tokens": total_tokens,
        "total_cost_usd": round(cost, 6),
        "per_image": per_image,
    }


est = estimate_batch_cost(["product_1.jpg", "product_2.jpg", "product_3.jpg"], model="gpt-4o-mini")
print(f"{est['n_images']} imágenes: {est['total_image_tokens']:,} tokens, ${est['total_cost_usd']:.6f}")

Explicación: Lee las dimensiones reales de cada imagen con Pillow para calcular tokens exactos. Útil para validar presupuesto antes de procesar lotes grandes.


Resumen

  • Estructura: OpenAI usa array de image_url, Anthropic bloques image con Base64, Gemini una lista plana de contenido
  • Referencia: Siempre numera las imágenes explícitamente en el prompt ("Imagen 1 = X, Imagen 2 = Y")
  • Límites prácticos: ~10 imágenes (OpenAI high), ~20-30 (Anthropic), ~50-100+ (Gemini)
  • Documentos multi-página: Chunking por lotes + consolidación para documentos que exceden el contexto
  • Tokens: Cada imagen en high consume ~765 tokens (1024×1024). Multiplica por N imágenes para estimar el total
  • Costo: Estima siempre antes de ejecutar. detail=low reduce 9x el consumo de tokens de imagen
  • Context window: Si excedes el límite, reduce detail, redimensiona, o aplica chunking. Gemini (1-2M tokens) es la mejor opción para alto volumen

Recursos Adicionales

  1. OpenAI Vision — Multiple Images — Documentación oficial con ejemplos multi-image
  2. Anthropic Vision Docs — Formato de múltiples bloques de imagen
  3. Google Gemini Vision — Multi-image con context extendido
  4. pdf2image (PyPI) — Conversión PDF a imágenes para análisis multi-página
  5. OpenAI Token Calculator — Referencia para estimación de tokens