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:

NecesidadSin middlewareCon middleware
Loggingprint() dispersos en el códigoUn hook centralizado que intercepta todo
Monitoreo de latenciaMedir manualmente cada llamada@wrap_model_call con time.time() automático
Conteo de tokensRevisar response.usage_metadata manualmenteAcumulador automático en after_model
Model routingif/else antes de crear el agenteMiddleware que decide por request
Filtrado de toolsTools hardcoded en create_agentDynamic tools por usuario o contexto
Retry en toolstry/except dentro de cada tool@wrap_tool_call con retry logic reutilizable
Error handling customLógica de error en cada agenteUn 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

HookCuándo se ejecutaQué recibePara qué sirve
before_modelAntes de cada llamada al LLMLista de mensajesLogging, validación, modificar mensajes
after_modelDespués de cada respuesta del LLMRespuesta del modeloLogging, métricas, modificar respuesta
@wrap_model_callEnvuelve todo el ciclo modeloRequest + handlerModel routing, retry, transformación completa
@wrap_tool_callEnvuelve cada ejecución de toolTool call + handlerRetry, error handling, logging de tools

Mapa del módulo

CápsulaTemaQué aprenderás
02Tu primer middleware: logging y monitoreobefore_model, after_model, timing, token counting, callbacks
03@wrap_model_call: interceptar llamadas al modeloModelRequest, handler pattern, modificar requests, leer state
04@wrap_tool_call: personalizar ejecución de toolsToolCallRequest, custom error handling, retry logic, tool-level logging
05Dynamic models: selección inteligenteRouting por complejidad, por costo, por latency requirements
06Dynamic tools y dynamic promptsFiltrar tools por permisos, registrar tools en runtime, prompts dinámicos avanzados
07AgentMiddleware class: middleware compuestoCombinar state_schema + tools + hooks, middleware como módulos reutilizables
08Proyecto: Agente con routing dinámicoAgente 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:

  1. Usa @wrap_model_call para 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
  2. Incluye middleware de logging que registra cada llamada al modelo con timestamp, duración, y tokens consumidos
  3. Implementa dynamic tools — las tools disponibles cambian según el rol del usuario
  4. 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_agent usa 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 @decorator y 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:

ErrorCausaSolución
ImportError: cannot import name 'create_agent'Versión de langchain antiguapip install --upgrade langchain (necesitas v1.2+)
TypeError: create_agent() got an unexpected keyword argument 'before_model'Versión de langchain no soporta middlewarepip install --upgrade langchain (necesitas v1.2+)
ModuleNotFoundError: No module named 'langgraph'langgraph no instaladopip 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 AgentMiddleware reutilizable
  • ✅ 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

  1. create_agent API Reference — Referencia completa de parámetros incluyendo middleware hooks
  2. LangChain Agents Overview — Guía conceptual oficial de agentes y personalización
  3. Middleware Pattern — Wikipedia — Concepto general de middleware en software engineering
  4. LangGraph Agents — Cómo create_agent construye grafos internamente (contexto para entender dónde se inyecta middleware)
  5. Python Decorators — Real Python — Refresher de decoradores Python (prerequisito para @wrap_model_call y @wrap_tool_call)
  6. 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.