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:
invokerecibe 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ón | MIME type | Notas |
|---|---|---|
.jpg / .jpeg | image/jpeg | Fotos, el más común |
.png | image/png | Imágenes con transparencia |
.webp | image/webp | Formato moderno, buena compresión |
.gif | image/gif | Solo 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
| Proveedor | Modelo | Imágenes | Audio | Video |
|---|---|---|---|---|
| OpenAI | gpt-4.1 | ✅ | ✅ | ❌ |
| OpenAI | gpt-4.1-mini | ✅ | ✅ | ❌ |
| OpenAI | o3-mini | ❌ | ❌ | ❌ |
| Anthropic | claude-sonnet-4-20250514 | ✅ | ❌ | ❌ |
| Anthropic | claude-haiku-4-20250514 | ✅ | ❌ | ❌ |
| gemini-2.0-flash | ✅ | ✅ | ✅ | |
| Ollama | llava | ✅ | ❌ | ❌ |
- ✅ 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:
| Approach | Proveedor | Modelos | Razonamiento visible |
|---|---|---|---|
| Modelos dedicados | OpenAI | o3-mini | No (tokens internos) |
| Extended thinking | Anthropic | Claude Sonnet 4, Claude Opus 4 | Sí (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 level | Velocidad | Costo | Calidad | Cuándo usarlo |
|---|---|---|---|---|
"low" | Rápido | Bajo | Buena para tareas simples | Clasificación, preguntas factuales directas |
"medium" | Medio | Medio | Buena en general | Default recomendado para la mayoría de casos |
"high" | Lento | Alto | Máxima | Matemá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_tokenses obligatorio y debe ser mayor quebudget_tokens - ✅
budget_tokenscontrola cuántos tokens puede usar el modelo para pensar - ⚠️
temperaturedebe ser1(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:
| Tarea | Modelo recomendado | Por qué |
|---|---|---|
| Q&A simple, resúmenes | GPT-4.1-mini, Claude Haiku | Rápido y barato, no necesita razonamiento |
| Escritura creativa | GPT-4.1, Claude Sonnet 4 | Necesita calidad, no lógica formal |
| Extracción de datos (structured output) | GPT-4.1-mini | Tarea mecánica, reasoning es desperdicio |
| Problemas de matemáticas | o3-mini (high) | Necesita verificación de pasos |
| Análisis lógico complejo | o3-mini (high), Claude + thinking | Descomposición multi-paso |
| Debugging de código | o3-mini (medium), Claude + thinking | Rastrear el flujo del programa |
| Clasificación de texto | GPT-4.1-mini | Tarea simple, no necesita razonamiento |
| Comparar opciones con trade-offs | Claude + thinking | Razonamiento 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_effortcontrola 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
- Multimodal — LangChain Docs — Guía conceptual de multimodal en LangChain
- How to pass multimodal data to models — Tutorial paso a paso con content blocks
- OpenAI Vision Guide — Documentación de visión de OpenAI
- Anthropic Vision Documentation — Guía de visión de Anthropic con límites y formatos
- OpenAI Reasoning Models — Guía de o3-mini y reasoning effort
- Anthropic Extended Thinking — Documentación de extended thinking con Claude
- Gemini Multimodal Capabilities — Imágenes, audio y video con Gemini
- LangChain ChatAnthropic API Reference — Referencia del parámetro
thinking
Módulo 1 — LangChain & LangGraph: From Chains to Agents