Cómo construir un agente con LangGraph Python: tutorial para producción
Tutorial paso a paso para construir un agente LangGraph con estado persistente en PostgreSQL y revisión humana integrada. Al final tendrás un agente listo para llevar a producción.
En Cognitiva, agencia chilena de IA, usamos LangGraph como base para los agentes que desplegamos en empresas B2B. A diferencia de un chatbot sin memoria, un agente LangGraph mantiene estado entre conversaciones, persiste cada decisión en PostgreSQL y puede pausar su ejecución para esperar la aprobación de un humano antes de continuar.\n\nEste tutorial te lleva desde cero hasta tener un agente de análisis de documentos operativo en producción. Construirás un grafo con tres nodos, checkpointing real en PostgreSQL —no en memoria— y un punto de interrupción human-in-the-loop que escala al equipo cuando el agente detecta baja confianza. Es el mismo stack que usan Klarna, Replit y Elastic para sus sistemas de IA en producción.\n\nAl final tendrás un agente listo para llevar a producción que recuerda conversaciones entre sesiones y puede reanudar flujos interrumpidos sin perder estado. El tiempo depende de tu entorno; si ya tienes Python 3.10 o superior instalado y acceso a una base de datos PostgreSQL, puedes completarlo en una sesión de trabajo según alcance. No necesitas experiencia previa con LangGraph.
Prerrequisitos y arquitectura del agente
LangGraph es el framework de orquestación de agentes de código abierto desarrollado por LangChain AI, publicado bajo licencia MIT. A diferencia de cadenas simples, un grafo LangGraph puede bifurcarse, retroceder a nodos anteriores y mantener estado persistente entre ejecuciones. Esto lo hace adecuado para flujos de trabajo largos, agentes de múltiples pasos y sistemas donde la intervención humana es parte del proceso. La versión 1.2.7, lanzada el 30 de junio de 2026, es la base de este tutorial.
El agente que construirás sigue el patrón de StateGraph: un grafo dirigido donde cada nodo es una función Python que recibe el estado actual y devuelve una actualización parcial. Las aristas condicionales enrutan el flujo según el valor del estado tras cada nodo. Los tres nodos del agente son: analizar_documento (invoca al LLM con el contenido), revision_humana (pausa y espera aprobación) y finalizar (cierra el flujo). La función enrutar actúa como función de ruteo en add_conditional_edges, pero no se registra como nodo del grafo. El checkpointer de PostgreSQL guarda el estado completo tras cada nodo, lo que permite reanudar el agente desde cualquier punto sin perder información.
- Python 3.10 o superior (LangGraph requiere Python >=3.10)
- PostgreSQL local o en la nube (Supabase, Neon o Railway funcionan)
- Una clave de API de Anthropic (obtenla en console.anthropic.com)
- pip o uv instalado en tu entorno
Instalación y configuración del entorno
Crea un entorno virtual y actívalo antes de instalar las dependencias. Esto evita conflictos entre versiones de distintos proyectos.
python -m venv .venv
source .venv/bin/activate
pip install langgraph==1.2.7 langgraph-checkpoint-postgres==3.1.0 langchain-anthropic psycopg[binary] python-dotenvCrea un archivo .env en la raíz del proyecto con las variables de entorno. Nunca escribas las credenciales directamente en el código fuente.
DATABASE_URL=postgresql://usuario:clave@localhost:5432/agente_db
ANTHROPIC_API_KEY=tu-clave-de-api-aquiVerifica que LangGraph quedó bien instalado antes de continuar.
import langgraph
print(langgraph.__version__) # Debe mostrar 1.2.7El grafo con estado y checkpointing en PostgreSQL
1. Define el estado del agente
El estado es el único dato que viaja entre nodos y que el checkpointer persiste en PostgreSQL tras cada paso. Usa TypedDict para que LangGraph pueda serializarlo. El campo messages usa el reductor add_messages de LangGraph: en vez de reemplazar la lista completa, agrega los mensajes nuevos al historial existente, lo que implementa la memoria de conversación de forma automática. Mantén el estado mínimo: cada campo se escribe en la base de datos en cada checkpoint.
# state.py
from typing import Annotated, Literal
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
documento: str
confianza: float
revision_requerida: bool
estado: Literal["procesando", "esperando_revision", "listo", "error"]2. Conecta PostgreSQL como checkpointer
Reemplaza InMemorySaver por PostgresSaver para tener persistencia real. La primera vez que ejecutes setup(), el checkpointer crea las tablas necesarias en tu base de datos. Los parámetros autocommit=True y row_factory=dict_row son obligatorios: sin ellos, el checkpointer lanza errores de tipo en tiempo de ejecución.
# checkpointer.py
import os
from dotenv import load_dotenv
from psycopg import Connection
from psycopg.rows import dict_row
from langgraph.checkpoint.postgres import PostgresSaver
load_dotenv()
conn = Connection.connect(
os.environ["DATABASE_URL"],
autocommit=True,
row_factory=dict_row
)
checkpointer = PostgresSaver(conn)
checkpointer.setup() # Crea las tablas en la primera ejecucion3. Define los nodos del agente
Cada nodo es una función Python que recibe el estado completo y devuelve un diccionario con los campos que actualiza. El nodo analizar_documento llama al LLM con el documento y extrae un nivel de confianza del texto de respuesta. Si la confianza está bajo el umbral definido, el nodo de enrutamiento deriva al flujo de revisión humana. En producción, usa salida estructurada del LLM para extraer la confianza con mayor fiabilidad que el parseo de texto.
# nodes.py
import os
from dotenv import load_dotenv
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, SystemMessage
from state import AgentState
load_dotenv()
# Actualiza el nombre del modelo con la version mas reciente disponible
MODELO = "claude-3-5-sonnet-20241022"
UMBRAL_CONFIANZA = 0.7
llm = ChatAnthropic(
model=MODELO,
api_key=os.environ["ANTHROPIC_API_KEY"]
)
def analizar_documento(state: AgentState) -> dict:
sistema = SystemMessage(
content="Analiza el documento. Al final escribe CONFIANZA: seguido de un numero del 0 al 1."
)
respuesta = llm.invoke([sistema, HumanMessage(content=state["documento"])])
texto = respuesta.content
# En produccion, usa salida estructurada del LLM
confianza = float(texto.split("CONFIANZA:")[-1].strip().split()[0])
return {
"messages": [respuesta],
"confianza": confianza,
"revision_requerida": confianza < UMBRAL_CONFIANZA,
"estado": "procesando"
}
def enrutar(state: AgentState) -> str:
return "revision_humana" if state["revision_requerida"] else "finalizar"
def finalizar(state: AgentState) -> dict:
return {"estado": "listo"}Human-in-the-loop: pausar y reanudar flujos
Human-in-the-loop (HITL) es el patrón que incluye a una persona en el ciclo de decisión del agente. En LangGraph se implementa con la función interrupt(), que suspende la ejecución, guarda el estado en el checkpointer y devuelve un payload al sistema externo. El agente permanece en espera indefinidamente hasta que recibe una respuesta, que se envía como Command(resume=valor). Esta pausa es durable: si el proceso se reinicia, el agente retoma exactamente desde donde se detuvo gracias al checkpoint en PostgreSQL.
El nodo de revisión humana
El nodo revision_humana llama a interrupt() con un diccionario que contiene el contexto necesario para el revisor: el mensaje descriptivo, el nivel de confianza y el resumen del análisis. El valor de retorno de interrupt() —la decisión del revisor— llega cuando alguien envía Command(resume=decision) desde tu API, tu interfaz web o tu bot de mensajería.
# revision.py
from langgraph.types import interrupt
from state import AgentState
def revision_humana(state: AgentState) -> dict:
decision = interrupt({
"mensaje": "El agente detecto baja confianza. Revisa el analisis y aprueba o rechaza.",
"confianza": state["confianza"],
"resumen": state["messages"][-1].content
})
return {
"revision_requerida": False,
"estado": "listo" if decision else "error"
}Armar el grafo completo
# graph.py
from langgraph.graph import StateGraph, START, END
from state import AgentState
from nodes import analizar_documento, enrutar, finalizar
from revision import revision_humana
from checkpointer import checkpointer
builder = StateGraph(AgentState)
builder.add_node("analizar", analizar_documento)
builder.add_node("revision_humana", revision_humana)
builder.add_node("finalizar", finalizar)
builder.add_edge(START, "analizar")
builder.add_conditional_edges("analizar", enrutar)
builder.add_edge("revision_humana", "finalizar")
builder.add_edge("finalizar", END)
grafo = builder.compile(checkpointer=checkpointer)Ejecutar y reanudar
Invoca el agente con un thread_id único por sesión. El thread_id es el identificador que LangGraph usa para recuperar el checkpoint correcto de PostgreSQL en cada invocación. Si usas un thread_id diferente al de la sesión original, el agente empieza desde cero sin historial. En producción, el thread_id suele ser el ID del usuario, del caso o de la transacción. Cuando el grafo llega a interrupt(), el stream emite el payload y se detiene. Para reanudar, pasa Command(resume=decision) en la siguiente llamada con el mismo thread_id.
# run.py
from langgraph.types import Command
from graph import grafo
config = {"configurable": {"thread_id": "sesion-empresa-001"}}
input_inicial = {
"documento": "Informe financiero Q1: ingresos 12M, margen 23%, deuda 4M.",
"messages": [],
"confianza": 0.0,
"revision_requerida": False,
"estado": "procesando"
}
print("--- Primera ejecucion ---")
for evento in grafo.stream(input_inicial, config=config):
print(evento)
# Si el agente quedo suspendido, el revisor envia su decision:
print("--- Reanudando tras aprobacion del revisor ---")
for evento in grafo.stream(Command(resume=True), config=config):
print(evento)Verificación y checklist de producción
Antes de llevar el agente a producción, verifica que el flujo completo funciona: que el agente analiza el documento, detecta baja confianza, se interrumpe correctamente y reanuda cuando el revisor aprueba. La siguiente prueba usa InMemorySaver para no depender de una base de datos externa.
# test_agente.py
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command
from langgraph.graph import StateGraph, START, END
from nodes import analizar_documento, enrutar, finalizar
from revision import revision_humana
from state import AgentState
def construir_grafo(checkpointer):
builder = StateGraph(AgentState)
builder.add_node("analizar", analizar_documento)
builder.add_node("revision_humana", revision_humana)
builder.add_node("finalizar", finalizar)
builder.add_edge(START, "analizar")
builder.add_conditional_edges("analizar", enrutar)
builder.add_edge("revision_humana", "finalizar")
builder.add_edge("finalizar", END)
return builder.compile(checkpointer=checkpointer)
def test_flujo_revision():
grafo = construir_grafo(InMemorySaver())
config = {"configurable": {"thread_id": "test-001"}}
input_prueba = {
"documento": "Texto ambiguo sin datos concretos.",
"messages": [],
"confianza": 0.0,
"revision_requerida": False,
"estado": "procesando"
}
for evento in grafo.stream(input_prueba, config=config):
print("Evento:", evento)
# El revisor aprueba: Command(resume=True) reanuda el flujo
for evento in grafo.stream(Command(resume=True), config=config):
print("Reanudado:", evento)Checklist de producción LangGraph (Cognitiva)
- PostgresSaver activo: nunca uses InMemorySaver en producción. El estado debe persistir entre reinicios del proceso.
- recursion_limit configurado: incluye recursion_limit con valor 25 en la config de invocación para evitar bucles infinitos.
- Variables de entorno sin valores escritos en el código: DATABASE_URL y ANTHROPIC_API_KEY deben venir de .env o del sistema, nunca escritas en el código fuente.
- Al menos un nodo interrupt() para cada acción con efectos irreversibles: envío de correos, aprobación de pagos, modificación de registros externos.
- Prueba de reanudación validada: confirma que Command(resume=valor) recupera el estado correcto tras un reinicio del proceso antes de pasar a producción.
Una vez que el checklist está verde, empaqueta el agente en un contenedor Docker con tu API FastAPI, apunta DATABASE_URL a tu PostgreSQL en la nube y despliega en la plataforma de tu elección. El agente escala horizontalmente porque el estado vive en la base de datos, no en la memoria del proceso. Cada instancia adicional puede atender un thread_id distinto sin interferir con las otras sesiones activas.
Preguntas frecuentes
Da el siguiente paso
Hablemos
Cuéntanos sobre tu negocio. Respondemos en 24 horas.
Otros canales
Escríbenos por WhatsApp o agenda una llamada de 30 minutos.