
LangGraph: arquitectura, patrones y agentes con estado (2026)
Los agentes de IA construidos sobre LLMs tienen un problema estructural: cada llamada al modelo es sin estado. El sistema olvida lo que hizo en el paso anterior, no puede pausar a esperar aprobación humana y no recupera contexto de ejecuciones pasadas. LangGraph existe para resolver exactamente eso: es un framework de orquestación que modela el flujo de un agente como un grafo dirigido con estado persistente, donde cada nodo es una función Python y cada edge define qué nodo se ejecuta a continuación según el valor actual del estado.
Este post es la guía técnica completa de LangGraph para builders que ya han leído la documentación superficial y necesitan entender la arquitectura real: cómo se define el StateGraph, cómo funcionan los reducers de mensajes, cómo implementar edges condicionales con tools_condition, cómo persiste el estado entre turns con MemorySaver, y cómo conectar tools externas vía agentes de IA para empresas de producción usando el MCP adapter. Incluye cinco bloques de código Python ejecutables con la API actual de LangGraph 0.2.x. Si lo que necesitas es desplegar un agente con estado en producción, estás en el sitio correcto.
El nivel asumido es dev senior o AI engineer con Python fluido y conocimiento básico de LLM APIs. Si vienes buscando «cómo usar ChatGPT», este post no es para ti. Si vienes buscando por qué tu cadena LangChain no puede pausar a mitad de ejecución para pedir confirmación al usuario, sí lo es.
Tabla de contenidos
- ¿Qué es LangGraph y qué problema resuelve?
- El modelo mental: State, Nodes, Edges
- StateGraph: definir el estado del agente
- Nodos: funciones que mutan el estado
- Edges condicionales y enrutamiento
- Construir tu primer agente con LangGraph (código)
- Persistencia: checkpoints y memoria long-term
- Human-in-the-loop y interrupts
- LangGraph + MCP: integrar tools externas
- LangGraph vs alternativas (tabla comparativa)
- Persistencia avanzada: SqliteSaver y PostgresSaver
- Preguntas frecuentes sobre LangGraph
- Conclusión
¿Qué es LangGraph y qué problema resuelve?
El límite de las cadenas lineales
El modelo de composición más común en sistemas LLM es la cadena: una secuencia de pasos donde la salida del paso N alimenta la entrada del paso N+1. El framework LangChain popularizó este patrón con LLMChain, SequentialChain y, más recientemente, LangChain Expression Language (LCEL). Las cadenas son excelentes para flujos deterministas y predecibles: summarization, traducción, extracción estructurada. Pero en cuanto el agente necesita tomar decisiones basadas en resultados intermedios, el modelo lineal colapsa. No hay ramificación real, no hay bucles controlados, no hay vuelta atrás.
El problema se vuelve más agudo cuando el sistema requiere persistencia entre turns de conversación. Un agente de soporte técnico que maneja un ticket durante varios días necesita recordar qué herramientas ejecutó ayer, qué respondió el usuario esta mañana y en qué punto del flujo quedó interrumpido. Una cadena stateless no puede hacer eso sin arquitectura adicional ad hoc.
LangGraph como state machine ejecutable
LangGraph reencuadra el problema: en vez de una cadena, un agente es un grafo dirigido potencialmente cíclico donde el estado fluye entre nodos. Cada nodo recibe el estado actual, ejecuta lógica (llamar al LLM, invocar una tool, hacer un cálculo) y devuelve una actualización parcial del estado. Los edges, que pueden ser estáticos o condicionales, determinan qué nodo se activa a continuación. Este modelo es isomórfico a una state machine finita y permite expresar bucles, ramificaciones, paralelismo y puntos de interrupción de forma explícita y auditada.
LangGraph fue lanzado en enero de 2024 por el equipo de LangChain como respuesta directa a las limitaciones del modelo cadena para agentes complejos. Desde la versión 0.1, el framework ha madurado significativamente: la API estable de StateGraph llegó en 0.1.5, los checkpointers con base de datos en 0.1.14, y el soporte nativo para subgrafos y LangGraph Platform en 0.2.x. Este post usa la API 0.2.x actual.
Cuándo LangGraph y cuándo no
LangGraph tiene sentido cuando el agente cumple al menos una de estas condiciones: necesita ciclos (el agente puede llamar al LLM múltiples veces en una misma tarea), necesita branches condicionales reales (qué nodo activar depende del output del LLM o de una tool), necesita persistencia entre invocaciones separadas (memoria long-term via checkpointer), o necesita human-in-the-loop (pausar la ejecución y reanudarla tras intervención humana). Si tu caso de uso es un pipeline ETL determinista sin ninguna de estas características, LCEL o incluso un script Python plano son más simples y más rápidos de mantener. LangGraph añade complejidad real — úsala cuando el valor lo justifique.
El modelo mental: State, Nodes, Edges
State: el único source of truth del agente
En LangGraph, el State es un TypedDict Python que contiene toda la información que el agente necesita para operar. Es el único source of truth: nada de variables globales, nada de contexto implícito en closures, nada de bases de datos externas acopladas directamente al grafo. Todo lo que el agente sabe en un momento dado está en el State. Cada vez que un nodo se ejecuta, recibe el State completo como argumento y devuelve un diccionario con las claves que quiere actualizar. LangGraph aplica ese delta al State usando un reducer — la función que define cómo se fusionan los updates parciales con el estado existente.
El reducer más importante del ecosistema es add_messages, importado de langgraph.graph.message. En vez de sobreescribir la lista de mensajes (comportamiento por defecto de TypedDict), add_messages los acumula y, si un nodo devuelve un mensaje con el mismo id, lo actualiza en lugar de duplicarlo. Esto es crítico para agentes conversacionales donde el historial de mensajes es el estado principal.
Nodes: unidades de trabajo atómicas
Un nodo en LangGraph es cualquier callable Python que acepte el State y devuelva un diccionario parcial. Puede ser una función regular, una función asíncrona, una clase con __call__, o incluso otro grafo (subgrafo). La atomicidad es clave: un nodo no debe tener efectos secundarios fuera del State salvo los estrictamente necesarios (llamadas a APIs externas, escrituras en bases de datos). La separación entre la lógica del nodo y la gestión del estado la hace LangGraph, no el desarrollador.
Los nodos más comunes son: el nodo LLM (que llama al modelo y devuelve el AIMessage resultante), el nodo tools (que ejecuta las tools solicitadas en el AIMessage y devuelve ToolMessages con los resultados) y los nodos de decisión (que inspeccionan el estado y determinan un branch sin llamar al LLM). Esta separación de responsabilidades hace que los grafos sean testeables en aislamiento.
Edges: el flujo de control explícito
Los edges conectan nodos y definen el orden de ejecución. LangGraph distingue tres tipos: edges estáticos (add_edge(A, B): siempre se va de A a B), edges condicionales (add_conditional_edges(A, router_fn, {value: node}): el nodo al que se va depende del valor devuelto por router_fn), y edges especiales hacia START y END. START es el nodo virtual de entrada al grafo. END termina la ejecución y devuelve el State final. Un grafo puede tener múltiples caminos hacia END, lo que permite que diferentes branches terminen la ejecución bajo condiciones distintas sin necesidad de un nodo de finalización unificado.
La función router del edge condicional más habitual en agentes ReAct es tools_condition, incluida en langgraph.prebuilt. Inspecciona el último AIMessage: si contiene tool_calls, devuelve "tools"; si no, devuelve END. Esta función cubre el 80% de los casos de agente con tool use sin necesidad de escribir lógica de routing propia.
StateGraph: definir el estado del agente
TypedDict con Annotated reducers
La definición del State es el primer paso al construir cualquier grafo. LangGraph usa el sistema de tipos de Python de forma no convencional: los reducers se especifican como metadata del tipo usando Annotated de typing. Cuando LangGraph ve Annotated[list[BaseMessage], add_messages], interpreta add_messages como la función que debe aplicar para fusionar updates en esa clave. Para claves sin Annotated, el reducer por defecto es sobreescritura directa.
Esta separación entre la definición del tipo y el comportamiento de merge es elegante: el TypedDict sigue siendo un TypedDict válido desde la perspectiva de mypy o pyright, y los reducers quedan documentados inline. La alternativa — definir los reducers en un diccionario separado al construir el grafo — es más propensa a inconsistencias.
# Code block 1: Setup + definición del State
# Instalación recomendada con uv
# uv add langgraph langchain-openai python-dotenv
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage
# El State es el TypedDict que contiene todo el contexto del agente.
# La clave `messages` usa add_messages como reducer:
# en lugar de sobreescribir la lista, acumula mensajes y
# actualiza los existentes si coincide el id.
class State(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
# Instanciar el grafo pasando el TypedDict como schema.
# LangGraph usa las anotaciones para inferir los reducers.
graph_builder = StateGraph(State)
Claves de estado adicionales
La mayoría de los agentes necesitan más que el historial de mensajes. Un agente de RAG necesita guardar los documentos recuperados. Un agente de ventas necesita el customer_id y el deal_stage. Un agente de procesamiento por lotes necesita un contador de intentos y un flag de error. Todas estas claves van en el mismo TypedDict, con su propio reducer si aplica. Si la clave es un entero o un string que se sobreescribe en cada paso, no necesita Annotated. Si es una lista que debe acumularse, usa Annotated[list, operator.add] o un reducer custom.
Una práctica recomendada es separar el estado en dos capas: el estado conversacional (mensajes, historial) que gestionan los nodos LLM y tools, y el estado de dominio (datos de negocio, flags, IDs de recursos) que gestionan nodos específicos de la lógica de negocio. Esta separación facilita el testing de cada capa de forma independiente y mantiene el grafo legible conforme crece.
Validación del estado con Pydantic
A partir de LangGraph 0.2, el schema del StateGraph también puede ser un modelo Pydantic v2 en lugar de un TypedDict. Esto permite validación en runtime: si un nodo devuelve un diccionario con un tipo incorrecto para una clave, LangGraph lanza un ValidationError inmediatamente en lugar de propagar silenciosamente datos corruptos. Para agentes en producción donde la robustez es crítica, la validación Pydantic compensa el overhead de serialización. Para prototipos o agentes de desarrollo, el TypedDict es más ágil.
Nodos: funciones que mutan el estado
Anatomía de un nodo LLM
El nodo LLM más simple llama al modelo con el historial de mensajes actual y devuelve el AIMessage resultante. LangGraph se encarga de añadirlo al State via el reducer add_messages. El nodo no necesita saber nada del grafo: solo recibe el State, usa state["messages"], llama al modelo y devuelve {"messages": [response]}. Esta pureza funcional es lo que hace que los nodos sean tan testeables.
El binding de tools al LLM se hace fuera del nodo, cuando se construye el modelo, usando llm.bind_tools(tools). El nodo call_model no sabe qué tools hay: simplemente llama al LLM enlazado y devuelve lo que el modelo decide. Si el modelo quiere usar una tool, emite un AIMessage con tool_calls. Si no, emite un AIMessage con texto plano. La decisión de qué hacer con ese resultado la toma el edge condicional, no el nodo.
Nodo ToolNode: ejecutar tools declaradas
ToolNode, importado de langgraph.prebuilt, es el nodo estándar para ejecutar tools. Recibe el State, extrae los tool_calls del último AIMessage, ejecuta cada tool con los argumentos indicados, y devuelve los resultados como una lista de ToolMessage. El ToolNode gestiona errores por defecto: si una tool lanza una excepción, el error se captura y se devuelve como un ToolMessage con el texto del error. Este comportamiento se puede configurar con handle_tool_errors=True/False.
Para agentes que usan context engineering avanzado, conviene revisar el contenido de los ToolMessages antes de que lleguen al siguiente turno del LLM. En ese caso, en lugar de usar el ToolNode predefinido, se implementa un nodo personalizado que ejecuta las tools y aplica post-procesamiento: truncar resultados demasiado largos, reformatear JSON, filtrar campos innecesarios. Este patrón reduce el token count y mejora la calidad de las decisiones del modelo. Para más detalle sobre cómo gestionar el contexto del LLM, ver nuestra guía de context engineering.
Paralelismo con Send
LangGraph soporta ejecución paralela de nodos usando la primitiva Send. Cuando un edge condicional devuelve una lista de objetos Send(node_name, state_override) en lugar de un string, LangGraph activa todos esos nodos en paralelo y fusiona sus resultados en el State usando los reducers definidos. Este mecanismo es útil para agentes de procesamiento por lotes (fan-out/fan-in), donde un nodo de planificación descompone una tarea en subtareas independientes que se ejecutan en paralelo y cuyos resultados se agregan antes de continuar.
Edges condicionales y enrutamiento
add_conditional_edges: la primitiva de routing
La función add_conditional_edges del StateGraph acepta tres argumentos: el nodo de origen, una función router que recibe el State y devuelve un string o lista de strings, y un diccionario de mapeo {valor_devuelto: nodo_destino}. El diccionario es opcional: si el router devuelve directamente el nombre del nodo destino, LangGraph lo usa sin mapeo. Omitir el diccionario hace el grafo más compacto pero menos legible, especialmente cuando los valores del router son constantes mágicas. La convención recomendada es usar siempre el diccionario de mapeo explícito.
La función router puede ser tan simple como inspeccionar el último mensaje (state["messages"][-1]) o tan compleja como ejecutar un clasificador secundario con otro LLM. En la mayoría de los agentes ReAct, tools_condition de langgraph.prebuilt es suficiente: devuelve "tools" si el último AIMessage tiene tool_calls, o END si no.
Routing multi-agente y subgrafos
En arquitecturas multi-agente, el router es el componente central que decide qué agente especializado debe manejar cada subtarea. Un agente orquestador recibe la solicitud del usuario, la clasifica (soporte, ventas, técnico, escalación) y emite un edge condicional hacia el subgrafo correspondiente. LangGraph soporta subgrafos como nodos de primer nivel: graph_builder.add_node("support_agent", support_subgraph.compile()). El subgrafo puede tener su propio State con campos adicionales; LangGraph gestiona la proyección entre el State del grafo padre y el del subgrafo automáticamente si los campos coinciden en nombre.
Este patrón es la base de los agentes de IA para empresas más robustos en producción: un orquestador estático que distribuye trabajo entre agentes especializados, cada uno con su propio grafo, checkpointer y contexto. El patrón está relacionado con la arquitectura agéntica que describe los distintos modos de composición de agentes autónomos.
Ciclos controlados y condición de salida
Una de las características más importantes de LangGraph respecto a los frameworks de cadenas es el soporte nativo para ciclos. Un agente ReAct puede ejecutar el LLM N veces antes de responder al usuario: LLM decide tool, tool se ejecuta, LLM recibe resultado, decide otra tool, y así hasta que el LLM decide responder sin llamar a ninguna tool. LangGraph modela este ciclo como un grafo con un edge de vuelta del nodo tools al nodo LLM. Para evitar bucles infinitos, la práctica estándar es añadir un contador de pasos al State y un edge condicional que fuerce la salida si el contador supera un umbral. La API de compile() también acepta recursion_limit como parámetro de seguridad.
Construir tu primer agente con LangGraph (código)
Arquitectura ReAct: el patrón más común
ReAct (Reasoning + Acting), descrito en el paper de Yao et al. (arXiv 2210.03629), es el patrón de agente más ampliamente implementado en producción. El LLM alterna entre razonamiento sobre la situación actual y ejecución de acciones (tools). En LangGraph, este patrón se expresa directamente en la topología del grafo: dos nodos (LLM y tools) conectados por un ciclo donde el edge condicional decide si seguir ejecutando tools o terminar. El grafo completo tiene menos de 60 líneas ejecutables.
# Code block 2: Agente ReAct completo con LangGraph 0.2.x
# Ejecutable con: uv run python react_agent.py
import os
from typing import Annotated, TypedDict
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import BaseMessage, HumanMessage
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
load_dotenv()
# --- 1. Definición del State ---
class State(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
# --- 2. Tools disponibles para el agente ---
@tool
def buscar_precio(producto: str) -> str:
"""Devuelve el precio actual de un producto en el catálogo."""
precios = {
"laptop": "1.299 EUR",
"monitor": "399 EUR",
"teclado": "89 EUR",
}
return precios.get(producto.lower(), f"Producto '{producto}' no encontrado.")
@tool
def calcular_descuento(precio_base: float, porcentaje: float) -> str:
"""Calcula el precio final aplicando un porcentaje de descuento."""
descuento = precio_base * (porcentaje / 100)
precio_final = precio_base - descuento
return f"Precio base: {precio_base} EUR | Descuento: {descuento:.2f} EUR | Precio final: {precio_final:.2f} EUR"
tools = [buscar_precio, calcular_descuento]
# --- 3. Modelo LLM con tools enlazadas ---
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
llm_with_tools = llm.bind_tools(tools)
# --- 4. Nodos del grafo ---
def call_model(state: State) -> dict:
"""Nodo LLM: llama al modelo con el historial de mensajes."""
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
# ToolNode ejecuta automáticamente las tools solicitadas en tool_calls
tool_node = ToolNode(tools)
# --- 5. Construcción del grafo ---
graph_builder = StateGraph(State)
graph_builder.add_node("llm", call_model)
graph_builder.add_node("tools", tool_node)
# Edges: START -> llm, luego condicional: tools o END
graph_builder.add_edge(START, "llm")
graph_builder.add_conditional_edges(
"llm",
tools_condition, # devuelve "tools" o END según tool_calls en el último AIMessage
)
graph_builder.add_edge("tools", "llm") # después de ejecutar tools, vuelve al LLM
# --- 6. Compilar y ejecutar ---
graph = graph_builder.compile()
# Invocar el agente
response = graph.invoke({
"messages": [HumanMessage(content="¿Cuánto cuesta una laptop con un 15% de descuento?")]
})
# El último mensaje es la respuesta final del agente
print(response["messages"][-1].content)
Inspeccionando el grafo visualmente
LangGraph incluye un método graph.get_graph().draw_mermaid_png() que genera un diagrama Mermaid del grafo compilado. Esto es útil durante el desarrollo para verificar que los edges son los esperados y que no hay nodos huérfanos. En entornos Jupyter, display(Image(graph.get_graph().draw_mermaid_png())) renderiza el diagrama inline. Para proyectos más complejos con subgrafos, draw_mermaid_png(xray=True) expande los subgrafos en el diagrama.
El método graph.invoke() es síncrono. Para streaming de tokens o de steps del grafo, usar graph.stream(input, stream_mode="values") (devuelve el State completo tras cada nodo) o stream_mode="updates" (devuelve solo el delta de cada nodo). El streaming es especialmente relevante para aplicaciones con interfaz de usuario donde el tiempo hasta primer token (TTFT) impacta en la experiencia.
Testing de nodos en aislamiento
Dado que los nodos son funciones puras que aceptan un dict y devuelven un dict, se pueden testear sin instanciar el grafo completo. Un test unitario del nodo call_model simplemente construye un State con mensajes de ejemplo, llama directamente a la función y verifica el output. Esto es radicalmente más rápido que un test de integración que ejecuta el grafo completo. Para nodos que llaman a APIs externas (LLM, bases de datos), usar mocks estándar de pytest. La arquitectura de LangGraph hace que esta práctica sea natural, no un afterthought.
Persistencia: checkpoints y memoria long-term
El problema del estado efímero
Sin persistencia, cada invocación de graph.invoke() comienza con un State vacío. El agente no recuerda nada de interacciones anteriores. Esto es suficiente para agentes single-turn (responde una pregunta y termina), pero inaceptable para casos de uso conversacionales o de larga duración: un agente de IA para ventas que gestiona un pipeline comercial durante días, un agente de soporte que sigue un ticket durante semanas, o un agente de procesamiento que reanuda un lote interrumpido. Para estos casos, LangGraph ofrece un sistema de checkpointing: el State se serializa y persiste tras cada nodo, indexado por un thread_id.
MemorySaver y checkpointers de producción
El checkpointer más simple es MemorySaver, que persiste el State en memoria RAM del proceso. Es perfecto para desarrollo y testing. Para producción, LangGraph ofrece checkpointers persistentes: SqliteSaver (SQLite, ideal para single-server), PostgresSaver (PostgreSQL con pgvector, recomendado para multi-tenant) y, a través de LangGraph Platform, un checkpointer gestionado con S3 o GCS. El contrato es el mismo en todos los casos: se construye el checkpointer, se pasa al método compile(checkpointer=...), y las invocaciones se identifican por thread_id en el config.
# Code block 3: Persistencia con MemorySaver y thread_id
# El agente recuerda el historial completo entre invocaciones del mismo thread
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import HumanMessage
# Reutilizamos graph_builder del ejemplo anterior (sin compilar todavía)
memory = MemorySaver()
graph = graph_builder.compile(checkpointer=memory)
# El thread_id identifica la "conversación" o "sesión"
# Todos los mensajes del mismo thread_id se acumulan automáticamente
config = {"configurable": {"thread_id": "sesion-usuario-42"}}
# Primer turno
response1 = graph.invoke(
{"messages": [HumanMessage(content="¿Cuánto cuesta una laptop?")]},
config=config
)
print("Respuesta 1:", response1["messages"][-1].content)
# Segundo turno — el agente recuerda que hablamos de laptops
response2 = graph.invoke(
{"messages": [HumanMessage(content="¿Y si le aplico un 20% de descuento?")]},
config=config
)
print("Respuesta 2:", response2["messages"][-1].content)
# Inspeccionar el estado guardado del thread
state = graph.get_state(config)
print(f"Mensajes en el historial: {len(state.values['messages'])}")
# Ver el historial completo de checkpoints del thread
for checkpoint in graph.get_state_history(config):
print(f"Step {checkpoint.metadata.get('step', '?')}: {checkpoint.metadata}")
Snapshot y time-travel
Una capacidad diferencial de LangGraph respecto a otros frameworks de agentes es el time-travel: dado que cada step del grafo genera un checkpoint, es posible revertir el State a cualquier punto anterior e iniciar una nueva rama de ejecución desde allí. Esto es útil en dos escenarios: depuración (reproducir un estado concreto que causó un fallo) y corrección humana (el usuario revisa el step 3 del agente, considera que la decisión fue incorrecta y reinicia desde el step 2 con una instrucción adicional). La API es graph.update_state(config, values, as_node="node_name") seguida de graph.invoke(None, config) para reanudar desde ese punto.
Para agentes que necesitan integrarse en sistemas empresariales con requisitos de auditoría, la pista de auditoría automática que generan los checkpoints es un feature de compliance, no solo una herramienta de debug.
Human-in-the-loop y interrupts
Interrupts: pausar el grafo entre nodos
El mecanismo de interrupt de LangGraph permite pausar la ejecución del grafo antes o después de un nodo específico y reanudarla tras una acción humana. Esto se configura en compile(interrupt_before=["node_name"]) o interrupt_after=["node_name"]. Cuando el grafo llega a ese nodo, detiene la ejecución y devuelve el control al caller. El State queda persistido en el checkpointer. Cuando el humano completa su revisión, el caller invoca graph.invoke(None, config) y la ejecución continúa desde donde se pausó.
Este patrón resuelve uno de los problemas más comunes en agentes de producción: acciones de alto impacto (ejecutar un pago, enviar un email masivo, borrar registros en base de datos) que requieren aprobación explícita antes de realizarse. Sin human-in-the-loop nativo, implementar esto requiere arquitectura adicional (colas de mensajes, workflows externos). Con LangGraph, es una línea en compile().
Actualizar el estado antes de reanudar
El interrupt no solo permite aprobar o rechazar: también permite que el humano modifique el State antes de que continúe la ejecución. graph.update_state(config, {"messages": [HumanMessage(content="Aprobado, pero limita el presupuesto a 500 EUR")]}, as_node="human_review") inyecta un mensaje en el historial antes de reanudar. El LLM ve ese mensaje en el siguiente turno y ajusta su comportamiento. Este flujo es la base de los patrones de corrección iterativa donde el agente propone un plan, el humano lo refina, y el agente ejecuta el plan refinado.
Para implementaciones de agentes de IA en entornos regulados (sanidad, finanzas, legal), el human-in-the-loop no es opcional: los reguladores europeos (AI Act, DORA) exigen supervisión humana para decisiones de alto impacto. LangGraph proporciona el mecanismo técnico para implementar esa supervisión de forma auditada. Para entornos WordPress, el patrón de interrupt también es relevante cuando el agente IA para WordPress necesita aprobación antes de publicar o modificar contenido automáticamente.
Dynamic interrupts con NodeInterrupt
A partir de LangGraph 0.2, también existe la clase NodeInterrupt (subclase de Exception), que permite interrumpir el grafo desde dentro de un nodo en función de condiciones dinámicas. Si el nodo detecta que una acción supera un umbral de riesgo, puede lanzar raise NodeInterrupt("Requiere aprobación: el importe supera 10.000 EUR"). LangGraph captura la excepción, persiste el State y devuelve el interrupt al caller con el mensaje de la excepción. Esto es útil cuando la decisión de interrumpir no se puede tomar en tiempo de compilación (porque depende del valor del State en runtime).
LangGraph + MCP: integrar tools externas
Por qué MCP y LangGraph son complementarios
El Model Context Protocol (MCP) es el estándar abierto propuesto por Anthropic que define cómo los LLM clients descubren y llaman tools expuestas por servidores MCP. Donde MCP resuelve el problema de cómo un LLM accede a tools externas de forma estandarizada, LangGraph resuelve el problema de en qué orden se llaman esas tools y cómo se persiste el estado entre llamadas. Son capas complementarias: MCP es el protocolo de transporte, LangGraph es el motor de orquestación.
La integración práctica se hace a través del paquete langchain-mcp-adapters, que expone tools de un MCP server como objetos BaseTool de LangChain. Esas tools son exactamente el tipo que ToolNode y bind_tools esperan. El agente LangGraph no sabe si una tool está implementada localmente en Python o en un MCP server remoto: la interfaz es idéntica.
# Code block 4: LangGraph + MCP adapter
# Requiere: uv add langchain-mcp-adapters mcp
import asyncio
from typing import Annotated, TypedDict
from langchain_core.messages import BaseMessage, HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from langchain_mcp_adapters.tools import load_mcp_tools
class State(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
async def build_mcp_agent():
# Conectar al MCP server vía stdio (también soporta HTTP/SSE)
server_params = StdioServerParameters(
command="python",
args=["./mi_mcp_server.py"], # el MCP server que expone las tools
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# Cargar las tools del MCP server como BaseTool de LangChain
mcp_tools = await load_mcp_tools(session)
print(f"Tools disponibles: {[t.name for t in mcp_tools]}")
# Construir el grafo LangGraph con las tools MCP
llm = ChatOpenAI(model="gpt-4o-mini").bind_tools(mcp_tools)
def call_model(state: State) -> dict:
return {"messages": [llm.invoke(state["messages"])]}
tool_node = ToolNode(mcp_tools)
builder = StateGraph(State)
builder.add_node("llm", call_model)
builder.add_node("tools", tool_node)
builder.add_edge(START, "llm")
builder.add_conditional_edges("llm", tools_condition)
builder.add_edge("tools", "llm")
graph = builder.compile()
# Invocar el agente — las tools se ejecutan via MCP protocol
result = await graph.ainvoke({
"messages": [HumanMessage(content="Lista los archivos del proyecto")]
})
return result["messages"][-1].content
if __name__ == "__main__":
print(asyncio.run(build_mcp_agent()))
Transporte HTTP/SSE para MCP en producción
El ejemplo anterior usa transporte stdio, adecuado para MCP servers ejecutados como subproceso en la misma máquina. En producción, los MCP servers suelen desplegarse como servicios independientes accesibles via HTTP con Server-Sent Events (SSE). El paquete mcp soporta ambos transportes con la misma interfaz de ClientSession: cambiar de stdio a HTTP es sustituir stdio_client por sse_client con la URL del servidor. Esto desacopla completamente el ciclo de vida del agente LangGraph del ciclo de vida del MCP server, permitiendo escalar ambos componentes de forma independiente.
Para equipos que están evaluando si LangGraph o el OpenAI Agents SDK se adapta mejor a su caso de uso con MCP, la diferencia clave es que el SDK de OpenAI tiene soporte MCP nativo vía MCPServerStdio y MCPServerSse, mientras que LangGraph lo implementa a través del adaptador de LangChain. El adaptador funciona bien, pero añade una dependencia más. Si el proyecto ya usa LangGraph para la orquestación, el adaptador es la ruta natural. Si el proyecto comienza desde cero con MCP como primer ciudadano, el SDK de OpenAI puede ser más directo.
Gestión del ciclo de vida de la sesión MCP
Un detalle crítico en producción es la gestión del ciclo de vida de la ClientSession MCP. La conexión al MCP server no se puede compartir entre requests concurrentes de forma naïve: la sesión MCP está ligada al contexto asíncrono del async with. Para servidores de alto tráfico, implementar un pool de sesiones o reutilizar una sesión long-lived con reconexión automática. El patrón más robusto es una sesión por thread de conversación LangGraph, lo que se alinea naturalmente con el thread_id del checkpointer: cada conversación tiene su propia sesión MCP, su propio estado persistido y su propio ciclo de vida.
LangGraph vs alternativas
El espacio de frameworks de orquestación de agentes
El ecosistema de frameworks de orquestación de agentes ha crecido rápidamente en 2024-2026. Cada framework hace apuestas arquitecturales distintas sobre qué abstracciones son las correctas. LangGraph apuesta por grafos dirigidos con estado explícito. El OpenAI Agents SDK apuesta por handoffs declarativos entre agentes. CrewAI apuesta por crews de agentes con roles y procesos. AutoGen apuesta por conversaciones entre agentes como primitiva. Cada uno tiene casos de uso donde brilla y donde chirría.
| Framework | Modelo de estado | Persistencia | Human-in-loop | MCP | Mejor para |
|---|---|---|---|---|---|
| LangGraph | TypedDict + reducers | Checkpointers (SQL, Postgres, memory) | Nativo (interrupt_before/after) | Via adaptador | Agentes con estado complejo, flujos cíclicos, producción con auditoría |
| OpenAI Agents SDK | Handoffs + context variables | No nativo (manual) | Via guardrails + hooks | Nativo (MCPServerStdio/Sse) | Orquestación multi-agente con OpenAI, MCP first-class |
| LangChain LCEL | Sin estado propio | No nativo | No nativo | Via adaptador | Pipelines deterministas, ETL, chains simples |
| CrewAI | Crew + Task + Process | Limitado | Via hooks | Parcial | Workflows multi-agente con roles explícitos, equipos |
| AutoGen | Conversación entre agentes | Via plugins | Human proxy agent | Parcial | Investigación, prototipado, agentes que debaten |
LangGraph vs LangChain: distintos niveles de abstracción
La confusión más común es entre LangGraph y LangChain. Son capas distintas del stack: LangChain es el framework de composición de componentes LLM (chains, document loaders, embeddings, vector stores, retrievers). LangGraph es el motor de orquestación que usa esos componentes como nodos. Un grafo LangGraph puede tener nodos que internamente usan chains LCEL de LangChain. Son complementarios, no excluyentes. La recomendación del equipo de LangChain es usar LCEL para lógica sin estado y LangGraph para agentes con estado: la decisión es qué necesitas, no qué framework es «mejor».
Cuándo LangGraph no es la respuesta
LangGraph añade complejidad real: hay que entender TypedDict, reducers, edges condicionales, checkpointers y el modelo de compilación. Para casos de uso simples (un agente que responde preguntas sobre un documento sin memoria), LCEL con un chain de RAG es más apropiado. Para workflows puramente deterministas sin LLM en el loop, Prefect, Airflow o un script Python son más adecuados. La complejidad de LangGraph se amortiza cuando el agente necesita estado persistente, ciclos, branching real o human-in-the-loop. Si ninguna de esas condiciones aplica, no lo uses.
Persistencia avanzada: SqliteSaver y PostgresSaver
Pasar de MemorySaver a producción
El salto de MemorySaver a un checkpointer persistente en producción es trivial en la API pero requiere decisiones de infraestructura. SqliteSaver escribe en un archivo SQLite: perfecto para aplicaciones single-server, scripts de larga duración o demos. No requiere infraestructura adicional. La limitación es concurrencia: SQLite tiene locking a nivel de base de datos, por lo que múltiples threads escribiendo checkpoints simultáneamente pueden generar contención. Para cargas de un solo proceso con múltiples threads, es aceptable.
PostgresSaver (paquete langgraph-checkpoint-postgres) usa PostgreSQL con partitioning por thread_id. Soporta alta concurrencia, tiene un esquema de índices optimizado para las queries de LangGraph (recuperar el último checkpoint de un thread, listar el historial de un thread) y se integra con pgvector si el State incluye embeddings. Para sistemas multi-tenant con cientos o miles de threads simultáneos, PostgresSaver es la elección correcta. La documentación oficial del sistema de persistencia de LangGraph está disponible en LangGraph Persistence Concepts. LangGraph Platform abstrae esta capa si se usa el servicio gestionado.
# Code block 5: SqliteSaver para persistencia en desarrollo/staging
# Mismo grafo del ejemplo ReAct, ahora con persistencia en SQLite
from langgraph.checkpoint.sqlite import SqliteSaver
from langchain_core.messages import HumanMessage
# SqliteSaver con context manager — gestiona la conexión automáticamente
with SqliteSaver.from_conn_string("./agent_checkpoints.db") as checkpointer:
# Compilar el grafo con checkpointer persistente
# graph_builder es el mismo del ejemplo ReAct (code block 2)
graph = graph_builder.compile(checkpointer=checkpointer)
config_thread_1 = {"configurable": {"thread_id": "pedido-2026-001"}}
config_thread_2 = {"configurable": {"thread_id": "pedido-2026-002"}}
# Thread 1: consulta de precio
graph.invoke(
{"messages": [HumanMessage(content="¿Cuánto cuesta un monitor?")]},
config=config_thread_1
)
# Thread 2: diferente conversación, estado independiente
graph.invoke(
{"messages": [HumanMessage(content="¿Cuánto cuesta un teclado?")]},
config=config_thread_2
)
# Thread 1 continúa — el agente recuerda la conversación anterior
response = graph.invoke(
{"messages": [HumanMessage(content="¿Y con un 10% de descuento?")]},
config=config_thread_1
)
print(response["messages"][-1].content)
# Verificar el estado persistido tras reinicio del proceso
# (SqliteSaver lee del archivo .db, el estado no se pierde)
state = graph.get_state(config_thread_1)
print(f"Mensajes almacenados: {len(state.values['messages'])}")
Estrategias de pruning del historial
Un problema práctico con checkpointers persistentes es el crecimiento ilimitado del historial de mensajes. Un agente conversacional que mantiene el historial completo durante semanas puede acumular miles de mensajes, lo que desborda el context window del LLM y hace las llamadas innecesariamente caras. Las estrategias más comunes son: sliding window (mantener solo los últimos N mensajes), summarization (resumir la conversación pasada en un mensaje system antes de los últimos N turnos) y selective retention (guardar solo los mensajes marcados como relevantes por el LLM). LangGraph no implementa ninguna de estas estrategias por defecto: el desarrollador añade un nodo de gestión del historial que se ejecuta tras cada turno y aplica la estrategia elegida. Esto conecta directamente con los principios de context engineering, donde la gestión activa del contexto es tan importante como el contenido en sí.
Monitorización y evaluación de agentes LangGraph
LangGraph se integra nativamente con LangSmith para tracing: cada step del grafo genera un span en LangSmith con el State de entrada, el delta de salida, la latencia y el cost del LLM. Para evaluar la calidad de las decisiones del agente, el patrón más robusto es LLM as judge, donde un modelo evaluador (diferente al modelo del agente) puntúa cada respuesta contra criterios predefinidos. La evaluación sistemática de agentes con LangGraph es un tema que cubrimos en detalle en el post de LLM as judge y evaluación de agentes.
Preguntas frecuentes sobre LangGraph
¿Cuál es la diferencia entre LangGraph y LangChain?
LangChain es un framework de composición de componentes LLM: proporciona abstracciones para modelos, prompts, chains, document loaders, retrievers, vector stores y memory básica. LangGraph es un motor de orquestación de agentes con estado que usa grafos dirigidos potencialmente cíclicos. Ambos son del mismo equipo (LangChain Inc.) y son complementarios: LangGraph puede usar cualquier componente de LangChain como nodo. La distinción clave es que LangChain gestiona qué componentes se usan y LangGraph gestiona en qué orden y bajo qué condiciones. Para agentes con estado, ciclos y human-in-the-loop, usar LangGraph. Para pipelines deterministas sin estado, LCEL de LangChain es suficiente.
¿LangGraph requiere usar OpenAI o funciona con cualquier LLM?
LangGraph no está acoplado a ningún proveedor de LLM. Cualquier modelo que implemente la interfaz BaseChatModel de LangChain funciona: OpenAI, Anthropic Claude, Google Gemini, Mistral, Ollama (modelos locales), AWS Bedrock y cualquier proveedor compatible con LiteLLM. Para tool use, el modelo debe soportar bind_tools y emitir tool_calls en el AIMessage. Todos los modelos líderes del mercado soportan esta capacidad. Para modelos que no soportan tool use nativo, se puede implementar tool calling basado en parseo de output en el nodo LLM, aunque es menos robusto.
¿Qué es un reducer en LangGraph y por qué es importante?
Un reducer es la función que define cómo se fusiona un update parcial de un nodo con el valor actual de una clave del State. El reducer por defecto es la sobreescritura directa (el nuevo valor reemplaza al anterior). El reducer add_messages, específico para listas de mensajes, acumula los nuevos mensajes en lugar de sobreescribir la lista completa. Sin el reducer correcto, un nodo que devuelve un mensaje sobreescribiría todo el historial previo. Los reducers son críticos para garantizar que el State evoluciona de forma correcta y predecible conforme múltiples nodos lo modifican en un mismo grafo.
¿Cómo funciona el checkpointing en producción?
El checkpointing en LangGraph serializa el State completo del grafo tras cada nodo y lo almacena en el backend configurado (memoria, SQLite, PostgreSQL) indexado por thread_id y checkpoint_id. Cuando se invoca el grafo con el mismo thread_id, LangGraph carga el último checkpoint de ese thread antes de ejecutar el primer nodo. En producción, PostgresSaver del paquete langgraph-checkpoint-postgres es el checkpointer recomendado: soporta alta concurrencia, tiene esquema optimizado con índices por thread_id y se puede desplegar en cualquier instancia PostgreSQL estándar. El overhead de serialización es bajo (milisegundos) para states de tamaño típico.
¿Puede LangGraph gestionar agentes en paralelo?
Sí. LangGraph soporta dos tipos de paralelismo. El primero es el paralelismo intra-grafo usando la primitiva Send: un edge condicional puede devolver múltiples objetos Send, activando varios nodos en paralelo y fusionando sus resultados en el State antes de continuar. El segundo es el paralelismo multi-thread: al usar checkpointers thread-safe (PostgresSaver), múltiples threads de Python pueden ejecutar grafos independientes con distintos thread_id simultáneamente. Para arquitecturas multi-agente donde el orquestador distribuye tareas en paralelo a agentes especializados, LangGraph Platform gestiona el scheduling y la concurrencia automáticamente.
¿Qué diferencia hay entre LangGraph y el patrón de IA agéntica puro?
La IA agéntica es un concepto arquitectural: sistemas donde el LLM tiene autonomía para planificar, ejecutar acciones y adaptar su comportamiento en función de los resultados. LangGraph es una implementación específica de ese concepto usando grafos con estado. Otros frameworks (OpenAI Agents SDK, AutoGen, CrewAI) implementan el mismo concepto con abstracciones distintas. La ventaja de LangGraph respecto al patrón agéntico implementado a mano es que gestiona automáticamente la persistencia, los ciclos, el human-in-the-loop y el tracing, eliminando código de plumbing que de otro modo habría que escribir y mantener. La desventaja es la curva de aprendizaje inicial del modelo de StateGraph.
Conclusión
LangGraph resuelve el problema correcto: cómo construir agentes que tengan estado persistente, tomen decisiones condicionales, soporten supervisión humana y sean auditables en producción. El modelo de StateGraph con TypedDict, reducers, nodos puros y edges condicionales es más verboso que una chain LCEL, pero esa verbosidad es deliberada: hace explícito lo que en otros frameworks está implícito y por tanto oculto. Un grafo LangGraph bien diseñado es un documento ejecutable de la lógica del agente.
Los cinco bloques de código de este post cubren el 80% de los casos de uso reales: agente ReAct básico, persistencia con MemorySaver, persistencia con SqliteSaver y continuación de conversaciones, e integración con MCP para tools externas. El 20% restante — subgrafos, paralelismo con Send, time-travel, LangGraph Platform — están documentados en la documentación oficial de LangGraph y en el repositorio GitHub de LangGraph con notebooks ejecutables.
Si estás evaluando LangGraph para un caso de uso concreto en tu organización, en Baigency diseñamos y desplegamos agentes de IA para empresas con LangGraph, MCP y los frameworks más adecuados a cada vertical. El primer paso es siempre entender el flujo de trabajo real antes de elegir la arquitectura técnica. Si quieres explorar cómo aplica este stack a tu caso específico, contacta con el equipo de agentes de IA de Baigency , sin compromiso.



