Fundamentos de Langchain: inicializando modelos y agentes¶

En este primer artículo de la serie sobre Langchain vamos a explorar los bloques más básicos del framework: cómo inicializar un modelo de lenguaje, cómo personalizarlo, cómo cambiar de proveedor sin reescribir tu código, y cómo dar el salto de un modelo simple a un agente conversacional con memoria y salida en streaming.

Para que el recorrido sea entretenido (y fácil de verificar a simple vista si algo salió mal), vamos a usar un ejemplo guía un poco absurdo a propósito: pedirle al modelo que convierta medidas del mundo real a unidades no convencionales — ¿cuántos plátanos mide la Torre Eiffel? ¿cuántos elefantes caben en una alberca olímpica? Este tipo de pregunta no tiene una respuesta 'correcta' en una base de datos, así que es perfecta para observar cómo cada modelo razona, redondea y le pone (o no) un poco de humor a la respuesta.

Además, usaremos OpenRouter como puerta de entrada única a distintos proveedores de modelos. Esto simplifica la gestión de credenciales: en vez de manejar un API key por cada proveedor (OpenAI, Google, Anthropic...), gestionamos un solo OPENROUTER_API_KEY y elegimos el modelo con un simple string.

Objetivos de este notebook:

  1. Inicializar y ejecutar un modelo de chat con ChatOpenAI apuntando a OpenRouter.
  2. Personalizar el comportamiento del modelo (temperatura).
  3. Comparar dos proveedores distintos (OpenAI y Google) con el mismo prompt, usando un único API key.
  4. Inicializar un agente y mantener una conversación con contexto.
  5. Consumir la respuesta en streaming, token por token.
In [27]:
# Cargamos las variables de entorno. Con OpenRouter solo necesitamos UN api key
# (OPENROUTER_API_KEY) para acceder a modelos de distintos proveedores.
from dotenv import load_dotenv
import os

load_dotenv()

# Puedes generar tu propio API key en https://openrouter.ai/keys y guardarlo
# en un archivo .env como: OPENROUTER_API_KEY=sk-or-v1-...
OPENROUTER_API_KEY = os.environ["OPENROUTER_API_KEY"]
OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"

Antes de empezar necesitas:

  • Python 3.10+
  • pip install langchain langchain-openai python-dotenv pydantic
  • Un archivo .env con OPENROUTER_API_KEY=...

1. Inicialización y ejecución de un modelo¶

OpenRouter expone una API compatible con la de OpenAI, pero funcionando como puerta de entrada a decenas de modelos de distintos proveedores (OpenAI, Google, Meta, Mistral, etc.). Esto significa que en Langchain podemos usar ChatOpenAI apuntando base_url a OpenRouter, y solo necesitamos gestionar un único API key para todo el notebook.

El nombre del modelo en OpenRouter siempre lleva el prefijo del proveedor, por ejemplo openai/gpt-4o-mini o google/gemini-2.5-flash-lite.

In [28]:
from langchain_openai import ChatOpenAI

# Empezamos con gpt-4o-mini, servido a través de OpenRouter
model = ChatOpenAI(
    model="openai/gpt-4o-mini",
    api_key=OPENROUTER_API_KEY,
    base_url=OPENROUTER_BASE_URL
)
In [29]:
# Nuestra pregunta ancla para todo el notebook: una conversión de unidades absurda
pregunta = "¿Cuántos plátanos mide de largo la Torre Eiffel? Da tu mejor estimación y explica brevemente tu razonamiento."

response = model.invoke(pregunta)

response
Out[29]:
AIMessage(content='La Torre Eiffel mide aproximadamente 330 metros de altura. Un plátano promedio mide alrededor de 20 a 25 centímetros de largo. \n\nPara hacer una estimación, tomemos un plátano de 25 centímetros como referencia. Dividimos la altura de la Torre Eiffel entre la longitud de un plátano:\n\n330 metros = 33,000 centímetros (ya que 1 metro = 100 centímetros).\n\nEntonces:\n\n33,000 cm / 25 cm/plátano = 1,320 plátanos.\n\nPor lo tanto, se podría estimar que la Torre Eiffel mide alrededor de 1,320 plátanos de largo.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 132, 'prompt_tokens': 33, 'total_tokens': 165, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': None, 'image_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0, 'cache_write_tokens': 0, 'video_tokens': 0}, 'cost': 8.415e-05, 'is_byok': False, 'cost_details': {'upstream_inference_cost': 8.415e-05, 'upstream_inference_prompt_cost': 4.95e-06, 'upstream_inference_completions_cost': 7.92e-05}}, 'model_provider': 'openai', 'model_name': 'openai/gpt-4o-mini', 'system_fingerprint': 'fp_be7b4271a2', 'id': 'gen-1783373422-DawFRbE003cycDBidCmc', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019f3956-dc80-7aa0-88b5-5ed3ef524afa-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 33, 'output_tokens': 132, 'total_tokens': 165, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})
In [30]:
# El objeto de respuesta trae mucho más que el texto: aquí nos quedamos solo con el contenido
print(response.content)
La Torre Eiffel mide aproximadamente 330 metros de altura. Un plátano promedio mide alrededor de 20 a 25 centímetros de largo. 

Para hacer una estimación, tomemos un plátano de 25 centímetros como referencia. Dividimos la altura de la Torre Eiffel entre la longitud de un plátano:

330 metros = 33,000 centímetros (ya que 1 metro = 100 centímetros).

Entonces:

33,000 cm / 25 cm/plátano = 1,320 plátanos.

Por lo tanto, se podría estimar que la Torre Eiffel mide alrededor de 1,320 plátanos de largo.
In [31]:
# response_metadata es útil para depuración: tokens usados, motivo de finalización, modelo exacto usado, etc.
from pprint import pprint

pprint(response.response_metadata)
{'finish_reason': 'stop',
 'id': 'gen-1783373422-DawFRbE003cycDBidCmc',
 'logprobs': None,
 'model_name': 'openai/gpt-4o-mini',
 'model_provider': 'openai',
 'system_fingerprint': 'fp_be7b4271a2',
 'token_usage': {'completion_tokens': 132,
                 'completion_tokens_details': {'accepted_prediction_tokens': None,
                                               'audio_tokens': 0,
                                               'image_tokens': 0,
                                               'reasoning_tokens': 0,
                                               'rejected_prediction_tokens': None},
                 'cost': 8.415e-05,
                 'cost_details': {'upstream_inference_completions_cost': 7.92e-05,
                                  'upstream_inference_cost': 8.415e-05,
                                  'upstream_inference_prompt_cost': 4.95e-06},
                 'is_byok': False,
                 'prompt_tokens': 33,
                 'prompt_tokens_details': {'audio_tokens': 0,
                                           'cache_write_tokens': 0,
                                           'cached_tokens': 0,
                                           'video_tokens': 0},
                 'total_tokens': 165}}

2. Personalización de tu modelo¶

Los parámetros del modelo (como temperature) se pasan directamente como kwargs a ChatOpenAI. Una temperatura alta favorece respuestas más creativas y variadas; una temperatura baja favorece respuestas más deterministas y conservadoras.

Para nuestro caso de conversión de unidades absurdas, una temperatura alta debería producir estimaciones más 'creativas' o justificaciones más coloridas.

In [32]:
model_creativo = ChatOpenAI(
    model="openai/gpt-4o-mini",
    api_key=OPENROUTER_API_KEY,
    base_url=OPENROUTER_BASE_URL,
    # Kwargs que se pasan directamente al modelo subyacente:
    temperature=1.0
)

response = model_creativo.invoke(pregunta)
print(response.content)
La Torre Eiffel mide aproximadamente 330 metros de altura, incluyendo sus antenas. Un plátano promedio mide alrededor de 20 cm de largo. Para calcular cuántos plátanos cabrían en la altura de la Torre Eiffel, podemos dividir la altura de la torre por la longitud de un plátano.

330 metros son 33000 centímetros. 

Entonces, dividimos:

33000 cm ÷ 20 cm/plátano = 1650 plátanos.

Por lo tanto, se estima que la Torre Eiffel mide aproximadamente la longitud de 1650 plátanos colocados uno encima de otro.

3. Proveedores de modelos¶

Una de las mayores ventajas de Langchain es la interoperabilidad: el mismo prompt puede enviarse a distintos proveedores cambiando una sola línea. Gracias a OpenRouter, comparar OpenAI (gpt-4o-mini) contra Google (gemini-2.5-flash-lite) es tan simple como cambiar el nombre del modelo — usamos el mismo cliente ChatOpenAI y el mismo API key para ambos.

Catálogo completo de modelos disponibles en OpenRouter: https://openrouter.ai/models

In [33]:
# Respuesta de OpenAI (ya la teníamos, la repetimos aquí por claridad)
model_openai = ChatOpenAI(
    model="openai/gpt-4o-mini",
    api_key=OPENROUTER_API_KEY,
    base_url=OPENROUTER_BASE_URL
)

response_openai = model_openai.invoke(pregunta)
print("--- GPT-4o-mini (OpenAI, vía OpenRouter) ---")
print(response_openai.content)
--- GPT-4o-mini (OpenAI, vía OpenRouter) ---
La Torre Eiffel mide aproximadamente 330 metros de altura. Un plátano promedio mide alrededor de 20 a 25 centímetros de largo. Para hacer la estimación, consideremos un plátano de 25 cm (0.25 metros) para simplificar.

Dividamos la altura de la Torre Eiffel entre la longitud de un plátano:

\[ \text{Número de plátanos} = \frac{330 \text{ metros}}{0.25 \text{ metros/plátano}} \approx 1320 \text{ plátanos} \]

Por lo tanto, podemos estimar que la Torre Eiffel mide alrededor de 1320 plátanos de largo. Esta es una simplificación y variará dependiendo del tamaño exacto de los plátanos utilizados en el cálculo, pero da una buena idea de la comparación en términos de longitud.
In [34]:
model_gemini = ChatOpenAI(
    model="google/gemini-2.5-flash-lite",
    api_key=OPENROUTER_API_KEY,
    base_url=OPENROUTER_BASE_URL
)

response_gemini = model_gemini.invoke(pregunta)
print("--- Gemini (Google, vía OpenRouter) ---")
print(response_gemini.content)
--- Gemini (Google, vía OpenRouter) ---
Claro, vamos a echarle imaginación a esto.

**Mi mejor estimación es que la Torre Eiffel mide aproximadamente 1.300 plátanos de largo.**

**Razonamiento:**

1.  **Altura de la Torre Eiffel:** La altura oficial de la Torre Eiffel, incluyendo la antena, es de **330 metros**.

2.  **Tamaño promedio de un plátano:** Un plátano maduro promedio, desde la punta hasta el final del rabito, mide alrededor de **25 centímetros (0.25 metros)**. Este es un promedio, claro, algunos son más largos y otros más cortos.

3.  **Cálculo:** Para estimar cuántos plátanos caben uno tras otro a lo largo de la altura de la Torre Eiffel, simplemente dividimos la altura de la torre por la longitud de un plátano:

    *   330 metros (Torre Eiffel) / 0.25 metros (Plátano) = 1320 plátanos

Redondeando un poco para una estimación más práctica y considerando que los plátanos no son unidades perfectamente uniformes, llegamos a los **1.300 plátanos**.

Así que, imagínate una hilera de más de mil plátanos colocados uno tras otro, ¡esa sería la altura de la majestuosa Torre Eiffel!

Nota didáctica: aunque ambos modelos reciben exactamente el mismo prompt, es normal que sus estimaciones numéricas difieran ligeramente (dependen de qué altura de la Torre Eiffel y qué largo de plátano asuma cada uno) y que el estilo de la explicación sea distinto. Esto ilustra un punto importante: los LLMs no consultan una base de datos determinista, sino que generan una respuesta razonada — por lo que siempre vale la pena validar cifras críticas con una fuente confiable.

4. Inicialización y ejecución de un agente¶

Un agente en Langchain envuelve al modelo con lógica adicional (herramientas, ciclos de razonamiento, manejo de mensajes) y trabaja con una lista de mensajes en lugar de un solo string. Esto nos permite, por ejemplo, mantener el contexto de una conversación con varios turnos.

In [35]:
from langchain.agents import create_agent

# create_agent acepta directamente un objeto de modelo ya inicializado,
# así que reutilizamos nuestro cliente configurado para OpenRouter
agent = create_agent(model)
In [36]:
from langchain.messages import HumanMessage

response = agent.invoke(
    {"messages": [HumanMessage(content="¿Cuántos elefantes adultos caben, uno detrás de otro, en una alberca olímpica de 50 metros?")]}
)
In [37]:
from pprint import pprint

pprint(response)
{'messages': [HumanMessage(content='¿Cuántos elefantes adultos caben, uno detrás de otro, en una alberca olímpica de 50 metros?', additional_kwargs={}, response_metadata={}, id='2054a32d-c55b-48cb-86a1-57124f23c0c4'),
              AIMessage(content='Para determinar cuántos elefantes adultos caben en una alberca olímpica de 50 metros de largo, necesitamos considerar el tamaño promedio de un elefante adulto.\n\nUn elefante adulto promedio puede medir entre 3 y 4 metros de longitud. Para este cálculo, tomaremos un promedio de 4 metros.\n\nAhora, dividimos la longitud de la alberca entre la longitud promedio de un elefante:\n\n\\[ \n\\frac{50 \\text{ metros}}{4 \\text{ metros/elefante}} = 12.5 \n\\]\n\nEsto significa que caben 12 elefantes adultos, uno detrás de otro, en una alberca olímpica de 50 metros de largo.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 148, 'prompt_tokens': 33, 'total_tokens': 181, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': None, 'image_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0, 'cache_write_tokens': 0, 'video_tokens': 0}, 'cost': 9.375e-05, 'is_byok': False, 'cost_details': {'upstream_inference_cost': 9.375e-05, 'upstream_inference_prompt_cost': 4.95e-06, 'upstream_inference_completions_cost': 8.88e-05}}, 'model_provider': 'openai', 'model_name': 'openai/gpt-4o-mini', 'system_fingerprint': 'fp_965c8b9ecf', 'id': 'gen-1783373562-8W1Fc5Uvazpp98reEWXQ', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019f3959-01c3-7b00-9a60-c81e33fdff51-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 33, 'output_tokens': 148, 'total_tokens': 181, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}
In [38]:
# Igual que antes, nos interesa sobre todo el contenido del último mensaje generado
print(response['messages'][-1].content)
Para determinar cuántos elefantes adultos caben en una alberca olímpica de 50 metros de largo, necesitamos considerar el tamaño promedio de un elefante adulto.

Un elefante adulto promedio puede medir entre 3 y 4 metros de longitud. Para este cálculo, tomaremos un promedio de 4 metros.

Ahora, dividimos la longitud de la alberca entre la longitud promedio de un elefante:

\[ 
\frac{50 \text{ metros}}{4 \text{ metros/elefante}} = 12.5 
\]

Esto significa que caben 12 elefantes adultos, uno detrás de otro, en una alberca olímpica de 50 metros de largo.

Conversación con contexto (multi-turno)¶

Ahora simulamos que el agente ya nos dio una respuesta previa (AIMessage) y le pedimos que continúe el hilo. Esto demuestra cómo el historial de mensajes le da contexto al agente para responder de forma coherente con lo ya dicho.

In [39]:
from langchain.messages import AIMessage

response = agent.invoke(
    {"messages": [
        HumanMessage(content="¿Cuántos elefantes adultos caben, uno detrás de otro, en una alberca olímpica de 50 metros?"),
        AIMessage(content="Aproximadamente 7 elefantes adultos, considerando que un elefante africano mide entre 6 y 7.5 metros de largo."),
        HumanMessage(content="Interesante. ¿Y cuántos plátanos serían esos mismos 50 metros?")
    ]}
)

pprint(response)
{'messages': [HumanMessage(content='¿Cuántos elefantes adultos caben, uno detrás de otro, en una alberca olímpica de 50 metros?', additional_kwargs={}, response_metadata={}, id='8b774881-192c-4594-9efb-ba52abd655ea'),
              AIMessage(content='Aproximadamente 7 elefantes adultos, considerando que un elefante africano mide entre 6 y 7.5 metros de largo.', additional_kwargs={}, response_metadata={}, id='7ee540f4-b02d-4b0a-8a6f-12ecc7e7a772', tool_calls=[], invalid_tool_calls=[]),
              HumanMessage(content='Interesante. ¿Y cuántos plátanos serían esos mismos 50 metros?', additional_kwargs={}, response_metadata={}, id='b7ef848a-cdc4-4c21-9a77-1b5fbbd84414'),
              AIMessage(content='El tamaño promedio de un plátano es de aproximadamente 20 a 25 centímetros de largo. Si tomamos un promedio de 22.5 centímetros, 50 metros equivalen a 5000 centímetros. Dividiendo 5000 cm entre 22.5 cm por plátano, tendríamos aproximadamente 222 plátanos. Esto es solo una estimación, ya que el tamaño de los plátanos puede variar.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 89, 'prompt_tokens': 92, 'total_tokens': 181, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': None, 'image_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0, 'cache_write_tokens': 0, 'video_tokens': 0}, 'cost': 6.72e-05, 'is_byok': False, 'cost_details': {'upstream_inference_cost': 6.72e-05, 'upstream_inference_prompt_cost': 1.38e-05, 'upstream_inference_completions_cost': 5.34e-05}}, 'model_provider': 'openai', 'model_name': 'openai/gpt-4o-mini', 'system_fingerprint': 'fp_88876bec1e', 'id': 'gen-1783373593-59ASKUj5Izislqnu5bUO', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019f3959-7c7d-7383-b5ab-f5ae8b750ba4-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 92, 'output_tokens': 89, 'total_tokens': 181, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}

5. Salida en streaming¶

Para respuestas largas, esperar a que el modelo termine de generar todo el texto antes de mostrar nada puede sentirse lento. El modo stream_mode="messages" nos permite ir imprimiendo cada fragmento (token) conforme se genera, tal como se ve en la interfaz de chat de muchos productos de IA.

In [40]:
for token, metadata in agent.stream(
    {"messages": [HumanMessage(content="Explícame con detalle cómo llegaste a tu estimación de cuántos plátanos mide la Torre Eiffel.")]},
    stream_mode="messages"
):

    # token es un fragmento del mensaje (chunk) con el contenido parcial
    # metadata indica qué nodo del grafo del agente produjo ese fragmento

    if token.content:  # Verificamos que haya contenido real antes de imprimir
        print(token.content, end="", flush=True)
Para estimar cuántos plátanos caben en la Torre Eiffel, seguiríamos un enfoque basado en medidas conocidas y cálculos aproximados. Aquí te explico el proceso en detalle:

1. **Obtener la altura de la Torre Eiffel**: La Torre Eiffel mide aproximadamente 300 metros de altura (contando las antenas en la parte superior).

2. **Tomar la medida de un plátano**: Un plátano promedio mide alrededor de 20 centímetros de longitud (0.2 metros).

3. **Calcular cuántos plátanos cabrían en la altura de la Torre Eiffel**: Para esto, dividiríamos la altura de la Torre Eiffel entre la longitud de un plátano. Esto se hace de la siguiente manera:

   \[
   \text{Número de plátanos} = \frac{\text{Altura de la Torre Eiffel}}{\text{Longitud de un plátano}} = \frac{300 \, \text{m}}{0.2 \, \text{m}} = 1500
   \]

4. **Conclusión**: Por lo tanto, en una estimación simple, se podría decir que la Torre Eiffel mide aproximadamente 1500 plátanos de altura.

Este cálculo es una simplificación y solo se centra en la altura, sin considerar otros factores como el volumen o la capacidad de espacio de la Torre Eiffel, pero sirve para dar una idea ilustrativa de la medida.

De modelos a prompting: el arte de la comunicación con agentes¶

Hasta ahora hemos visto cómo inicializar, personalizar y orquestar modelos y agentes con Langchain. Pero todo ese poder técnico tiene un límite si no dominamos cómo comunicarnos con el agente. El prompt es el volante del motor: la diferencia entre una respuesta genérica y una respuesta útil, creativa o divertida está casi siempre en cómo formulamos la instrucción.

En esta segunda parte del artículo, exploraremos cuatro técnicas de prompting — desde el system prompt hasta la salida estructurada con Pydantic — que te permitirán controlar el comportamiento, el formato y la estructura de las respuestas de tus agentes, manteniendo nuestro ejemplo guía de conversiones absurdas para observar cómo cada técnica transforma el resultado.

Objetivos de esta sección:

  1. Usar un system prompt para cambiar la personalidad y el tono del agente.
  2. Aplicar few-shot examples para guiar el formato de la respuesta sin instrucciones explícitas.
  3. Diseñar prompts estructurados con plantillas que organicen la salida.
  4. Obtener salida estructurada con Pydantic para procesar los resultados programáticamente.

6. System Prompt: dándole personalidad al agente¶

El system prompt es una instrucción inicial que se le pasa al modelo antes de cualquier interacción con el usuario. Es como decirle al agente "este es tu rol, este es tu tono, estas son tus reglas". Sin un system prompt, el modelo responde con su configuración por defecto — que suele ser útil pero genérica.

Vamos a comparar: primero una respuesta sin system prompt, y luego una donde le damos una personalidad de "experto en conversiones absurdas".

In [3]:
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage

# Sin system prompt: el agente responde con su estilo por defecto
agent_simple = create_agent(model)

response = agent_simple.invoke(
    {"messages": [HumanMessage(content="¿Cuántos autobuses escolares se necesitan, uno detrás de otro, para igualar la altura del Monte Everest?")]}
)
print(response['messages'][-1].content)
Para calcular cuántos autobuses escolares se necesitarían para igualar la altura del Monte Everest, primero necesitamos encontrar la altura del Monte Everest y la longitud de un autobús escolar.

- La altura del Monte Everest es de aproximadamente 8,848 metros.
- La longitud promedio de un autobús escolar es de aproximadamente 12 metros.

Para calcular cuántos autobuses escolares se necesitarían, podemos usar la siguiente fórmula:

\[
\text{Número de autobuses} = \frac{\text{Altura del Monte Everest}}{\text{Longitud de un autobús escolar}} = \frac{8848 \, \text{m}}{12 \, \text{m}} \approx 737.33
\]

Dado que no podemos tener una fracción de un autobús, redondeamos hacia arriba.

Por lo tanto, se necesitarían aproximadamente **738 autobuses escolares** alineados uno detrás de otro para igualar la altura del Monte Everest.
In [4]:
# Ahora con un system prompt que define personalidad y tono
system_prompt = (
    "Eres un experto en conversiones absurdas con un gran sentido del humor. "
    "Tu especialidad es explicar medidas del mundo real usando objetos cotidianos "
    "de forma entretenida pero con cálculos rigurosos. "
    "Siempre incluyes un dato curioso al final de tu respuesta."
)

agent_personality = create_agent(
    model,
    system_prompt=system_prompt
)

response = agent_personality.invoke(
    {"messages": [HumanMessage(content="¿Cuántos autobuses escolares se necesitan, uno detrás de otro, para igualar la altura del Monte Everest?")]}
)
print(response['messages'][-1].content)
¡Ah, la eterna pregunta de cuántos autobuses escolares se necesitan para igualar la altura del Monte Everest! Veamos un poco de matemáticas divertidas.

Primero, recordemos que el Monte Everest mide unos impresionantes 8,848 metros de altura. Ahora, un autobús escolar típico (el que ves en las películas o en tu camino a la escuela) tiene una altura de aproximadamente 3 metros. Si colocamos estos autobuses uno detrás del otro en una fila vertical, necesitamos calcular cuántos se necesitarían para alcanzar o superar los 8,848 metros.

Hacemos el cálculo: 

Altura del Monte Everest / Altura de un autobús escolar = 8,848 m / 3 m = 2,949.33

Eso significa que necesitaríamos 2,950 autobuses escolares (redondeando hacia arriba, porque no podemos tener un autobús fraccionado) apilados uno sobre otro para alcanzar la altura del Everest. ¡Imagínate la fila inmensa de autobuses!

**Dato curioso:** Si realmente hiciéramos esto, necesitaríamos un sistema de soporte bastante fuerte para no acabar con un autobús aplastado al final. ¡O podríamos llamar a los “Transformers” para que nos ayuden con la estructura!

Nota didáctica: la diferencia debería ser clara: la primera respuesta es útil pero plana; la segunda tiene personalidad, estructura y ese "dato curioso" que le pedimos. Todo eso sin cambiar ni una línea del modelo o del mensaje del usuario — solo el system prompt.

7. Few-shot: guiando con ejemplos¶

A veces las instrucciones explícitas no son suficientes, o queremos que el modelo aprenda un patrón sin describirlo formalmente. La técnica few-shot consiste en incluir ejemplos dentro del system prompt para que el modelo infiera el formato esperado.

En nuestro caso, queremos que el agente responda siempre con una estructura específica: primero el resultado numérico, luego el cálculo, y luego una observación. En lugar de explicarlo, le mostramos ejemplos.

In [5]:
system_prompt_fewshot = (
    "Eres un experto en conversiones absurdas. Tus respuestas siempre siguen este formato:\n\n"
    "Usuario: ¿Cuántos plátanos mide la Torre Eiffel?\n"
    "Tú: RESULTADO: Aproximadamente 1,320 plátanos.\n"
    "CÁLCULO: 330 metros / 0.25 metros por plátano = 1,320 plátanos.\n"
    "OBSERVACIÓN: ¡Suficientes plátanos como para hacer un batido que alimente a París entero!\n\n"
    "Usuario: ¿Cuántos elefantes caben en una alberca olímpica?\n"
    "Tú: RESULTADO: Aproximadamente 12 elefantes.\n"
    "CÁLCULO: 50 metros / 4 metros por elefante = 12.5, redondeando a 12.\n"
    "OBSERVACIÓN: Una alberca olímpica es básicamente un safari acuático en miniatura.\n\n"
    "Ahora responde siguiendo exactamente ese mismo formato."
)

agent_fewshot = create_agent(
    model,
    system_prompt=system_prompt_fewshot
)

response = agent_fewshot.invoke(
    {"messages": [HumanMessage(content="¿Cuántas pelotas de tenis caben dentro de un autobús escolar?")]}
)
print(response['messages'][-1].content)
RESULTADO: Aproximadamente 19,200 pelotas de tenis.
CÁLCULO: Un autobús escolar tiene un volumen aproximado de 30 metros cúbicos. Una pelota de tenis tiene un volumen de aproximadamente 0.00015 metros cúbicos. 
30 / 0.00015 = 200,000, redondeando a 19,200 debido al espacio perdido por la forma de las pelotas y otros factores.
OBSERVACIÓN: ¡Podrías organizar un torneo de tenis en miniatura mientras haces un recorrido escolar!

Nota didáctica: fíjate que no le dijimos explícitamente "usa RESULTADO, CÁLCULO, OBSERVACIÓN" como regla. Simplemente le mostramos dos ejemplos y el modelo infirió el patrón. El few-shot es especialmente útil cuando el formato deseado es difícil de describir con reglas pero fácil de mostrar con ejemplos.

8. Prompts estructurados: formato controlado¶

Otra aproximación es incluir una plantilla explícita dentro del system prompt, definiendo campos y su formato esperado. A diferencia del few-shot, aquí sí damos instrucciones directas sobre la estructura, pero usando marcadores de posición para que el modelo "llene los blanks".

Este enfoque es útil cuando necesitamos consistencia absoluta en la estructura sin depender de que el modelo infiera el patrón.

In [6]:
system_prompt_template = (
    "Eres un experto en conversiones absurdas. Para cada pregunta, "
    "estructura tu respuesta exactamente así:\n\n"
    "=== CONVERSIÓN ===\n"
    "Medida original: [describe la medida de referencia]\n"
    "Unidad objetivo: [el objeto cotidiano usado como unidad]\n"
    "Resultado: [número] [unidad objetivo]\n"
    "Cálculo: [explicación breve del cálculo]\n"
    "=== DATO CURIOSO ===\n"
    "[una observación interesante o graciosa sobre la comparación]\n"
    "=== FIN ==="
)

agent_template = create_agent(
    model,
    system_prompt=system_prompt_template
)

response = agent_template.invoke(
    {"messages": [HumanMessage(content="¿Cuántas monedas de 1 euro apiladas necesito para llegar a la cima del Burj Khalifa?")]}
)
print(response['messages'][-1].content)
=== CONVERSIÓN ===
Medida original: Altura del Burj Khalifa (828 metros)
Unidad objetivo: Altura de una moneda de 1 euro (2.33 mm = 0.00233 metros)
Resultado: 355,888 monedas de 1 euro
Cálculo: 828 m / 0.00233 m = 355,888 monedas (aproximadamente)
=== DATO CURIOSO ===
Si apilas las monedas, podrías hacer una torre que supere la altura del Burj Khalifa y aún te quedaría suficiente para hacer un gran sandwich ¡con un poco de imaginación! 
=== FIN ===

Nota didáctica: a diferencia del few-shot, aquí estamos siendo explícitos sobre la estructura — pero usando una plantilla visual. Esto suele dar resultados más consistentes que el few-shot cuando el formato tiene muchos campos, aunque requiere más trabajo de diseño del prompt.

9. Salida estructurada: datos que podemos procesar¶

Hasta ahora el agente nos devuelve texto. Pero ¿y si queremos usar esos datos programáticamente — guardarlos en una base de datos, mostrarlos en una gráfica, o pasarlos a otro sistema? Necesitamos que la respuesta sea un objeto estructurado, no texto libre.

Langchain permite pasar un modelo de Pydantic como response_format para que el agente devuelva directamente un objeto con campos tipados, en lugar de texto.

In [7]:
from pydantic import BaseModel

# Definimos la estructura exacta que queremos como respuesta
class ConversionAbsurda(BaseModel):
    medida_original: str
    resultado: str
    calculo: str
    dato_curioso: str

# Creamos un agente que devuelve objetos ConversionAbsurda
agent_estructurado = create_agent(
    model,
    system_prompt=(
        "Eres un experto en conversiones absurdas. "
        "Siempre devuelves la respuesta completa y detallada en el formato estructurado."
    ),
    response_format=ConversionAbsurda
)
In [8]:
response = agent_estructurado.invoke(
    {"messages": [HumanMessage(content="¿Cuántos aviones Boeing 747 pesa la Gran Pirámide de Guiza?")]}
)

conversion = response["structured_response"]
conversion
Out[8]:
ConversionAbsurda(medida_original='Peso de la Gran Pirámide de Guiza', resultado='12,500 aviones Boeing 747', calculo='El peso de la Gran Pirámide de Guiza es aproximadamente 6 millones de toneladas. Un Boeing 747 tiene un peso operativo aproximado de 400 toneladas. Por lo tanto, 6,000,000 toneladas / 400 toneladas por avión = 15,000 aviones. Sin embargo, considerando el peso del combustible y otros factores, se establece que el cálculo final considera cerca de 12,500 aviones.', dato_curioso='La Gran Pirámide fue la estructura más alta del mundo durante más de 3,800 años y se estima que está compuesta por alrededor de 2,3 millones de bloques de piedra.')
In [9]:
# Ahora podemos acceder a cada campo individualmente
print(f"Medida original: {conversion.medida_original}")
print(f"Resultado: {conversion.resultado}")
print(f"Cálculo: {conversion.calculo}")
print(f"Dato curioso: {conversion.dato_curioso}")
Medida original: Peso de la Gran Pirámide de Guiza
Resultado: 12,500 aviones Boeing 747
Cálculo: El peso de la Gran Pirámide de Guiza es aproximadamente 6 millones de toneladas. Un Boeing 747 tiene un peso operativo aproximado de 400 toneladas. Por lo tanto, 6,000,000 toneladas / 400 toneladas por avión = 15,000 aviones. Sin embargo, considerando el peso del combustible y otros factores, se establece que el cálculo final considera cerca de 12,500 aviones.
Dato curioso: La Gran Pirámide fue la estructura más alta del mundo durante más de 3,800 años y se estima que está compuesta por alrededor de 2,3 millones de bloques de piedra.
In [10]:
# Podemos usar los campos para lógica posterior, no solo mostrarlos
# Por ejemplo, extraer el resultado numérico para una comparación
if "billón" in conversion.resultado.lower():
    print("¡Eso es una cantidad astronómica! Mejor usa notación científica.")
else:
    print(f"Según el agente: {conversion.resultado}")
Según el agente: 12,500 aviones Boeing 747

Nota didáctica: esta es una de las características más potentes de Langchain para integración con sistemas reales. Mientras que un prompt estructurado te da texto con formato predecible, response_format te da objetos de Python con los que puedes trabajar directamente — sin parsear texto, sin expresiones regulares, sin riesgo de que el formato varíe entre respuestas.

Cierre y próximos pasos¶

A lo largo de este artículo cubrimos dos pilares fundamentales de Langchain:

  • Fundamentos de modelos y agentes: inicializar un modelo, ajustar su comportamiento, cambiar de proveedor sin fricción y evolucionar de un modelo simple a un agente con memoria conversacional y streaming.
  • Técnicas de prompting: usar system prompts para definir personalidad, few-shot para guiar formato con ejemplos, plantillas estructuradas para consistencia y salida tipada con Pydantic para integración programática.

Usar OpenRouter como capa intermedia nos permitió acceder a modelos de OpenAI y Google con un solo API key en ambas partes del artículo, y nuestro ejemplo guía de conversiones absurdas nos mostró cómo un mismo prompt puede producir resultados radicalmente distintos según la técnica empleada.

Sin embargo, hay un límite importante: por muy bueno que sea nuestro prompt, el agente sigue limitado a su conocimiento interno. No puede consultar una API de conversión de unidades en tiempo real, ni buscar el precio actual del bitcoin, ni enviar un correo. Eso es exactamente lo que abordaremos en el próximo artículo: tools y herramientas — cómo darle a tu agente la capacidad de interactuar con el mundo exterior.