Módulo 1: Modelos y Providers

Multimodal y Reasoning

Descripción de la cápsula

Hasta ahora, todos los ejemplos que has visto envían texto al modelo y reciben texto de vuelta. Pero los modelos modernos pueden hacer mucho más: procesar imágenes, interpretar diagramas, analizar screenshots, y en algunos casos trabajar con audio y video. Todo esto sin cambiar de framework — LangChain usa el mismo sistema de mensajes que ya conoces, solo con un formato de contenido más rico.

La segunda parte de esta cápsula cubre los modelos de reasoning: modelos que "piensan" internamente antes de dar una respuesta final. En lugar de generar texto token por token de izquierda a derecha, estos modelos dedican tokens a razonar sobre el problema, considerar alternativas y verificar su lógica. El resultado son respuestas más precisas en tareas que requieren matemáticas, análisis multi-paso o lógica compleja.

Ambas capacidades — multimodal y reasoning — se integran directamente en el flujo que ya conoces con init_chat_model e invoke. En el proyecto del módulo, podrás usar multimodal para analizar imágenes dentro del chat, y reasoning para preguntas que necesiten respuestas más rigurosas.


Contenido multimodal: más allá del texto

Los modelos multimodales aceptan imágenes (y otros medios) como parte del input. En LangChain, esto se implementa a través de content blocks: en vez de pasar un string simple como contenido, pasas una lista de bloques donde cada bloque puede ser texto o una imagen.

Enviar imágenes al modelo

Hasta ahora has usado HumanMessage con un string:

from langchain_core.messages import HumanMessage

message = HumanMessage(content="¿Qué es Python?")

Para enviar imágenes, content pasa de ser un string a una lista de diccionarios:

message = HumanMessage(
    content=[
        {"type": "text", "text": "¿Qué ves en esta imagen?"},
        {"type": "image_url", "image_url": {"url": "https://..."}},
    ]
)

Cada bloque tiene un type: "text" para texto normal, "image_url" para imágenes (por URL o base64). Puedes combinar cuantos bloques necesites en un solo mensaje.


Imagen desde URL

La forma más directa de enviar una imagen es con una URL pública:

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage

model = init_chat_model("openai:gpt-4.1")

message = HumanMessage(
    content=[
        {"type": "text", "text": "Describe esta imagen en 2-3 frases."},
        {
            "type": "image_url",
            "image_url": {
                "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"
            },
        },
    ]
)

response = model.invoke([message])
print(response.content)
# Output esperado: "La imagen muestra un gato doméstico de pelaje atigrado..."

Puntos clave:

  • invoke recibe una lista de mensajes, no un solo mensaje
  • La URL debe ser accesible públicamente — el proveedor la descarga internamente
  • Usa un modelo con soporte de visión (GPT-4.1, Claude Sonnet 4, Gemini 2.0 Flash)

Imagen desde base64

Para imágenes locales o privadas que no tienen URL pública, conviértelas a base64:

from dotenv import load_dotenv
load_dotenv()

import base64
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage

model = init_chat_model("openai:gpt-4.1")


def encode_image(path: str) -> str:
    """Lee una imagen local y retorna su representación base64."""
    with open(path, "rb") as f:
        return base64.standard_b64encode(f.read()).decode("utf-8")


image_data = encode_image("foto.jpg")

message = HumanMessage(
    content=[
        {"type": "text", "text": "¿Qué objeto aparece en esta foto?"},
        {
            "type": "image_url",
            "image_url": {
                "url": f"data:image/jpeg;base64,{image_data}"
            },
        },
    ]
)

response = model.invoke([message])
print(response.content)
# Output esperado: Descripción del contenido de la imagen local

La URL de base64 sigue el formato data:{mime_type};base64,{datos}. Los MIME types comunes:

ExtensiónMIME typeNotas
.jpg / .jpegimage/jpegFotos, el más común
.pngimage/pngImágenes con transparencia
.webpimage/webpFormato moderno, buena compresión
.gifimage/gifSolo procesa el primer frame

Múltiples imágenes en un mensaje

Puedes enviar varias imágenes en un solo mensaje añadiendo más bloques image_url:

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage

model = init_chat_model("openai:gpt-4.1")

message = HumanMessage(
    content=[
        {"type": "text", "text": "Compara estas dos imágenes. ¿Qué diferencias ves?"},
        {
            "type": "image_url",
            "image_url": {
                "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"
            },
        },
        {
            "type": "image_url",
            "image_url": {
                "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/26/YellowLabradorLooking_new.jpg/1200px-YellowLabradorLooking_new.jpg"
            },
        },
    ]
)

response = model.invoke([message])
print(response.content)
# Output esperado: "La primera imagen muestra un gato mientras que la segunda
# muestra un perro labrador. Las principales diferencias son..."

No hay un límite fijo en la cantidad de imágenes, pero cada imagen consume tokens del context window. Imágenes más grandes = más tokens = más costo.


Multimodal con Structured Output

Puedes combinar multimodal con with_structured_output de la cápsula anterior para extraer datos estructurados de imágenes:

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
from pydantic import BaseModel, Field


class ImageAnalysis(BaseModel):
    description: str = Field(description="Descripción breve de la imagen en una frase")
    objects: list[str] = Field(description="Lista de objetos principales detectados")
    dominant_colors: list[str] = Field(description="Colores dominantes en la imagen")
    mood: str = Field(description="Estado de ánimo o atmósfera general")


model = init_chat_model("openai:gpt-4.1")
structured_model = model.with_structured_output(ImageAnalysis)

message = HumanMessage(
    content=[
        {"type": "text", "text": "Analiza esta imagen."},
        {
            "type": "image_url",
            "image_url": {
                "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"
            },
        },
    ]
)

result = structured_model.invoke([message])

print(f"Descripción: {result.description}")
print(f"Objetos: {result.objects}")
print(f"Colores: {result.dominant_colors}")
print(f"Mood: {result.mood}")
# Output esperado:
# Descripción: Un gato atigrado descansando en un entorno exterior
# Objetos: ['gato', 'pasto', 'plantas']
# Colores: ['verde', 'marrón', 'gris']
# Mood: tranquilo

Este patrón es muy útil para pipelines de procesamiento de imágenes. Funciona con cualquier schema Pydantic de la Cápsula 05.


Soporte multimodal por proveedor

ProveedorModeloImágenesAudioVideo
OpenAIgpt-4.1
OpenAIgpt-4.1-mini
OpenAIo3-mini
Anthropicclaude-sonnet-4-20250514
Anthropicclaude-haiku-4-20250514
Googlegemini-2.0-flash
Ollamallava
  • ✅ GPT-4.1 y Claude Sonnet 4 son las opciones principales para visión
  • ✅ Gemini 2.0 Flash es el más versátil: soporta imágenes, audio y video
  • ❌ Los modelos de reasoning (o3-mini) generalmente no soportan multimodal
  • ⚠️ El soporte de audio y video varía frecuentemente — verifica la documentación del proveedor

Audio y video (overview)

Algunos modelos aceptan audio y video como input además de imágenes. El patrón es similar — content blocks con tipos específicos:

import base64
from langchain_core.messages import HumanMessage

with open("grabacion.mp3", "rb") as f:
    audio_data = base64.standard_b64encode(f.read()).decode("utf-8")

# Audio input (GPT-4.1, Gemini 2.0 Flash)
message = HumanMessage(
    content=[
        {"type": "text", "text": "Transcribe este audio."},
        {
            "type": "input_audio",
            "input_audio": {"data": audio_data, "format": "mp3"},
        },
    ]
)

Para video, Gemini 2.0 Flash es actualmente el principal modelo con soporte directo:

# Video input (Gemini 2.0 Flash)
message = HumanMessage(
    content=[
        {"type": "text", "text": "Describe qué sucede en este video."},
        {"type": "media", "mime_type": "video/mp4", "data": video_base64},
    ]
)

⚠️ Audio y video son capacidades más nuevas cuyas APIs están en evolución. El formato exacto de los content blocks puede variar entre proveedores y versiones del paquete. Verifica siempre la documentación actualizada del proveedor correspondiente.


Reasoning: modelos que razonan paso a paso

¿Qué son los modelos de reasoning?

Los modelos estándar (GPT-4.1, Claude Sonnet 4) generan texto token por token sin "pensar" globalmente sobre el problema. Funcionan bien para la mayoría de tareas, pero pueden fallar en problemas que requieren lógica multi-paso o razonamiento matemático complejo.

Los modelos de reasoning dedican tokens internos a razonar sobre el problema antes de generar la respuesta final: descomponen la tarea, consideran alternativas, verifican pasos intermedios. Más tokens = más tiempo y costo, pero mayor calidad en tareas complejas.

Hay dos approaches principales:

ApproachProveedorModelosRazonamiento visible
Modelos dedicadosOpenAIo3-miniNo (tokens internos)
Extended thinkingAnthropicClaude Sonnet 4, Claude Opus 4Sí (bloques en la respuesta)

La diferencia clave: con OpenAI, el razonamiento es interno. Con Anthropic, puedes ver exactamente qué pensó el modelo.


Reasoning con OpenAI (o3-mini)

o3-mini es un modelo diseñado para razonamiento. Se usa igual que cualquier otro modelo:

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model

model = init_chat_model("openai:o3-mini")

response = model.invoke(
    "Un tren sale de Madrid a las 8:00 a 120 km/h. "
    "Otro sale de Barcelona (620 km) a las 9:00 a 150 km/h hacia Madrid. "
    "¿A qué hora se cruzan?"
)
print(response.content)
# Output esperado: Resolución con cálculos y respuesta correcta (~10:20)

El modelo razona internamente pero no muestra los pasos en content. Los tokens de razonamiento aparecen en la metadata:

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model

model = init_chat_model("openai:o3-mini")
response = model.invoke("¿Cuántos números primos hay entre 1 y 50?")

print(response.content)
# Output esperado: Hay 15 números primos entre 1 y 50: 2, 3, 5, 7, 11, 13, ...

print(f"Tokens de entrada: {response.usage_metadata['input_tokens']}")
print(f"Tokens de salida: {response.usage_metadata['output_tokens']}")
print(f"Total: {response.usage_metadata['total_tokens']}")
# Output esperado:
# Tokens de entrada: 18
# Tokens de salida: 245
# Total: 263

Los tokens de salida incluyen los tokens de razonamiento (internos) y los de la respuesta visible. Pagas por ambos.


Effort levels (OpenAI)

Puedes controlar cuánto esfuerzo dedica o3-mini a razonar con el parámetro reasoning_effort:

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model

model_low = init_chat_model("openai:o3-mini", reasoning_effort="low")
model_medium = init_chat_model("openai:o3-mini", reasoning_effort="medium")
model_high = init_chat_model("openai:o3-mini", reasoning_effort="high")

question = "Explica por qué 0.1 + 0.2 != 0.3 en la mayoría de lenguajes de programación."

for label, m in [("low", model_low), ("medium", model_medium), ("high", model_high)]:
    response = m.invoke(question)
    tokens = response.usage_metadata["output_tokens"]
    print(f"[{label:6s}] Tokens de salida: {tokens}")
    print(f"[{label:6s}] Respuesta: {response.content[:120]}...")
    print()
# Output esperado: cada nivel usa más tokens y da respuestas más detalladas
# [low   ] ~80 tokens  → respuesta concisa
# [medium] ~200 tokens → explicación con contexto
# [high  ] ~450 tokens → análisis detallado del estándar IEEE 754
Effort levelVelocidadCostoCalidadCuándo usarlo
"low"RápidoBajoBuena para tareas simplesClasificación, preguntas factuales directas
"medium"MedioMedioBuena en generalDefault recomendado para la mayoría de casos
"high"LentoAltoMáximaMatemáticas complejas, lógica formal, debugging de código

Importante: o3-mini ignora el parámetro temperature. El comportamiento se controla exclusivamente con reasoning_effort. Si necesitas controlar aleatoriedad, usa un modelo estándar.


Extended thinking con Anthropic

Anthropic ofrece un approach diferente: los modelos Claude pueden activar extended thinking, donde los pasos de razonamiento son visibles en la respuesta. Requiere inicialización directa con ChatAnthropic:

from dotenv import load_dotenv
load_dotenv()

from langchain_anthropic import ChatAnthropic

model = ChatAnthropic(
    model="claude-sonnet-4-20250514",
    max_tokens=16000,
    thinking={
        "type": "enabled",
        "budget_tokens": 10000,
    },
)

response = model.invoke(
    "Si tengo 3 cajas, cada caja tiene 2 bolsas, y cada bolsa tiene 5 monedas, "
    "¿cuántas monedas tengo en total? Muestra tu razonamiento."
)

# Con extended thinking, response.content es una LISTA de bloques
for block in response.content:
    if block["type"] == "thinking":
        print(f"[Razonamiento del modelo]")
        print(block["thinking"][:500])
        print()
    elif block["type"] == "text":
        print(f"[Respuesta final]")
        print(block["text"])

# Output esperado:
# [Razonamiento del modelo]
# Necesito calcular el total de monedas.
# 3 cajas × 2 bolsas = 6 bolsas. 6 bolsas × 5 monedas = 30. Verifico: 3×2×5 = 30.
#
# [Respuesta final]
# Tienes 30 monedas en total.

Requisitos de extended thinking:

  • max_tokens es obligatorio y debe ser mayor que budget_tokens
  • budget_tokens controla cuántos tokens puede usar el modelo para pensar
  • ⚠️ temperature debe ser 1 (o no especificarse) cuando extended thinking está activo
  • ⚠️ No todos los modelos Claude lo soportan — verifica la documentación de Anthropic

Cuándo usar reasoning vs modelos estándar

Reasoning no es siempre la mejor opción. Más razonamiento significa más tokens, más latencia y más costo. Usa esta guía para decidir:

TareaModelo recomendadoPor qué
Q&A simple, resúmenesGPT-4.1-mini, Claude HaikuRápido y barato, no necesita razonamiento
Escritura creativaGPT-4.1, Claude Sonnet 4Necesita calidad, no lógica formal
Extracción de datos (structured output)GPT-4.1-miniTarea mecánica, reasoning es desperdicio
Problemas de matemáticaso3-mini (high)Necesita verificación de pasos
Análisis lógico complejoo3-mini (high), Claude + thinkingDescomposición multi-paso
Debugging de códigoo3-mini (medium), Claude + thinkingRastrear el flujo del programa
Clasificación de textoGPT-4.1-miniTarea simple, no necesita razonamiento
Comparar opciones con trade-offsClaude + thinkingRazonamiento visible ayuda a auditar

Regla general: Si puedes resolver la tarea sin pensar más de 5 segundos, usa un modelo estándar. Si necesitas papel y lápiz para verificar la respuesta, usa reasoning.


Conexión con el proyecto

En el Chat Multi-Proveedor con Fallback:

  • Puedes añadir soporte multimodal para que el usuario envíe imágenes al chat
  • Los content blocks funcionan igual con todos los proveedores que soportan visión — el fallback puede pasar de GPT-4.1 a Claude Sonnet 4 sin cambiar el formato del mensaje
  • Para preguntas complejas, el chat puede rutear a un modelo de reasoning en vez del estándar
  • La metadata structured (Cápsula 05) puede incluir un campo que indique si se usó reasoning

Troubleshooting

Problema 1: "Could not process image" o error al enviar imagen

Causa: El modelo no soporta input multimodal, o la URL de la imagen no es accesible. Solución: Verifica que estás usando un modelo con soporte de visión (ver tabla de soporte). Si usas URL, asegúrate de que es pública y accesible:

import requests

url = "https://tu-imagen.com/foto.jpg"
r = requests.head(url)
print(f"Status: {r.status_code}")
print(f"Content-Type: {r.headers.get('content-type')}")
# Debe ser 200 y un content-type de imagen

Problema 2: content como lista no funciona con el modelo

Causa: El modelo no soporta content blocks (ej: o3-mini, o modelos locales sin visión). Solución: Verifica el soporte en la tabla de proveedores. Si necesitas un modelo sin soporte multimodal, envía solo texto:

# Esto funciona con cualquier modelo
message = HumanMessage(content="Texto simple")

# Esto requiere modelo con soporte multimodal
message = HumanMessage(content=[{"type": "text", "text": "..."}, {"type": "image_url", ...}])

Problema 3: Imagen base64 causa error de tamaño

Causa: La imagen es demasiado grande. Los proveedores tienen límites (OpenAI: ~20MB, Anthropic: ~5MB por imagen). Solución: Redimensiona la imagen antes de codificarla:

from PIL import Image
import io
import base64


def encode_image_resized(path: str, max_size: int = 1024) -> str:
    """Redimensiona la imagen si excede max_size y retorna base64."""
    img = Image.open(path)

    if max(img.size) > max_size:
        img.thumbnail((max_size, max_size))

    buffer = io.BytesIO()
    img.save(buffer, format="JPEG", quality=85)
    return base64.standard_b64encode(buffer.getvalue()).decode("utf-8")

Problema 4: reasoning_effort no tiene efecto

Causa: Estás usando reasoning_effort con un modelo que no es de reasoning (ej: gpt-4.1). Solución: Este parámetro solo funciona con modelos de reasoning como o3-mini. Para modelos estándar, usa temperature para controlar el comportamiento.

Problema 5: Extended thinking de Anthropic retorna string en vez de lista

Causa: El parámetro thinking no se pasó correctamente, o falta max_tokens. Solución: max_tokens es obligatorio cuando usas thinking, y budget_tokens debe ser menor que max_tokens:

model = ChatAnthropic(
    model="claude-sonnet-4-20250514",
    max_tokens=16000,
    thinking={"type": "enabled", "budget_tokens": 10000},
)

Ejercicios

Ejercicio 1: Describir una imagen (Fácil)

Envía una imagen desde URL a un modelo con soporte multimodal y pídele que la describa. Usa gpt-4.1 o claude-sonnet-4-20250514. Imprime la descripción.

Ver solución
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage

model = init_chat_model("openai:gpt-4.1")

message = HumanMessage(
    content=[
        {"type": "text", "text": "Describe esta imagen en detalle."},
        {
            "type": "image_url",
            "image_url": {
                "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/300px-PNG_transparency_demonstration_1.png"
            },
        },
    ]
)

response = model.invoke([message])
print(response.content)
# Output esperado: Descripción detallada de la imagen
# (dos dados rojos sobre un fondo con patrón de cuadros que demuestra transparencia PNG)

Explicación: El content block de tipo image_url le dice al modelo que descargue y procese la imagen. El modelo combina el texto de la instrucción con el análisis visual para generar la descripción.

Ejercicio 2: Extraer datos de una imagen (Medio)

Combina multimodal con Structured Output. Define un schema Pydantic ProductInfo con campos name, price (float), y category (str). Envía la URL de una imagen de un producto y extrae los datos estructurados.

Ver solución
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
from pydantic import BaseModel, Field


class ProductInfo(BaseModel):
    name: str = Field(description="Nombre del producto visible en la imagen")
    price: float = Field(description="Precio del producto si es visible, 0.0 si no")
    category: str = Field(description="Categoría general del producto: electrónica, ropa, alimento, otro")


model = init_chat_model("openai:gpt-4.1")
structured_model = model.with_structured_output(ProductInfo)

message = HumanMessage(
    content=[
        {"type": "text", "text": "Extrae la información del producto de esta imagen."},
        {
            "type": "image_url",
            "image_url": {
                "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/AirPods_Pro_%282nd_generation%29.jpg/440px-AirPods_Pro_%282nd_generation%29.jpg"
            },
        },
    ]
)

result = structured_model.invoke([message])
print(f"Nombre: {result.name}")
print(f"Precio: ${result.price}")
print(f"Categoría: {result.category}")
# Output esperado:
# Nombre: AirPods Pro (2nd generation)
# Precio: $0.0  (no visible en la imagen)
# Categoría: electrónica

Explicación: with_structured_output funciona igual con mensajes multimodales. El modelo analiza la imagen y mapea lo que ve a los campos del schema Pydantic. El Field(description=...) guía al modelo sobre qué extraer de la imagen.

Ejercicio 3: Reasoning con o3-mini (Fácil)

Usa o3-mini para resolver un problema de lógica. Compara la respuesta con la de gpt-4.1-mini para el mismo problema. Imprime ambas respuestas y los tokens usados.

Ver solución
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model

question = (
    "Tengo 5 camisas y 3 pantalones. "
    "¿De cuántas formas diferentes puedo vestirme si siempre uso una camisa y un pantalón?"
)

model_standard = init_chat_model("openai:gpt-4.1-mini")
model_reasoning = init_chat_model("openai:o3-mini")

print("=== GPT-4.1-mini (estándar) ===")
r1 = model_standard.invoke(question)
print(f"Respuesta: {r1.content}")
print(f"Tokens: {r1.usage_metadata['total_tokens']}")

print("\n=== o3-mini (reasoning) ===")
r2 = model_reasoning.invoke(question)
print(f"Respuesta: {r2.content}")
print(f"Tokens: {r2.usage_metadata['total_tokens']}")
# Output esperado: Ambos responden 15, pero o3-mini usa más tokens (reasoning interno).

Explicación: Ambos llegan a la respuesta correcta, pero o3-mini usa más tokens (y más dinero) porque razona internamente. Para tareas simples, el modelo estándar es más eficiente.

Ejercicio 4: Comparar effort levels (Medio)

Haz la misma pregunta compleja a o3-mini con los 3 niveles de effort (low, medium, high). Imprime la respuesta, los tokens de salida, y mide el tiempo de cada uno.

Ver solución
from dotenv import load_dotenv
load_dotenv()

import time
from langchain.chat_models import init_chat_model

question = (
    "Un caracol sube 3 metros cada día pero resbala 2 metros cada noche. "
    "Si el pozo tiene 10 metros de profundidad, ¿cuántos días tarda en salir?"
)

for effort in ["low", "medium", "high"]:
    model = init_chat_model("openai:o3-mini", reasoning_effort=effort)

    start = time.time()
    response = model.invoke(question)
    elapsed = time.time() - start

    print(f"=== Effort: {effort} ===")
    print(f"Respuesta: {response.content[:200]}")
    print(f"Tokens de salida: {response.usage_metadata['output_tokens']}")
    print(f"Tiempo: {elapsed:.1f}s")
    print()

# Output esperado: low es rápido con pocos tokens, high es lento pero detallado.
# Todos deberían responder 8 días (al día 8 sube a 10m y sale).

Explicación: Observa cómo high dedica más tokens (y tiempo) a razonar. En este caso, la respuesta correcta es 8 días (al día 8 sube a 10m y sale antes de que anochezca). Con low, el modelo puede dar la respuesta correcta pero con menos detalle. Con high, probablemente verás una verificación día por día.

Ejercicio 5: Extended thinking con Anthropic (Difícil)

Usa ChatAnthropic con extended thinking habilitado para resolver un problema de lógica. Extrae y muestra por separado el razonamiento y la respuesta final. Calcula qué porcentaje de los tokens se dedicó al razonamiento.

Ver solución
from dotenv import load_dotenv
load_dotenv()

from langchain_anthropic import ChatAnthropic

model = ChatAnthropic(
    model="claude-sonnet-4-20250514",
    max_tokens=8000,
    thinking={"type": "enabled", "budget_tokens": 5000},
)

question = (
    "En una carrera, adelantas al que va en segundo lugar. "
    "¿En qué posición quedas?"
)

response = model.invoke(question)

thinking_text = ""
answer_text = ""

for block in response.content:
    if block["type"] == "thinking":
        thinking_text = block["thinking"]
    elif block["type"] == "text":
        answer_text = block["text"]

print("[Razonamiento del modelo]")
print(thinking_text)
print()
print("[Respuesta final]")
print(answer_text)
print()

input_tokens = response.usage_metadata["input_tokens"]
output_tokens = response.usage_metadata["output_tokens"]
print(f"Tokens de entrada: {input_tokens}")
print(f"Tokens de salida: {output_tokens}")

# Output esperado:
# [Razonamiento del modelo]
# La pregunta intenta generar una respuesta intuitiva incorrecta.
# Si adelantas al que va en segundo, tomas SU posición...
#
# [Respuesta final]
# Quedas en segundo lugar. Al adelantar al segundo, ocupas su posición.

Explicación: Extended thinking revela el proceso de razonamiento completo. Puedes auditar cómo el modelo llegó a su conclusión, algo imposible con o3-mini.


Resumen

En esta cápsula aprendiste:

  • Content blocks permiten enviar imágenes (y otros medios) junto con texto en un solo mensaje
  • Para imágenes desde URL, usas {"type": "image_url", "image_url": {"url": "https://..."}}
  • Para imágenes locales, las codificas en base64 con el formato data:image/jpeg;base64,...
  • Multimodal + Structured Output se combinan directamente — puedes extraer datos tipados de imágenes
  • Los modelos de reasoning (o3-mini) dedican tokens internos a pensar antes de responder
  • reasoning_effort controla cuánto piensa o3-mini: "low", "medium", "high"
  • Extended thinking de Anthropic hace visible el razonamiento en bloques separados de la respuesta
  • No todo necesita reasoning — para tareas simples, los modelos estándar son más rápidos y baratos

Próxima cápsula: Modelos locales, caching y rate limiting — cómo ejecutar modelos con Ollama, reducir costos con prompt caching, y manejar rate limits en producción.


Recursos adicionales

  1. Multimodal — LangChain Docs — Guía conceptual de multimodal en LangChain
  2. How to pass multimodal data to models — Tutorial paso a paso con content blocks
  3. OpenAI Vision Guide — Documentación de visión de OpenAI
  4. Anthropic Vision Documentation — Guía de visión de Anthropic con límites y formatos
  5. OpenAI Reasoning Models — Guía de o3-mini y reasoning effort
  6. Anthropic Extended Thinking — Documentación de extended thinking con Claude
  7. Gemini Multimodal Capabilities — Imágenes, audio y video con Gemini
  8. LangChain ChatAnthropic API Reference — Referencia del parámetro thinking

Módulo 1 — LangChain & LangGraph: From Chains to Agents