LangGraph + LangSmith: crear y monitorizar sistemas multiagente en producción (2026)

Surya Pratap
By Surya Pratap

28 de marzo de 2026

14 min de lectura

Orquestación de agentes · LangGraph · LangSmith
Arquitectura de orquestación multiagente con LangGraph y LangSmith: nodos, grafo de estado y vista de trazas

Casi todos los equipos que construyen agentes de IA chocan con el mismo muro. La demo funciona. El prototipo impresiona a los responsables. Y entonces llega producción, y todo se vuelve opaco. Los agentes entran en bucle sin motivo aparente. Las llamadas a herramientas fallan en silencio. Los costes se disparan. Nadie sabe por qué ha fallado una ejecución, porque no hay forma de ver qué ocurrió por dentro.

Esta guía resuelve los dos problemas. LangGraph aporta una orquestación multiagente con estado y controlable, que no se desmorona al crecer. LangSmith aporta la capa de observabilidad para trazar cada ejecución, evaluar cada salida y depurar cada fallo, tanto en desarrollo como en producción. Juntos son el stack de producción para sistemas de agentes serios.

1. Por qué las cadenas de LangChain no bastan para orquestar de verdad

Las cadenas LCEL (LangChain Expression Language) son excelentes para pipelines lineales: entrada → prompt → LLM → salida. Pero los sistemas de agentes reales no son lineales. Necesitan:

  • Bucles: un agente que reintenta hasta que se cumple una condición
  • Ramificación: enrutamiento condicional según la salida del agente
  • Memoria persistente: estado que sobrevive entre pasos y llamadas
  • Aprobación humana: puntos de control donde una persona puede intervenir
  • Ejecución en paralelo: varios agentes funcionando a la vez
  • Streaming: salida token a token mientras los agentes trabajan

LangGraph se creó justo para esto. Modela el flujo de tus agentes como un grafo dirigido en el que los nodos son funciones (agentes, herramientas, comprobaciones condicionales) y las aristas definen el flujo de ejecución. El estado es un diccionario de Python tipado que se pasa y se actualiza en cada paso. El checkpointer persiste ese estado, así que obtienes memoria, capacidad de reintento y puntos de interrupción humana sin esfuerzo adicional.

bashInstalación
pip install langgraph langsmith langchain-openai langchain-anthropic

# Set environment variables
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_API_KEY=ls__your_api_key_here
export LANGCHAIN_PROJECT=production-agents
export OPENAI_API_KEY=sk-your-key

2. Conceptos básicos de LangGraph: estado, nodos, aristas y checkpointers

Antes de escribir un solo agente, entiende las cuatro primitivas sobre las que se construye todo lo demás.

📦

Estado

Un TypedDict que contiene todos los datos que circulan por el grafo. Cada nodo lee y escribe en el estado. Es la única fuente de verdad.

Nodos

Funciones de Python que reciben el estado y devuelven una actualización parcial. Un nodo puede ser una llamada a un LLM, la ejecución de una herramienta, una comprobación condicional: lo que necesites.

Aristas

Conexiones entre nodos. Pueden ser fijas (ir siempre al siguiente nodo) o condicionales (enrutar según los valores del estado, con una simple función de Python como lógica de enrutamiento).

💾

Checkpointers

Persisten el estado entre pasos. SqliteSaver para desarrollo, RedisSaver o PostgresSaver para producción. Habilitan la memoria, los reintentos y la aprobación humana.

Diagrama de StateGraph de LangGraph: nodos planificador, ejecutor y replanificador con aristas condicionales y checkpointer

El patrón planificar y ejecutar: el planificador descompone el objetivo, el ejecutor recorre los pasos, un enrutador condicional decide si replanificar o terminar, y el replanificador corrige si hace falta.

3. Crear un sistema multiagente de atención al cliente con LangGraph

Este es un agente de atención al cliente listo para producción que clasifica tickets, consulta una base de conocimiento RAG, resuelve de forma autónoma o escala a una persona, usando todas las capacidades de LangGraph.

pythonstate.py — definir el estado compartido
from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage
import operator

class SupportAgentState(TypedDict):
    # All messages in the conversation
    messages: Annotated[Sequence[BaseMessage], operator.add]
    # Classified ticket category
    category: str
    # Retrieved knowledge base docs
    kb_results: list[dict]
    # Generated resolution
    resolution: str | None
    # Whether ticket needs escalation
    needs_escalation: bool
    # Escalation reason if applicable
    escalation_reason: str | None
    # Final response sent to customer
    final_response: str | None
    # Confidence score (0-1)
    confidence: float
pythonnodes.py — funciones de los nodos agente
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.prompts import ChatPromptTemplate

llm = ChatOpenAI(model="gpt-4o", temperature=0)

# Node 1: Classify the incoming ticket
def classifier_node(state: SupportAgentState) -> dict:
    prompt = ChatPromptTemplate.from_messages([
        ("system", """Classify this support ticket into one of:
        billing, technical, account, shipping, general
        Also assess confidence (0-1) that you can auto-resolve it.
        Return JSON: {{"category": "...", "confidence": 0.0}}"""),
        ("human", "{ticket}"),
    ])
    chain = prompt | llm
    ticket_text = state["messages"][-1].content
    result = chain.invoke({"ticket": ticket_text})
    parsed = parse_json(result.content)
    return {
        "category": parsed["category"],
        "confidence": parsed["confidence"],
    }

# Node 2: Query the RAG knowledge base
def rag_node(state: SupportAgentState) -> dict:
    query = state["messages"][-1].content
    # Your vector store query here (Pinecone, pgvector, etc.)
    results = vector_store.similarity_search(
        query, k=5, filter={"category": state["category"]}
    )
    return {"kb_results": [r.page_content for r in results]}

# Node 3: Generate resolution using retrieved context
def resolver_node(state: SupportAgentState) -> dict:
    context = "\n\n".join(state["kb_results"])
    prompt = ChatPromptTemplate.from_messages([
        ("system", f"""You are a support agent. Use this context to
        resolve the ticket. If you cannot resolve with high confidence,
        return null for resolution.\n\nContext:\n{context}"""),
        ("human", "{ticket}"),
    ])
    chain = prompt | llm
    result = chain.invoke({"ticket": state["messages"][-1].content})
    return {"resolution": result.content}

# Node 4: Human escalation node
def escalation_node(state: SupportAgentState) -> dict:
    return {
        "needs_escalation": True,
        "escalation_reason": (
            f"Low confidence ({state['confidence']:.0%}) — "
            f"routed to human agent"
        ),
        "final_response": (
            "We've escalated your ticket to a specialist "
            "who will follow up within 2 business hours."
        ),
    }

# Node 5: Format and send final response
def responder_node(state: SupportAgentState) -> dict:
    return {
        "final_response": state["resolution"],
        "messages": [AIMessage(content=state["resolution"])],
    }
pythongraph.py — montar y compilar el grafo
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.sqlite import SqliteSaver

# Conditional routing function
def route_after_classification(state: SupportAgentState) -> str:
    """Route based on category and confidence score."""
    if state["confidence"] < 0.7:
        return "escalate"  # Low confidence → human
    if state["category"] == "billing":
        return "escalate"  # Always escalate billing
    return "rag"           # High confidence → try auto-resolve

def route_after_resolution(state: SupportAgentState) -> str:
    """Check if resolution is good enough to send."""
    if state["resolution"] is None:
        return "escalate"
    if len(state["resolution"]) < 50:
        return "escalate"  # Too short, probably incomplete
    return "respond"

# Build the graph
builder = StateGraph(SupportAgentState)

# Add all nodes
builder.add_node("classifier", classifier_node)
builder.add_node("rag", rag_node)
builder.add_node("resolver", resolver_node)
builder.add_node("escalation", escalation_node)
builder.add_node("responder", responder_node)

# Add edges
builder.add_edge(START, "classifier")
builder.add_conditional_edges(
    "classifier",
    route_after_classification,
    {
        "rag": "rag",
        "escalate": "escalation",
    },
)
builder.add_edge("rag", "resolver")
builder.add_conditional_edges(
    "resolver",
    route_after_resolution,
    {
        "respond": "responder",
        "escalate": "escalation",
    },
)
builder.add_edge("responder", END)
builder.add_edge("escalation", END)

# Compile with checkpointer for persistent memory
memory = SqliteSaver.from_conn_string(":memory:")  # Use PostgresSaver in prod
graph = builder.compile(
    checkpointer=memory,
    interrupt_before=["escalation"],  # Human-in-the-loop checkpoint
)

# Run the graph
config = {"configurable": {"thread_id": "ticket-48291"}}
result = graph.invoke(
    {"messages": [HumanMessage(content="My invoice is wrong")]},
    config=config,
)
print(result["final_response"])

🔑 La potencia de thread_id

Cada invocación con el mismo thread_id retoma la ejecución donde la dejó la anterior. El checkpointer se encarga de ello automáticamente. Así consigues memoria entre turnos, tareas de larga duración y la posibilidad de pausar y reanudar, sin escribir ni una línea de gestión de sesiones.

4. Dónde se rompen los agentes, y por qué necesitas LangSmith

Los sistemas multiagente tienen un modo de fallo que no existe en el software tradicional: pueden parecer que funcionan mientras producen salidas sutilmente incorrectas. El código se ejecuta. No salta ninguna excepción. Pero el agente clasificó mal el ticket, recuperó contexto irrelevante y envió al cliente una respuesta equivocada con tono seguro. Sin observabilidad, nunca te enteras.

LangSmith captura una traza completa de cada ejecución: cada llamada al LLM, cada ejecución de herramienta, cada transición de estado y cada token consumido. Define dos variables de entorno y empieza a funcionar automáticamente con LangGraph, sin código de instrumentación:

bashConfigurar el trazado automático de LangSmith
# These two variables are all you need to start tracing
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_API_KEY=ls__your_api_key

# Optional but recommended
export LANGCHAIN_PROJECT=customer-support-prod
export LANGCHAIN_ENDPOINT=https://api.smith.langchain.com

# That's it. Your LangGraph runs are now fully traced.
# No code changes required.
Panel de LangSmith con trazas de agentes, uso de tokens, desglose de latencia y puntuaciones de evaluación

LangSmith traza cada nodo, cada llamada a herramienta y cada invocación del LLM, con recuento de tokens, latencia, coste y valoraciones humanas en un solo panel.

5. Evaluar las salidas de los agentes con LangSmith

Las trazas te dicen qué ocurrió. La evaluación te dice cómo de bien funcionó. El framework de evaluación de LangSmith permite ejecutar evaluaciones puntuadas contra conjuntos de datos, con evaluadores propios o con un LLM como juez.

pythonevaluation.py — evaluar contra un conjunto de datos
from langsmith import Client
from langsmith.evaluation import evaluate, LangChainStringEvaluator

client = Client()

# Create or use an existing dataset
dataset = client.create_dataset(
    dataset_name="support-tickets-v2",
    description="Golden set of 200 resolved support tickets",
)

# Add examples: input ticket → expected resolution
client.create_examples(
    inputs=[
        {"messages": [{"role": "user", "content": "How do I cancel my subscription?"}]},
        {"messages": [{"role": "user", "content": "My payment failed but I was charged."}]},
    ],
    outputs=[
        {"final_response": "To cancel, go to Settings → Billing → Cancel Plan."},
        {"final_response": "We've issued a refund. It will appear in 3-5 business days."},
    ],
    dataset_id=dataset.id,
)

# Define evaluators
correctness_evaluator = LangChainStringEvaluator(
    "criteria",
    config={"criteria": "correctness"},
    prepare_output_fn=lambda run, example: {
        "prediction": run.outputs.get("final_response", ""),
        "reference": example.outputs.get("final_response", ""),
    },
)

# Run evaluation against your compiled graph
def run_agent(inputs: dict) -> dict:
    config = {"configurable": {"thread_id": f"eval-{id(inputs)}"}}
    result = graph.invoke(inputs, config=config)
    return {"final_response": result.get("final_response", "")}

results = evaluate(
    run_agent,
    data=dataset.name,
    evaluators=[correctness_evaluator],
    experiment_prefix="gpt-4o-support-v3",
    num_repetitions=1,
)

print(f"Average correctness: {results.to_pandas()['feedback.correctness'].mean():.2f}")

6. Aprobación humana: pausar el grafo para que alguien dé el visto bueno

Para acciones delicadas —enviar correos, tramitar reembolsos, actualizar registros del CRM— quieres que una persona apruebe antes de que el agente actúe. El parámetro interrupt_before de LangGraph pausa la ejecución en cualquier nodo y persiste el estado mediante el checkpointer. Después se reanuda con una sola llamada.

pythonhuman_in_the_loop.py
# Compile with human checkpoint before escalation
graph = builder.compile(
    checkpointer=memory,
    interrupt_before=["escalation"],  # Pause here for human review
)

config = {"configurable": {"thread_id": "ticket-48291"}}

# First run — graph pauses at "escalation" node
result = graph.invoke(
    {"messages": [HumanMessage(content="My invoice is wrong")]},
    config=config,
)
print("Graph paused. Current state:", result)
# → Graph stopped before escalation node

# Human reviews in your dashboard / Slack / UI...
# When approved, resume from the same thread_id:
graph.update_state(
    config,
    {"escalation_reason": "Human approved escalation — billing dispute"},
    as_node="escalation",
)

# Resume — graph continues from where it paused
final_result = graph.invoke(None, config=config)
print("Final response:", final_result["final_response"])

7. Enviar la salida del agente al frontend con streaming

LangGraph admite streaming en tres niveles: por token (caracteres según se generan), por evento (inicio y fin de cada nodo) y por valor (el estado completo tras cada nodo). Usa el que encaje con tu interfaz.

pythonstreaming.py — streaming de tokens y eventos
# Stream tokens as they're generated (for chat UIs)
async for event in graph.astream_events(
    {"messages": [HumanMessage(content="Help me understand my bill")]},
    config=config,
    version="v2",
):
    kind = event["event"]

    if kind == "on_chat_model_stream":
        # Token-level streaming — send to frontend via SSE/WebSocket
        chunk = event["data"]["chunk"]
        if chunk.content:
            print(chunk.content, end="", flush=True)

    elif kind == "on_chain_start":
        # Node started — useful for progress indicators
        node_name = event["name"]
        print(f"\n[{node_name} started]")

    elif kind == "on_tool_start":
        # Tool call started
        tool_name = event["name"]
        inputs = event["data"]["input"]
        print(f"\n🔧 Using tool: {tool_name}({inputs})")

    elif kind == "on_chain_end":
        # Node completed — log duration
        node_name = event["name"]
        print(f"\n[{node_name} completed]")

8. Patrones de producción y errores habituales

✓ Usa PostgresSaver (no SqliteSaver) en producción

SqliteSaver está bien para desarrollo local, pero no funciona en entornos distribuidos ni serverless. Cambia a PostgresSaver para despliegues con varias réplicas. Las dos tienen la misma API: cambia un import y listo.

from langgraph.checkpoint.postgres import PostgresSaver
memory = PostgresSaver.from_conn_string(os.getenv('DATABASE_URL'))

✓ Fija max_iterations para evitar bucles infinitos

Los agentes de planificar y ejecutar pueden dar vueltas para siempre si el replanificador nunca converge. Añade un contador de iteraciones al estado y una arista condicional que fuerce END pasadas N iteraciones.

def should_continue(state):
    if state['iteration'] >= 10:
        return 'end'  # Force termination
    return 'continue'

✓ Usa etiquetas de LangSmith para seguir tus experimentos

Etiqueta cada ejecución con la versión del modelo, la del prompt y los flags de funcionalidad. Así comparar resultados entre experimentos en la interfaz de LangSmith es inmediato.

config = {
    'configurable': {'thread_id': tid},
    'tags': ['gpt-4o', 'prompt-v3', 'prod'],
    'metadata': {'customer_tier': 'enterprise'}
}

✓ Vincula las herramientas al LLM, no al nodo

Define tus herramientas una sola vez y vincúlalas al LLM. El ToolNode de LangGraph se encarga de ejecutarlas automáticamente, lo que mantiene las funciones de los nodos limpias y comprobables por separado.

from langgraph.prebuilt import ToolNode
tools = [search_web, query_rag, send_email]
llm_with_tools = llm.bind_tools(tools)
tool_node = ToolNode(tools)

9. El stack completo de producción

Así es un despliegue de LangGraph + LangSmith en producción, capa por capa:

CapaHerramientaPor qué
OrquestaciónLangGraphEjecución de grafos con estado, bucles, ramificación y aprobación humana
LLMGPT-4o / Claude 3.5 SonnetEl mejor razonamiento para tareas complejas de agentes
Llamadas a herramientasLangGraph ToolNodeDespacha automáticamente las llamadas del LLM a funciones de Python
Memoria y estadoPostgresSaverPersiste el estado del hilo entre llamadas en producción
RAGPinecone + pgvectorBúsqueda vectorial para recuperar de la base de conocimiento
ObservabilidadLangSmithTrazas, evaluaciones, conjuntos de datos y control de costes
Capa de APIFastAPI + SSEEnvía tokens al frontend y gestiona la autenticación
FrontendNext.js + Vercel AI SDKEl AI SDK se encarga de renderizar la respuesta en streaming
ColasRedis / CeleryPara tareas de agentes asíncronas y de larga duración
DespliegueRailway / AWS ECSServidor FastAPI + LangGraph en contenedores

Qué puedes construir con este stack en 4-8 semanas

→Un agente de atención al cliente que resuelve más del 70 % de los tickets por su cuenta

→Un agente de prospección comercial que investiga, escribe y hace seguimiento

→Un pipeline de procesamiento documental con extracción de datos estructurados

→Un agente de selección de personal que criba, puntúa y agenda entrevistas

→Un agente de informes financieros que vigila la cuenta de resultados y señala anomalías

→Un agente de conocimiento interno que responde a las preguntas del equipo a partir de la documentación

La conclusión

LangGraph resuelve el problema de la orquestación: grafos con estado, enrutamiento condicional, memoria persistente y puntos de control humanos. LangSmith resuelve el problema de la visibilidad en producción: cada traza, cada evaluación y cada fallo a la vista y depurables. Juntos te dan el control y la observabilidad necesarios para lanzar sistemas de agentes que funcionan de verdad en producción, no solo en una demo.

Configurar LangSmith con dos variables de entorno es uno de los cambios con mejor retorno que puedes hacer hoy. Cero cambios de código. Visibilidad inmediata de cada ejecución. Si lanzas agentes de IA y no lo usas, vas a ciegas.

IdeaToMVP Academy

Want to build with AI — not just read about it?

4-week live cohort for founders. Learn to ship AI agents, scope MVPs, and automate your business — taught by the same team that writes these guides.

Explore the Academy →
Share this post :