Módulo 4: Middleware y Customización
Introducción: Personalizar Agentes sin Reescribirlos
Descripción
En el Módulo 3 construiste agentes completos con create_agent. Una línea de código y tenías un agente autónomo: razona, llama tools, observa resultados, y repite hasta tener la respuesta. Le agregaste system prompts, estado custom, streaming, structured output. Todo funcionaba.
Hasta que necesitaste algo que create_agent no resuelve con un parámetro: quieres saber cuánto tarda cada llamada al modelo. O necesitas que use un modelo económico para preguntas simples y uno potente para preguntas complejas. O que filtre las tools disponibles según los permisos del usuario. O que reintente automáticamente cuando una tool falla.
Podrías reescribir el agente desde cero para cada caso. Pero eso va en contra de la idea misma de create_agent: una abstracción de alto nivel que no deberías tener que abrir para modificar.
La solución es middleware — funciones que interceptan lo que pasa entre el agente y el modelo (o entre el agente y las tools) sin modificar la lógica interna del agente. Piensa en ellas como filtros que puedes agregar o quitar sin tocar el código del agente.
¿Dónde estamos en la guía?
Este es el Módulo 4 de la guía LangChain & LangGraph: From Chains to Agents. Es el último módulo del Bloque 1 (LangChain Core) antes de pasar al Bloque 2 (LangGraph Fundamentals).
Bloque 1: LangChain Core (Módulos 1-4) ← ESTÁS AQUÍ (Módulo 4)
Bloque 2: LangGraph Fundamentals (Módulos 5-7)
Bloque 3: LangGraph Avanzado (Módulos 8-10)
Bloque 4: Producción (Módulos 11-12)
Tu progreso en el Bloque 1:
Módulo 1: Modelos y Proveedores ✅ Completado
│
▼
Módulo 2: Tools y Tool Calling ✅ Completado
│
▼
Módulo 3: Agents (create_agent) ✅ Completado
│
▼
Módulo 4: Middleware y Customización ← ESTÁS AQUÍ
En el Módulo 1 aprendiste a conectar con modelos. En el Módulo 2 les diste herramientas. En el Módulo 3 automatizaste el loop completo con create_agent. Ahora vas a personalizar ese agente sin reescribirlo: interceptar sus llamadas, modificar su comportamiento, y agregar capacidades transversales.
El puente: de crear agentes a personalizarlos
Lo que ya sabes
En el Módulo 3 llegaste a esto:
from dotenv import load_dotenv
load_dotenv()
from langchain.agents import create_agent
from langchain_core.tools import tool
@tool
def search(query: str) -> str:
"""Busca información en la web."""
return f"Resultados para '{query}': LangChain es un framework para LLMs."
agent = create_agent(
"openai:gpt-4.1-mini",
tools=[search],
system_prompt="Eres un asistente de investigación."
)
result = agent.invoke({"messages": [("user", "¿Qué es LangChain?")]})
print(result["messages"][-1].content)
# Output esperado: LangChain es un framework para construir aplicaciones con LLMs.
Funciona perfecto. Pero ahora hazte estas preguntas:
- ¿Cuánto tardó la llamada al modelo? No lo sabes.
- ¿Cuántos tokens consumió? No lo sabes.
- ¿Puedes cambiar el modelo a uno más potente si la pregunta es compleja? No sin reescribir.
- ¿Puedes filtrar las tools según quién está preguntando? No sin reescribir.
- ¿Puedes reintentar si el modelo falla? No sin reescribir.
Lo que aprenderás aquí
Con middleware, agregas todas esas capacidades sin tocar el agente:
from dotenv import load_dotenv
load_dotenv()
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
import time
@tool
def search(query: str) -> str:
"""Busca información en la web."""
return f"Resultados para '{query}': LangChain es un framework para LLMs."
model = init_chat_model("openai:gpt-4.1-mini")
def before_model(messages):
print(f"[LOG] Enviando {len(messages)} mensajes al modelo")
def after_model(response):
print(f"[LOG] Modelo respondió con {len(response.content)} caracteres")
if response.tool_calls:
print(f"[LOG] Tool calls: {[tc['name'] for tc in response.tool_calls]}")
agent = create_agent(
model,
tools=[search],
before_model=before_model,
after_model=after_model,
system_prompt="Eres un asistente de investigación."
)
result = agent.invoke({"messages": [("user", "¿Qué es LangChain?")]})
print(result["messages"][-1].content)
# Output esperado:
# [LOG] Enviando 2 mensajes al modelo
# [LOG] Modelo respondió con 0 caracteres
# [LOG] Tool calls: ['search']
# [LOG] Enviando 4 mensajes al modelo
# [LOG] Modelo respondió con 65 caracteres
# LangChain es un framework para construir aplicaciones con LLMs.
El agente funciona exactamente igual. Pero ahora puedes ver lo que hace. Y esto es solo el principio — los middleware más avanzados no solo observan, sino que modifican el comportamiento.
El problema: personalización sin reescritura
Conforme tus agentes pasan de prototipos a producción, necesitas capacidades que cruzan múltiples agentes:
| Necesidad | Sin middleware | Con middleware |
|---|---|---|
| Logging | print() dispersos en el código | Un hook centralizado que intercepta todo |
| Monitoreo de latencia | Medir manualmente cada llamada | @wrap_model_call con time.time() automático |
| Conteo de tokens | Revisar response.usage_metadata manualmente | Acumulador automático en after_model |
| Model routing | if/else antes de crear el agente | Middleware que decide por request |
| Filtrado de tools | Tools hardcoded en create_agent | Dynamic tools por usuario o contexto |
| Retry en tools | try/except dentro de cada tool | @wrap_tool_call con retry logic reutilizable |
| Error handling custom | Lógica de error en cada agente | Un middleware que aplica a todos |
Sin middleware, cada una de estas necesidades requiere modificar el agente directamente — o peor, copiar y pegar lógica entre agentes. Con middleware, escribes la lógica una vez y la aplicas donde la necesites.
Analogía: los checkpoints de seguridad del aeropuerto
Piensa en un aeropuerto:
Pasajero ──▶ [Check-in] ──▶ [Seguridad] ──▶ [Migración] ──▶ Avión
│ │ │
Verifica Inspecciona Valida
identidad equipaje permisos
- El pasajero es el mensaje que va al modelo (o el tool call).
- El avión es el modelo (o la tool).
- Los checkpoints son middleware.
Cada checkpoint puede:
- Inspeccionar lo que pasa (logging — "el pasajero lleva equipaje de mano")
- Modificar lo que pasa (cambiar el gate — "tu vuelo cambió a gate 12")
- Bloquear lo que pasa (seguridad — "este objeto no puede pasar")
- Redirigir lo que pasa (migración — "no tienes visa, ve a otra fila")
Y lo más importante: la aerolínea no cambia. El avión despega igual. Los checkpoints operan de forma independiente — puedes agregar uno nuevo (nuevo control sanitario) o quitar uno (eliminar revisión de líquidos) sin que el avión se entere.
Eso es middleware: interceptores que actúan entre tu código y los servicios que llama, sin que ninguno de los dos tenga que cambiar.
Qué puede hacer el middleware
El middleware de create_agent opera en dos puntos de intercepción:
1. Intercepción de llamadas al modelo
Cada vez que el agente va a llamar al LLM, el middleware puede intervenir:
Mensajes ──▶ [before_model] ──▶ LLM ──▶ [after_model] ──▶ Respuesta
│
[wrap_model_call]
(envuelve todo el ciclo)
before_model— Se ejecuta antes de enviar los mensajes al modelo. Puede inspeccionar los mensajes, loggearlos, o modificarlos.after_model— Se ejecuta después de recibir la respuesta del modelo. Puede inspeccionar la respuesta, loggear métricas, o modificarla.@wrap_model_call— Envuelve el ciclo completo. Recibe el request, puede modificarlo, llama al handler (que ejecuta el modelo), y puede modificar la respuesta. Es el más potente.
2. Intercepción de llamadas a tools
Cada vez que el agente va a ejecutar una tool, el middleware puede intervenir:
Tool call ──▶ [wrap_tool_call] ──▶ Tool ──▶ Resultado
@wrap_tool_call— Envuelve la ejecución de cada tool. Puede inspeccionar los argumentos, modificarlos, reintentar si falla, loggear resultados, o sustituir la tool por completo.
Resumen de los hooks
| Hook | Cuándo se ejecuta | Qué recibe | Para qué sirve |
|---|---|---|---|
before_model | Antes de cada llamada al LLM | Lista de mensajes | Logging, validación, modificar mensajes |
after_model | Después de cada respuesta del LLM | Respuesta del modelo | Logging, métricas, modificar respuesta |
@wrap_model_call | Envuelve todo el ciclo modelo | Request + handler | Model routing, retry, transformación completa |
@wrap_tool_call | Envuelve cada ejecución de tool | Tool call + handler | Retry, error handling, logging de tools |
Mapa del módulo
| Cápsula | Tema | Qué aprenderás |
|---|---|---|
| 02 | Tu primer middleware: logging y monitoreo | before_model, after_model, timing, token counting, callbacks |
| 03 | @wrap_model_call: interceptar llamadas al modelo | ModelRequest, handler pattern, modificar requests, leer state |
| 04 | @wrap_tool_call: personalizar ejecución de tools | ToolCallRequest, custom error handling, retry logic, tool-level logging |
| 05 | Dynamic models: selección inteligente | Routing por complejidad, por costo, por latency requirements |
| 06 | Dynamic tools y dynamic prompts | Filtrar tools por permisos, registrar tools en runtime, prompts dinámicos avanzados |
| 07 | AgentMiddleware class: middleware compuesto | Combinar state_schema + tools + hooks, middleware como módulos reutilizables |
| 08 | Proyecto: Agente con routing dinámico | Agente con modelo económico/potente, logging middleware, dynamic tools |
Flujo de aprendizaje: Empiezas observando lo que el agente hace (logging en cápsula 02). Luego aprendes a modificar las llamadas al modelo (03) y a las tools (04). Con esas bases, construyes capacidades avanzadas: selección dinámica de modelos (05) y de tools (06). Después aprendes a empaquetar todo en módulos reutilizables (07). Finalmente, integras todo en el proyecto (08).
Conexión con el proyecto
Mini-Proyecto de este módulo: Agente con Routing Dinámico de Modelos
En la Cápsula 08 construirás un agente que:
- Usa
@wrap_model_callpara seleccionar automáticamente entre un modelo económico (gpt-4.1-mini) y uno potente (gpt-4.1) según la complejidad de la pregunta - Incluye middleware de logging que registra cada llamada al modelo con timestamp, duración, y tokens consumidos
- Implementa dynamic tools — las tools disponibles cambian según el rol del usuario
- Tiene retry middleware en tools que reintenta automáticamente si una API externa falla
Cada concepto de las cápsulas 02-07 se integra en este proyecto.
Conexión con la guía completa
El middleware es el cierre del Bloque 1. Después de dominarlo, entenderás cómo personalizar agentes a nivel de LangChain — la capa de alto nivel. Lo que viene a continuación opera a un nivel más bajo:
- Módulo 5 (LangGraph Fundamentals): Después de dominar las abstracciones de alto nivel (
create_agent+ middleware), bajarás a LangGraph para construir workflows custom. Si middleware te permite interceptar, LangGraph te permite rediseñar el flujo completo. - Módulos 6-7: Condicionales, loops, y sub-grafos. Estos son los building blocks que
create_agentusa internamente — ahora los construirás tú. - Módulos 8-10: Memoria persistente, human-in-the-loop, y multi-agente. Middleware se complementa con estas capacidades — por ejemplo, puedes usar middleware para loggear las decisiones de un sistema multi-agente.
- Módulos 11-12: Producción y deployment. El middleware de logging y monitoreo que construyes aquí es la base del observability en producción.
Límites: qué NO cubre este módulo
- ❌ Custom LangGraph nodes — Se cubre en Módulos 5-7. Aquí personalizas agentes con middleware; no construyes grafos desde cero.
- ❌ Multi-agent systems — Se cubre en Módulo 10. Aquí trabajas con un solo agente y sus interceptores.
- ❌ Production deployment — Se cubre en Módulos 11-12. Aquí construyes middleware para desarrollo; la infraestructura de producción viene después.
- ❌ LangSmith / LangFuse — Herramientas de observabilidad externas. El middleware de logging que construyes aquí es la versión manual; las plataformas de observabilidad son complementarias.
- ❌ Guardrails avanzados — Validación de contenido con frameworks dedicados. El middleware puede hacer validación básica, pero los guardrails completos son otro tema.
Setup técnico
Prerequisitos
Antes de continuar, verifica que tienes:
- ✅ Módulo 3 completado — sabes crear agentes con
create_agent, system prompts, state, streaming, y structured output - ✅ Familiaridad con decoradores Python — entiendes
@decoratory funciones que reciben/retornan funciones - ✅ Python 3.11+ instalado
- ✅ Al menos una API key de un proveedor que soporte tool calling (OpenAI o Anthropic recomendado)
Instalación
Usas los mismos paquetes que en el Módulo 3. No necesitas instalar nada nuevo:
pip install langchain langgraph langchain-openai python-dotenv
Si ya tienes todo instalado del Módulo 3, verifica que tu versión de langchain es v1.2+ (el middleware system se introdujo en esa versión):
import langchain
print(langchain.__version__)
# Necesitas >= 1.2.0
Verificar que todo funciona
from dotenv import load_dotenv
load_dotenv()
from langchain.agents import create_agent
from langchain_core.tools import tool
@tool
def ping(message: str) -> str:
"""Responde con un pong."""
return f"pong: {message}"
def before_model(messages):
print(f"[TEST] Middleware activo — {len(messages)} mensajes")
agent = create_agent(
"openai:gpt-4.1-mini",
tools=[ping],
before_model=before_model
)
result = agent.invoke({"messages": [("user", "Haz ping con 'hola'")]})
print(result["messages"][-1].content)
# Output esperado:
# [TEST] Middleware activo — 2 mensajes
# [TEST] Middleware activo — 4 mensajes
# pong: hola (o similar)
Si ves los mensajes [TEST] seguidos de la respuesta del agente, tu setup está listo para middleware.
Si algo falla:
| Error | Causa | Solución |
|---|---|---|
ImportError: cannot import name 'create_agent' | Versión de langchain antigua | pip install --upgrade langchain (necesitas v1.2+) |
TypeError: create_agent() got an unexpected keyword argument 'before_model' | Versión de langchain no soporta middleware | pip install --upgrade langchain (necesitas v1.2+) |
ModuleNotFoundError: No module named 'langgraph' | langgraph no instalado | pip install langgraph |
Evidencia de éxito
Al terminar este módulo, sabrás que tuviste éxito si:
- ✅ Puedes agregar logging a un agente existente sin modificar su código
- ✅ Entiendes la diferencia entre
before_model,after_model,@wrap_model_call, y@wrap_tool_call - ✅ Tu agente selecciona automáticamente el modelo correcto según la complejidad de la pregunta
- ✅ Puedes filtrar las tools disponibles según permisos o contexto del usuario
- ✅ Sabes componer múltiples middleware en un
AgentMiddlewarereutilizable - ✅ Tu proyecto demuestra routing dinámico, logging, y dynamic tools funcionando juntos
Vista previa: del middleware a LangGraph
En este módulo personalizas agentes con middleware — interceptores que modifican el comportamiento sin cambiar la estructura. Pero el agente sigue siendo un loop ReAct lineal: model → tools → model → tools → respuesta.
¿Qué pasa cuando necesitas un flujo diferente? Por ejemplo:
- Un agente que primero planifica y luego ejecuta (dos fases secuenciales)
- Un workflow que se ramifica según el tipo de input (clasificación → ruta A o ruta B)
- Un proceso que requiere aprobación humana antes de ejecutar una acción crítica
- Un sistema con múltiples agentes que colaboran
Para eso necesitas LangGraph — el framework de orquestación de bajo nivel. En el Módulo 5, dejarás las abstracciones de alto nivel (create_agent + middleware) y construirás grafos desde cero: nodos, edges, condicionales, y state management custom.
# Módulo 4: Middleware (personalizar el loop ReAct)
agent = create_agent(
model, tools,
middleware=[logging_middleware, routing_middleware]
)
# Módulo 5: LangGraph (diseñar tu propio flujo)
from langgraph.graph import StateGraph
graph = StateGraph(MyState)
graph.add_node("classify", classify_input)
graph.add_node("simple_agent", handle_simple)
graph.add_node("complex_agent", handle_complex)
graph.add_conditional_edges("classify", route_by_complexity)
Middleware te da control sobre el agente. LangGraph te da control del flujo completo.
Resumen
- En el Módulo 3 aprendiste a crear agentes autónomos con
create_agent— pero personalizarlos requería modificar el código del agente directamente - El middleware system de LangChain v1.2+ permite interceptar y modificar el comportamiento del agente sin reescribirlo
- Middleware actúa como checkpoints de seguridad en un aeropuerto: inspecciona, modifica, o bloquea lo que pasa entre el agente y los servicios que llama
- Hay dos puntos de intercepción: llamadas al modelo (
before_model,after_model,@wrap_model_call) y llamadas a tools (@wrap_tool_call) - Los hooks simples (
before_model,after_model) son para observar; los wrappers (@wrap_model_call,@wrap_tool_call) son para modificar - Middleware resuelve necesidades transversales: logging, monitoreo, model routing, dynamic tools, retry, error handling — todo sin copiar lógica entre agentes
- Este es el último módulo del Bloque 1 (LangChain Core). Después viene LangGraph para workflows custom de bajo nivel
- El proyecto integrador es un agente con routing dinámico de modelos — modelo económico para preguntas simples, potente para complejas
Recursos adicionales
- create_agent API Reference — Referencia completa de parámetros incluyendo middleware hooks
- LangChain Agents Overview — Guía conceptual oficial de agentes y personalización
- Middleware Pattern — Wikipedia — Concepto general de middleware en software engineering
- LangGraph Agents — Cómo create_agent construye grafos internamente (contexto para entender dónde se inyecta middleware)
- Python Decorators — Real Python — Refresher de decoradores Python (prerequisito para
@wrap_model_cally@wrap_tool_call) - What's New in LangChain v1.2 — Release notes que introdujeron el middleware system
Módulo 4 — LangChain & LangGraph: From Chains to Agents
Siguiente cápsula: Tu Primer Middleware: Logging y Monitoreo — aprenderás a observar lo que tu agente hace con before_model y after_model.