Blogs / LangChain vs LangGraph: Cuándo Realmente Necesitas un Grafo

LangChain vs LangGraph: Cuándo Realmente Necesitas un Grafo

Publicado
1 de octubre de 2026
Autor
Faizan Nadeem
Etiquetas
LangGraph LangChain AI Agents Python
Una imagen abstracta de una red de puntos interconectados
Photo by BoliviaInteligente on Unsplash

Pregúntale a un equipo por qué migraron de LangChain a LangGraph y generalmente obtendrás un encogimiento de hombros y “el agente seguía haciendo cosas raras.” Esa respuesta es más precisa de lo que suena, pero oculta el mecanismo real. Un AgentExecutor es un loop disfrazado de trench coat —parece que toma decisiones, pero por debajo es un while-loop fijo que llama a un modelo, verifica si hay un tool call, ejecuta el tool, y repite hasta que el modelo deja de pedir tools. No hay forma de pausarlo, no hay forma de bifurcarlo, y no hay forma de hacerle recordar nada más allá de lo que cabe en el prompt de la próxima iteración.

Eso está bien para un enorme número de casos de uso reales, y esta es la parte que la mayoría de los análisis “LangGraph es mejor” se saltan. La pregunta real no es “¿cuál es mejor?” —es “¿mi control flow necesita volver sobre sí mismo, bifurcarse en ramas paralelas, o sobrevivir un reinicio?” La mayoría de los agentes no. Algunos genuinamente sí lo hacen, y forzarlos en una chain lineal no falla con estrépito —falla como comportamiento inestable, pérdida silenciosa de estado, y retries que silenciosamente vuelven a ejecutar efectos secundarios. Este artículo trata sobre saber en qué categoría estás antes de escribir una línea de código de orquestación.

Aprenderás:

  • Qué hace realmente una chain de LangChain y AgentExecutor por debajo
  • Los tres síntomas concretos que significan que has superado una chain
  • Cómo LangGraph modela el control flow como un grafo con nodos, edges y estado compartido
  • Una comparación lado a lado de AgentExecutor y un StateGraph construido para la misma tarea
  • Cómo el checkpointer de LangGraph te da estado durable y resumible que una chain no tiene
  • Un framework de decisión para elegir entre los dos en tu próximo proyecto
  • Qué involucra realmente migrar un agente AgentExecutor existente a LangGraph

Tabla de contenidos

  1. Qué es realmente LangChain
  2. Dónde una chain deja de ser suficiente
  3. Qué agrega LangGraph
  4. AgentExecutor vs StateGraph: El mismo agente, dos formas
  5. Estado durable y por qué importa
  6. Cuándo usar LangGraph — y cuándo no
  7. Migrando de AgentExecutor a LangGraph
  8. Por dónde seguir

Qué es realmente LangChain

La abstracción central de LangChain es la chain: una secuencia de pasos —un template de prompt, una llamada al modelo, un output parser— compuestos con el operador | mediante LCEL (LangChain Expression Language). Cada paso toma el output del paso anterior como input. Es un pipeline, y los pipelines son genuinamente un buen modelo para todo lo que realmente es lineal: recuperar documentos, meterlos en un prompt, llamar al modelo, parsear la respuesta.

from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_template("Summarize this in two sentences:\n\n{text}")
model = ChatAnthropic(model="claude-sonnet-5")
chain = prompt | model | StrOutputParser()

result = chain.invoke({"text": long_document})

Los agentes construidos sobre esto usan AgentExecutor, que envuelve la misma idea en un loop: llama al modelo con un conjunto de tools vinculados a él, verifica si la respuesta pide un tool call, ejecuta el tool si es así, devuelve el resultado, y repite hasta que el modelo devuelva una respuesta final en lugar de un tool call.

from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful research assistant."),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

agent = create_tool_calling_agent(model, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, max_iterations=10)

result = executor.invoke({"input": "What's the current weather in Lahore, and is it good for a run?"})

Esto funciona bien para un loop de agente único, hilo único, con un número acotado de tool calls. Sin embargo, el loop es opaco —no controlas qué pasa entre iteraciones, no puedes pausarlo a mitad del loop y reanudarlo después, y no hay forma de tener dos ramas de razonamiento corriendo en paralelo y luego fusionarse. max_iterations es la única válvula de seguridad real, y es contundente: solo detiene el loop, no te deja redirigirlo.

Dónde una chain deja de ser suficiente

Tres síntomas aparecen de manera confiable justo antes de que un equipo llegue a LangGraph. Ninguno es exótico —son lo que pasa naturalmente una vez que el trabajo de un agente se vuelve algo más real.

1. El agente necesita volver sobre sí mismo con un propósito, no solo reintentar. Una chain reintenta volviendo a ejecutar la misma secuencia fija. Pero un patrón genuinamente útil —escribir código, ejecutar tests, leer el fallo, arreglar el código, ejecutar tests nuevamente— requiere que el resultado de un paso determine cuál paso se ejecuta a continuación, potencialmente ciclando varias veces antes de salir. AgentExecutor puede llegar ahí técnicamente mediante llamadas repetidas al tool, pero no tienes visibilidad ni control sobre el ciclo en sí; es solo “el loop pasó a llamar el tool nuevamente.”

2. El agente necesita bifurcarse y posiblemente ejecutar pasos en paralelo. Supón que una solicitud debería ir a una respuesta de camino rápido o a un camino de investigación multi-paso, decidido primero por clasificar la consulta. Una chain es un camino. Obtener una bifurcación en AgentExecutor significa escribir lógica personalizada fuera del executor e invocarlo condicionalmente —en ese punto estás construyendo tu propia orquestación encima de una abstracción que no fue diseñada para exponerla.

3. El estado del agente necesita sobrevivir más allá del proceso que lo está ejecutando. Si el proceso de una chain se cae, reinicia, o una solicitud necesita pausarse por minutos (una revisión humana) o días (esperando un evento externo), AgentExecutor no tiene ningún lugar donde poner ese estado. Vive en memoria, en variables de Python, por la duración de una sola llamada .invoke(). Cuando esa llamada termina, todo lo que sabía también termina.

Si ninguno de estos tres aplica a lo que estás construyendo, una chain no es una elección inferior —generalmente es la elección correcta, porque es más simple de razonar, más barata de ejecutar (sin overhead de traversal de grafo), y más fácil de debuggear con un stack trace lineal.

Qué agrega LangGraph

LangGraph reencuadra el control flow de un agente como un grafo explícito: un conjunto de nodos (cada uno una función que recibe el estado actual y devuelve actualizaciones del mismo), edges (que conectan nodos, ya sea incondicionalmente o basado en una función de routing), y un objeto de estado compartido que cada nodo lee y escribe. Los ciclos no son un workaround aquí —son el caso por defecto que un grafo soporta naturalmente, porque un edge puede apuntar de vuelta a un nodo que ya visitaste.

from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode

class AgentState(TypedDict):
    messages: Annotated[list, add_messages]

def call_model(state: AgentState):
    response = model_with_tools.invoke(state["messages"])
    return {"messages": [response]}

def should_continue(state: AgentState):
    last_message = state["messages"][-1]
    return "tools" if last_message.tool_calls else END

builder = StateGraph(AgentState)
builder.add_node("agent", call_model)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
builder.add_edge("tools", "agent")

graph = builder.compile()

Esto parece más verboso que AgentExecutor para el mismo loop básico, y lo es —ese es el tradeoff. Lo que obtienes a cambio es que el ciclo (agent → tools → agent) ahora es un hecho que puedes ver, modificar y sobre el que puedes razonar, en lugar de algo enterrado dentro del loop interno de una biblioteca. Puedes agregar un tercer nodo que solo registra el estado, insertar un edge condicional que enruta a un nodo de revisión humana en lugar de volver a hacer loop automáticamente, o ejecutar dos ramas independientes desde el mismo nodo y fusionar sus resultados —nada de esto cabe naturalmente en el modelo de AgentExecutor.

La comparación que la gente realmente quiere decir cuando dice “LangGraph vs AgentExecutor” es esta: AgentExecutor te da una forma de loop fija con tool-calling incorporado; StateGraph te da una forma de grafo arbitraria donde el tool-calling es solo un patrón común que construyes encima, no una forma que el framework te entrega gratis.

AgentExecutor vs StateGraph: El mismo agente, dos formas

Para hacer la diferencia concreta, aquí está el mismo agente “investigar y resumir”, una vez como AgentExecutor, una vez como StateGraph con una rama de revisión explícita.

Versión AgentExecutor — lineal, sin rama:

executor = AgentExecutor(agent=agent, tools=[search_tool, summarize_tool])
result = executor.invoke({"input": "Research recent LLM pricing changes and summarize."})

Cada ejecución sigue el mismo camino implícito: el modelo decide, el tool corre, el modelo decide de nuevo. Si quisieras “resumir solo si la investigación encontró más de tres fuentes, de lo contrario hacer una pregunta de seguimiento”, tendrías que post-procesar el output del executor y posiblemente re-invocarlo —código pegamento fuera del framework.

Versión StateGraph — misma tarea, con una rama condicional explícita:

def route_after_research(state: AgentState) -> str:
    sources = state.get("source_count", 0)
    return "summarize" if sources > 3 else "ask_followup"

builder = StateGraph(AgentState)
builder.add_node("research", research_node)
builder.add_node("summarize", summarize_node)
builder.add_node("ask_followup", followup_node)
builder.add_edge(START, "research")
builder.add_conditional_edges("research", route_after_research)
builder.add_edge("summarize", END)
builder.add_edge("ask_followup", END)

graph = builder.compile()

La rama es ahora una parte de primera clase de la definición del grafo —visible, testeable de forma aislada (puedes hacer unit test de route_after_research como una función pura), e independiente de cuántos tool calls necesitó internamente el paso de investigación. Eso es el delta real: no “LangGraph es más inteligente”, sino “la decisión de control flow se movió de implícita (dentro del loop) a explícita (un nodo en el grafo).”

Ramas paralelas: Algo que AgentExecutor no puede hacer en absoluto

El ejemplo de investigación/resumen anterior es una rama condicional —un camino u otro. LangGraph también soporta verdadero fan-out/fan-in: múltiples nodos corriendo desde el mismo predecesor, con el grafo esperando a que todos terminen antes de continuar. Esto no tiene un equivalente real en AgentExecutor, cuyo loop es inherentemente de un solo hilo a través del modelo.

def route_to_all_sources(state: AgentState) -> list[str]:
    return ["search_web", "search_docs", "search_internal_db"]

builder.add_conditional_edges("plan", route_to_all_sources)
builder.add_edge("search_web", "merge_results")
builder.add_edge("search_docs", "merge_results")
builder.add_edge("search_internal_db", "merge_results")
builder.add_node("merge_results", merge_node)

Cada uno de los tres nodos de búsqueda corre independientemente y escribe sus hallazgos en el estado compartido; merge_results solo se ejecuta cuando los tres han terminado, y ve el estado combinado. Tratar de replicar esto dentro de AgentExecutor significa salir del framework completamente —ejecutar tu propio asyncio.gather() sobre instancias de executor separadas y unir los resultados manualmente, que es exactamente el tipo de código de orquestación que LangGraph está pensado para absorber.

Una comparación rápida

AgentExecutorLangGraph StateGraph
Forma del control flowLoop fijo (llamar modelo, ejecutar tool, repetir)Grafo arbitrario que tú defines
BifurcaciónNo es nativo — requiere código pegamento externoadd_conditional_edges, de primera clase
Fan-out paraleloNo soportadoNativo, mediante múltiples edges desde un nodo
CiclosImplícito dentro del loop, opacoEdges explícitos, completamente visibles
Estado persistidoNinguno — vive en memoria para un .invoke()Checkpointer (memoria, SQLite, Postgres)
Reanudar después de un reinicioNo es posiblethread_id + checkpointer
DebuggingStack trace a través de los internos de la bibliotecaInspeccionar estado en cualquier límite de nodo
Curva de aprendizajeMenor — unas pocas llamadas a funcionesMayor — defines nodos, edges y estado explícitamente

Estado durable y por qué importa

La otra pieza que genuinamente le falta a AgentExecutor es un checkpointer. LangGraph puede persistir el objeto de estado completo después de que cada nodo se ejecuta, indexado por un thread_id, usando un backend pluggable —en memoria para desarrollo, Postgres o SQLite para cualquier cosa que necesite sobrevivir un reinicio del proceso.

from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)

config = {"configurable": {"thread_id": "user-42-session-1"}}
graph.invoke({"messages": [("user", "Start the research task")]}, config)

Porque el output de cada nodo está checkpointed, puedes llamar graph.invoke() nuevamente más tarde con el mismo thread_id y retoma exactamente donde lo dejó —mismo historial de mensajes, mismo estado intermedio, sin volver a ejecutar efectos secundarios que ya ocurrieron. AgentExecutor no tiene equivalente: mata el proceso Python a mitad de una ejecución y toda la memoria de trabajo del agente desaparece. Esta durabilidad es también lo que hace que patrones como la revisión human-in-the-loop sean prácticos en lugar de teóricos —un grafo puede pausar en un nodo, esperar indefinidamente, y reanudar con el estado exactamente como lo dejó. Ese es el tema de su propio deep dive, vinculado a continuación.

Cuándo usar LangGraph — y cuándo no

Usa una chain de LangChain (con o sin AgentExecutor) cuando:

  • La tarea es fundamentalmente lineal: recuperar, aumentar, generar.
  • Los tool calls son acotados y no necesitan bifurcación condicional basada en sus resultados.
  • Toda la interacción se completa dentro de la vida de un solo proceso —sin pausas entre reinicios o espera de un humano por un período extendido.
  • Valoras la superficie más pequeña y predecible de una abstracción más simple.

Usa LangGraph cuando al menos uno de estos aplica:

  • El agente necesita un ciclo cuya condición de salida depende del estado evaluado, no solo “el modelo dejó de llamar tools” (loops de test-fix-retest, negociación multi-ronda entre sub-agentes).
  • Necesitas bifurcación explícita —diferentes caminos para diferentes clasificaciones del mismo input, o ramas paralelas que se abren y luego se fusionan.
  • El workflow debe sobrevivir un reinicio del proceso, o necesita pausarse por una cantidad indefinida de tiempo (aprobación humana, esperando un webhook async) y reanudarse después con el contexto completo intacto.
  • Estás construyendo un sistema multi-agente donde varios agentes necesitan compartir y actualizar un objeto de estado común en lugar de solo pasarse texto de ida y vuelta.

Una verificación de intuición útil: si puedes dibujar la lógica de tu agente como una sola flecha yendo de izquierda a derecha, todavía no necesitas un grafo. En el momento en que dibujas una flecha que vuelve sobre sí misma, o dos flechas que salen del mismo cuadro, ya estás describiendo un grafo —LangGraph solo le da a ese dibujo un motor de ejecución.

Migrando de AgentExecutor a LangGraph

Si estás migrando un agente existente en lugar de empezar de cero, la parte mecánica es más pequeña de lo que parece:

  1. Modela tu estado. Toma lo que AgentExecutor estaba pasando a través de las invocaciones (generalmente solo messages) y defínelo como un TypedDict.
  2. Convierte tu loop de tool-calling en dos nodos. Un nodo llama al modelo con tools vinculados; un ToolNode (LangGraph lo incluye preconstruido) ejecuta cualquier tool call solicitado.
  3. Reemplaza max_iterations con un edge condicional explícito. En lugar de un límite duro, escribe una función de routing que decida si volver al nodo del modelo o salir —todavía puedes limitar el conteo de iteraciones dentro de esa función si quieres la misma red de seguridad.
  4. Agrega un checkpointer antes de agregar cualquier otra cosa. Incluso MemorySaver en desarrollo te acostumbra a pasar un thread_id a través de cada llamada, que es la parte que la mayoría de las migraciones olvidan hasta que necesitan durabilidad en producción.
  5. Solo entonces agrega la rama o ciclo que realmente necesitabas. La forma de grafo a la que migras debería ser no más compleja que el loop de AgentExecutor que tenías —los nodos adicionales son para el nuevo comportamiento que estás agregando, no para scaffolding requerido.

La mayor parte de esto es unas pocas horas de trabajo para un agente pequeño, no una reescritura. El cambio mayor es mental: dejas de pensar en el agente como “un loop con tools” y empiezas a pensar en él como “una máquina de estados con un modelo en algunos de los nodos.”

Por dónde seguir

Este artículo cubre el punto de decisión —cuándo una chain deja de ser suficiente y un grafo empieza a pagar por sí mismo. El resto de esta serie retoma desde ahí, profundizando en las piezas que solo importan una vez que has dado ese salto:

Conclusión

AgentExecutor y StateGraph no son una comparación de “cuál es mejor” —están resolviendo diferentes cantidades de complejidad de control flow. Una chain es la herramienta correcta cuando la lógica de tu agente realmente es una línea recta con algunos tool calls en el camino, y llegar a un grafo antes de que lo necesites solo agrega nodos y edges que tienes que mantener sin ganancia conductual. Pero en el momento en que tu agente necesita ciclar con propósito, bifurcarse sobre estado evaluado, o sobrevivir más que la vida de un solo proceso, una chain no degrada graciosamente —empieza a acumular workarounds atornillados desde fuera del framework. LangGraph existe exactamente para ese momento, y saber en qué lado de eso estás antes de empezar a escribir código de orquestación te ahorrará una reescritura después.

Relacionado: Descubre cómo un servidor MCP facilita la investigación de software en Reverse Engineer Anything (REA): guía de uso.

Faizan Nadeem
Autor
Faizan Nadeem

AI/GenAI engineer specialising in agentic RAG, LangGraph, and production LLM systems with Python and FastAPI. 2 years building AI agents, RAG pipelines, and the backends behind them.

Más artículos