Course EN

Capítulo 12 de 22 · intermedio

Visión y entrada multimodal

Qué cubre esta sesión

Los modelos a los que les has estado mandando texto también pueden recibir imágenes. No un modelo de visión aparte, atornillado encima — el mismo modelo de chat, al que le entregas un mensaje cuyo contenido es una lista: unas partes de texto, unas partes de imagen, razonadas todas en una sola pasada. Ese único cambio destraba toda una categoría de trabajo: describir una imagen, responder preguntas sobre una foto, leer un recibo o una gráfica o un screenshot y volverlo datos estructurados, comparar dos fotos. La interfaz casi no cambia; lo que el modelo puede percibir, sí.

Esta semana es corta a propósito en sintaxis nueva y larga en posibilidades. Vas a construir el mensaje multimodal una vez y luego apuntarlo a tres trabajos distintos. La única arruga real es la plomería — OpenAI recibe una URL de imagen directo, Gemini quiere los bytes — y una vez resuelto eso, todo se vuelve "agrega una parte de imagen y pregunta".

OpenAI

El contenido del mensaje se vuelve una lista de partes. OpenAI descarga la URL de la imagen por ti, así que le pasas la URL tal cual junto con el texto.

# The message content is a list: a text part and an image part. OpenAI accepts
# an image URL directly (it fetches it), so no downloading on our side. Add more
# image parts for multi-image reasoning ("what changed between these two?").
def describe(image_url: str, question: str) -> str:
    response = client.responses.create(
        model=_MODEL,
        input=[{
            "role": "user",
            "content": [
                {"type": "input_text", "text": question},
                {"type": "input_image", "image_url": image_url},
            ],
        }],
    )
    return response.output_text

Agrega una segunda parte input_image y ya puedes preguntar "¿qué cambió entre estas dos fotos?" — el modelo ve las dos. La parte de texto sigue siendo la que dirige: la misma imagen con "descríbela" versus "lee la etiqueta del precio" versus "¿esto es seguro de comer?" da tres respuestas distintas. La visión no reemplaza al prompting; le da al prompting más material con qué trabajar.

Gemini

Una diferencia de cableado: Gemini quiere la imagen como bytes inline, no como URL, así que la descargas primero y le pasas una parte de bytes. Después de eso la forma es la misma — parte de imagen, parte de texto, una llamada.

# Download the image and pass it as an inline bytes part alongside the question.
# The contents list mixes a Part and a plain string — Gemini treats the string
# as a text part.
def describe(image_url: str, question: str) -> str:
    image_bytes = urllib.request.urlopen(urllib.request.Request(image_url, headers={"User-Agent": "Mozilla/5.0"})).read()
    response = client.models.generate_content(
        model=_MODEL,
        contents=[
            types.Part.from_bytes(data=image_bytes, mime_type=_mime(image_url)),
            question,
        ],
    )
    return response.text or ""

Un par de notas prácticas que te ahorran una tarde de confusión. Las imágenes cuestan tokens, y una foto de alta resolución puede costar muchísimos — reduce la escala antes de mandarla si no necesitas el detalle. Y los modelos de visión son fuertes describiendo y leyendo pero más débiles en razonamiento espacial preciso ("¿la taza está exactamente 3cm a la izquierda del plato?"); confía en ellos para el contenido, verifícalos para la geometría.

Ponlo a trabajar

Tres apps de docker-compose bajo code/showcase/<slug>/. Cada una recibe una URL de imagen pública como input y regresa texto, así que corren en la misma caja de texto que cualquier otra semana. Mismo procedimiento: bash bootstrap-secrets.sh, docker compose up --build, http://localhost:3000.

Showcase 1 — Descripción de imagen

Entra una URL, salen un texto alt y una descripción corta. La llamada multimodal más simple posible, y una genuinamente útil: el texto alt automático es una victoria de accesibilidad que la mayoría de los sitios nunca llega a implementar.

Showcase 2 — Preguntas y respuestas visuales

La primera línea es una URL, el resto es una pregunta. Pregunta cuántas personas hay en una foto, qué dice un letrero, si el cielo está nublado. La imagen es el contexto y tu pregunta es el prompt — retrieval-augmented generation, salvo que el "documento" es una foto.

Showcase 3 — Parser de recibos

Visión más salida estructurada de la semana 4. La foto de un recibo se vuelve JSON limpio — comercio, fecha, partidas, total. Esta es la que se paga sola: convertir documentos-como-imágenes en filas de base de datos es una tarea que antes necesitaba un pipeline de OCR a la medida y ahora necesita un prompt.

Las tres construyen el mismo mensaje multimodal y cambian solo la instrucción y el manejo de la salida. Apúntalo a una descripción, a una pregunta o a un schema.

Córrelo

El README de esta carpeta tiene la versión de Python, la línea de instalación, las dos variables de entorno y los comandos exactos. Las keys vienen del .env sin trackear en la raíz del curso. Las imágenes se descargan con la biblioteca estándar; solo las llamadas al SDK necesitan keys.

Lo que te llevas

La entrada multimodal son los mismos modelos con un mensaje más rico — el contenido se vuelve una lista de partes de texto e imagen, razonadas juntas — así que todo lo que ya sabes sobre prompting, salida estructurada y grounding sigue aplicando, ahora sobre pixeles. La plomería es la única trampa (URL para OpenAI, bytes para Gemini), y dos hábitos te mantienen fuera de problemas: reduce la escala de las imágenes que no necesitas en resolución completa porque cuestan tokens de verdad, y confía en la visión para el contenido mientras la verificas para la geometría precisa. La próxima semana el modelo deja de solo leer imágenes y empieza a hacerlas: generación de imágenes.