Desarrollo IATutorial

LangGraph en 2026: Un Agente de Soporte Que Sabe Cuándo Preguntar

22 de agosto de 2026
12 min de lectura
Grafo de agente de soporte en LangGraph con paso de aprobación humana
Compartir:

La mayoría de los tutoriales de agentes de IA terminan donde empieza producción: la demo responde una pregunta y olvida todo. Un agente de soporte real necesita memoria entre turnos, herramientas que tocan sistemas reales y — lo crítico — el criterio para frenar y pedirle a un humano antes de devolver dinero.

En este tutorial vas a construir exactamente eso con LangGraph: un agente de soporte con grafo de estado tipado, llamadas a herramientas, memoria con checkpointer, un paso de aprobación humana y tracing en LangSmith. Cada API está verificada contra la documentación oficial actual.

1. El Problema: Las Demos Olvidan, los Reembolsos No

Un chatbot de soporte sobre una simple llamada al LLM tiene tres fallas fatales. No tiene memoria entre mensajes, así que cada turno empieza de cero. No puede actuar, así que solo habla de reembolsos en vez de procesarlos. Y no sabe escalar, así que la decisión riesgosa — devolver dinero — ocurre sin supervisión o se bloquea por completo.

El Error Más Común

Darle a un agente una herramienta de reembolso sin compuerta de aprobación. Vi este patrón en proyectos de rescate más de una vez: el agente funciona bárbaro en la demo y en la primera semana devuelve un reembolso real a la orden equivocada. Autonomía sin botón de pausa es un riesgo, no una feature.

LangGraph lo resuelve con un grafo explícito: los nodos hacen el trabajo, las aristas deciden qué sigue, un checkpointer recuerda el estado por hilo de conversación y un interrupt pausa la ejecución hasta que un humano aprueba. Esa es la arquitectura que vamos a construir.

2. Conceptos Mínimos: Estado, Nodos, Aristas, Checkpoints

LangGraph es un framework de orquestación de bajo nivel: definís un agente como un grafo en vez de una cadena. Cuatro ideas explican el 90% del framework. Aprendelas y el tutorial se va a leer como Python común.

🗂️

StateGraph + TypedDict

langgraph.graph · la memoria compartida

Un único objeto de estado tipado fluye por cada nodo. Las listas de mensajes usan un reductor (operator.add o add_messages) para acumular en vez de sobrescribir. Los nodos devuelven actualizaciones parciales, nunca todo el estado.

🔧

ToolNode

langgraph.prebuilt · las manos

Un nodo prearmado que ejecuta las llamadas a herramientas del modelo y devuelve ToolMessages. Definís herramientas con el decorador @tool y las conectás con model.bind_tools(tools). Sin ejecutor custom.

💾

Checkpointer + thread_id

persistencia · la memoria

Compilá con checkpointer=InMemorySaver() en local (PostgresSaver o SqliteSaver en producción) y pasá config={“configurable”: {“thread_id”: …}} para que cada conversación retome su propio estado.

✋

interrupt + Command(resume=…)

langgraph.types · el botón de pausa

Llamá interrupt(payload) dentro de un nodo para congelar la ejecución y mostrar una pregunta. Retomá con graph.invoke(Command(resume=respuesta), config). El nodo se re-ejecuta desde su inicio: el código previo al interrupt debe ser idempotente.

🔀

Aristas condicionales

ruteo · el criterio

add_conditional_edges rutea según el estado: si el último mensaje tiene tool_calls va al nodo de herramientas, si no a END. Esta función mínima es todo el loop ReAct: el modelo actúa, las herramientas responden, el modelo continúa.

🔭

Tracing con LangSmith

observabilidad · la caja negra

Con LANGSMITH_TRACING=true cada nodo, herramienta y token queda registrado como trace. Cuando tu agente falla en producción, leés el trace de arriba a abajo en vez de adivinar.

Lo Que Vamos a Construir

Un agente de soporte con 4 nodos: agent (el LLM), tools (búsqueda de órdenes + reembolso), refund_approval (compuerta humana con interrupt) y END. Memoria con InMemorySaver, ruteo con should_continue y tracing completo en LangSmith.

3. Pasos 1–2: Herramientas y Estado

Instalá los paquetes, definí dos herramientas de soporte y atalas al modelo. La herramienta de reembolso no se ejecuta directo — la va a controlar el nodo de aprobación. El estado es un TypedDict con lista de mensajes acumulativa más un campo de dominio.

pip install langgraph langchain-anthropic
export ANTHROPIC_API_KEY="sk-..."
export LANGSMITH_TRACING="true"   # caja negra activada

from langchain.tools import tool
from langchain.chat_models import init_chat_model

model = init_chat_model("claude-sonnet-4-6", temperature=0)

@tool
def lookup_order(order_id: str) -> str:
    """Look up an order by ID. Returns status and total."""
    return f"Order {order_id}: shipped, total $84.20"

@tool
def issue_refund(order_id: str, amount: float) -> str:
    """Issue a refund. Only runs after human approval."""
    return f"Refunded ${amount:.2f} to order {order_id}"

tools = [lookup_order, issue_refund]
model_with_tools = model.bind_tools(tools)

from typing_extensions import TypedDict, Annotated
from langchain_core.messages import AnyMessage
import operator

class SupportState(TypedDict):
    messages: Annotated[list[AnyMessage], operator.add]
    customer_tier: str

Por Qué operator.add Importa

Sin el reductor Annotated, cada nodo sobrescribiría el historial de mensajes. Con operator.add, cada nodo acumula y la conversación completa sobrevive todo el run — incluso a través de interrupts.

4. Pasos 3–4: Nodos, Aristas y el Grafo

Dos nodos (agent + ToolNode prearmado), una función de ruteo, cuatro aristas. El nodo agent llama al modelo atado a herramientas; should_continue manda las llamadas a ejecución y las respuestas planas a END. Compilá primero sin checkpointer para probar el loop, y sumá memoria en el paso siguiente.

from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from typing import Literal

def agent_node(state: SupportState):
    reply = model_with_tools.invoke(state["messages"])
    return {"messages": [reply]}

def should_continue(state: SupportState) -> Literal["tools", END]:
    if state["messages"][-1].tool_calls:
        return "tools"
    return END

builder = StateGraph(SupportState)
builder.add_node("agent", agent_node)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", should_continue, ["tools", END])
builder.add_edge("tools", "agent")

graph = builder.compile()  # la memoria viene después

from langchain_core.messages import HumanMessage
out = graph.invoke({
    "messages": [HumanMessage(content="Where is order 8821?")],
    "customer_tier": "pro",
})
print(out["messages"][-1].content)

Probá Antes de Agregar Memoria

Corré primero el grafo sin memoria con una pregunta de consulta. Si el loop ReAct funciona acá, cualquier bug posterior vive en checkpointing o interrupts — acabás de partir tu superficie de debugging a la mitad.

5. Pasos 5–6: Memoria, Compuerta de Aprobación y Tracing

Recompilá con checkpointer, insertá el nodo refund_approval entre herramientas y ejecución, y retomá con Command. Reembolsos de más de $50 frenan para un humano; el resto fluye directo. LangSmith registra ambas mitades del run pausado automáticamente.

from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt, Command

def refund_approval(state: SupportState):
    last = state["messages"][-1]
    calls = [c for c in last.tool_calls if c["name"] == "issue_refund"]
    if not calls or calls[0]["args"].get("amount", 0) <= 50:
        return {"messages": []}  # montos chicos pasan directo
    ok = interrupt({  # pausa acá, el payload lo ve el revisor
        "question": "Approve this refund?",
        "order": calls[0]["args"],
        "tier": state["customer_tier"],
    })
    if not ok:
        return {"messages": [HumanMessage(content="Refund declined by reviewer.")]}
    return {"messages": []}

builder.add_node("refund_approval", refund_approval)
builder.add_edge("tools", "refund_approval")
# rewire: tools -> approval -> agent (rebuild + recompile)
checkpointer = InMemorySaver()  # SqliteSaver / PostgresSaver en prod
graph = builder.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "support-123"}}
first = graph.invoke(
    {"messages": [HumanMessage(content="Refund $340 to order 8821")],
     "customer_tier": "pro"},
    config=config,
)
print(first.get("__interrupt__"))  # pausado, esperando un humano
final = graph.invoke(Command(resume=True), config=config)  # aprobar
print(final["messages"][-1].content)

Checklist de Producción

Cambiá InMemorySaver por PostgresSaver (sobrevive reinicios), mantené los thread_id bajo 255 caracteres, nunca envuelvas interrupt() en un try/except genérico y hacé los reembolsos idempotentes — el nodo de aprobación se re-ejecuta desde su inicio al retomar.

6. Errores Comunes (y el Fix de Una Línea para Cada Uno)

Estas cuatro fallas explican la mayoría de los hilos de soporte de LangGraph que vi. Cada una tiene una causa precisa y un fix de una línea — memorizalos antes de deployar.

🧠

El grafo olvida todo en el segundo turno

Causa: compilaste sin checkpointer o invocás sin thread_id. Fix: compilá con checkpointer=… y pasá siempre config={“configurable”: {“thread_id”: …}} — el mismo thread retoma, un thread nuevo empieza de cero.

🔁

Loop infinito entre agente y herramientas

Causa: should_continue nunca devuelve END — típico cuando una herramienta falla y el modelo reintenta para siempre. Fix: poné recursion_limit en el invoke y logueá los errores como ToolMessages para que el modelo vea la falla.

💥

interrupt() nunca pausa — el grafo lo atraviesa

Causa: interrupt() envuelto en un try/except genérico que traga su excepción de control, o falta el checkpointer. Fix: dejá interrupt() fuera de los try/except y compilá con checkpointer.

📦

Reembolsos duplicados después de aprobar

Causa: un efecto no idempotente antes de interrupt() se re-ejecuta al retomar. Fix: mové las escrituras después del interrupt, o hacelas idempotentes (upsert con clave de idempotencia, nunca insert ciego).

7. Cuándo LangGraph Rinde — y Cuándo No

LangGraph es overhead para prompts de un solo tiro y un superpoder para flujos con estado. Usá esta regla para decidir antes de commitear un grafo nuevo a tu codebase.

✅ Usá LangGraph

  • • Soporte, onboarding u ops multi-turno con memoria
  • • Herramientas con side effects que piden aprobación o auditoría
  • • Runs largos que deben sobrevivir reinicios, timeouts y handoffs
  • • Debugging con traces de LangSmith en vez de prints
  • • Ruteo entre especialistas (triage → billing → técnico)

❌ Salteá el grafo

  • • Una pregunta, una respuesta — llamá al modelo directo
  • • Pipelines estáticos sin branching ni reintentos
  • • Prototipos donde una abstracción simple shippea más rápido
  • • Estados con una docena de campos ad-hoc que nadie documenta

Regla de oro

Si la tarea necesita memoria, branching o un botón de pausa humano, modelala como grafo. Si no necesita ninguno de los tres, el grafo es ceremonia. Esto lo reviso con cada equipo antes de agregar el primer nodo.

Fuentes

Cada API de este tutorial sale de la documentación oficial y guías actuales de la comunidad. Verificá antes de deployar — los frameworks se mueven rápido.

Conclusión

Ya tenés un agente de soporte que recuerda la conversación, consulta órdenes y se niega a mover dinero sin que un humano diga que sí. Ese loop — estado, herramientas, checkpoint, interrupt, trace — es el template detrás de la mayoría de los agentes productivos que construyo o rescato.

Próximo paso: apuntá el mismo grafo a tus propias herramientas (lookup en Zendesk, reembolso en Stripe en modo test), cambiá InMemorySaver por PostgresSaver y mirá los primeros diez traces en LangSmith. Los traces te van a enseñar más que cualquier tutorial — incluido este.

Lo Que Construiste: Resumen

Grafo

  • • StateGraph + ToolNode
  • • Router should_continue
  • • Compuerta refund_approval

Memoria & Seguridad

  • • InMemorySaver → Postgres
  • • thread_id por conversación
  • • interrupt + resume

Observabilidad

  • • LANGSMITH_TRACING=true
  • • Trace de cada herramienta
  • • Auditoría de aprobaciones
Diego Rodriguez

Diego Rodriguez

Ingeniero Senior Full-Stack & AI

Diego tiene mas de 9 anos de experiencia construyendo aplicaciones potenciadas por IA de produccion, desde orquestacion de LLMs y pipelines RAG hasta deteccion de riesgos con ML y sistemas de trading algoritmico.

Conoce mas sobre Diego →