Módulo 5: Introducción a LangGraph

Introducción: De Agentes Lineales a Workflows como Grafos

Descripción

En los Módulos 1-4 construiste agentes completos con create_agent. Una línea de código y tenías un agente autónomo que razona, llama tools, observa resultados, y repite hasta responder. Le agregaste system prompts, estado custom, streaming, structured output. Personalizaste su comportamiento con middleware sin reescribirlo. Todo funcionaba.

Hasta que necesitaste algo que el loop ReAct no puede hacer: ramificar la ejecución según el tipo de input. O pausar el workflow para pedir aprobación humana antes de una acción costosa. O crear un proceso donde un nodo clasifica, otro investiga, otro valida, y otro genera la respuesta final — cada uno con su propia lógica. O hacer un loop condicional que reintenta solo si la calidad del output no es suficiente.

create_agent es potente, pero fundamentalmente lineal: el modelo razona, llama tools, observa, repite. Siempre el mismo ciclo. No puedes cambiar ese flujo — solo puedes interceptarlo con middleware.

LangGraph te da el control total. Tú defines cada paso, cada decisión, cada loop. No hay magia ni abstracciones ocultas. Es como pasar de conducir con piloto automático a diseñar la ruta tú mismo — más poder, más responsabilidad.


¿Dónde estamos en la guía?

Este es el Módulo 5 de la guía LangChain & LangGraph: From Chains to Agents. Es el primer módulo del Bloque 2 (LangGraph Fundamentals) — la transición más importante de la guía.

Bloque 1: LangChain Core (Módulos 1-4)     ✅ Completado
Bloque 2: LangGraph Fundamentals (Módulos 5-7)  ← ESTÁS AQUÍ (Módulo 5)
Bloque 3: LangGraph Avanzado (Módulos 8-10)
Bloque 4: Producción (Módulos 11-12)
Tu progreso:

Bloque 1 — LangChain Core                   ✅ Completado
    │
    │  Módulo 1: Modelos y Proveedores       ✅
    │  Módulo 2: Tools y Tool Calling        ✅
    │  Módulo 3: Agents (create_agent)       ✅
    │  Módulo 4: Middleware y Customización   ✅
    │
    ▼
Bloque 2 — LangGraph Fundamentals
    │
    │  Módulo 5: Introducción a LangGraph    ← ESTÁS AQUÍ
    │  Módulo 6: Functional API              🔒 Siguiente
    │  Módulo 7: Flujos Avanzados            🔒
    │
    ▼
Bloques 3-4 — Avanzado + Producción         🔒

En el Bloque 1 dominaste el alto nivel: modelos, tools, agentes con create_agent, y middleware para personalizarlos. Ahora bajas al nivel donde tú controlas cada aspecto del flujo de ejecución.


El puente: de agentes automáticos a workflows diseñados por ti

Lo que ya sabes

En el Bloque 1 llegaste a construir esto con create_agent y middleware:

from dotenv import load_dotenv
load_dotenv()

from langchain.agents import create_agent
from langchain_core.tools import tool

@tool
def search(query: str) -> str:
    """Busca información en la web."""
    return f"Resultados para '{query}': Python es un lenguaje creado por Guido van Rossum."

@tool
def calculator(expression: str) -> str:
    """Calcula una expresión matemática."""
    return str(eval(expression))

agent = create_agent(
    "openai:gpt-4.1-mini",
    tools=[search, calculator],
    system_prompt="Eres un asistente de investigación."
)

result = agent.invoke({"messages": [("user", "¿Quién creó Python y cuánto es 2**20?")]})
print(result["messages"][-1].content)
# Output esperado: Python fue creado por Guido van Rossum. Y 2^20 = 1,048,576.

El agente funciona. Razona, llama tools, combina resultados. Pero el flujo siempre es el mismo:

┌──────────┐     ┌──────────┐     ┌──────────┐
│  Reason  │────▶│   Act    │────▶│ Observe  │───┐
│ (Modelo) │     │  (Tool)  │     │(Resultado)│   │
└──────────┘     └──────────┘     └──────────┘   │
     ▲                                            │
     └────────────────────────────────────────────┘

No puedes cambiar este flujo. No puedes hacer que un nodo clasifique y otro ejecute. No puedes agregar una pausa para aprobación humana entre el razonamiento y la acción. No puedes hacer que el agente tome una ruta diferente según el tipo de pregunta.

Lo que aprenderás aquí

Con LangGraph, tú diseñas el flujo:

from langgraph.graph import StateGraph, START, END

graph_builder = StateGraph(MyState)
graph_builder.add_node("classify", classify_input)
graph_builder.add_node("qa_handler", handle_qa)
graph_builder.add_node("creative_handler", handle_creative)
graph_builder.add_node("code_handler", handle_code)
graph_builder.add_conditional_edges("classify", route_by_intent)

Cada nodo es una función que tú defines. Cada conexión es una decisión que tú controlas. El flujo se ramifica, hace loops, se pausa — lo que tú necesites.


Limitaciones de create_agent

create_agent resuelve el 80% de los casos. Pero tiene límites estructurales:

Necesidadcreate_agentLangGraph (StateGraph)
Agente que razona y llama toolsUna línea de códigoRequiere más código
Ramificar según tipo de inputNo es posibleConditional edges
Pausar para aprobación humanaLimitado (interrupt_before)Interrupts granulares
Múltiples nodos especializadosUn solo loop ReActNodos independientes
Loops condicionales (retry si calidad < umbral)No es posibleEdges que vuelven atrás
Proceso de múltiples fases (plan → execute → validate)No es posibleNodos secuenciales con lógica propia
Estado custom complejostate_schema básicoTypedDict + reducers completos
Visualización del flujoGrafo fijo (model ↔ tools)draw_mermaid_png() de tu diseño

create_agent no es inferior — es diferente. Si necesitas un agente que razone y llame tools, create_agent es la opción correcta y más rápida. Si necesitas un workflow con flujo custom, LangGraph es lo que necesitas.

La pregunta no es "¿cuál es mejor?" sino "¿qué tipo de flujo necesito?"


El concepto central: grafos como diagramas de flujo

Un grafo en LangGraph tiene tres componentes:

  1. Nodos — Funciones que transforman estado. Cada nodo recibe el estado actual, hace algo (llama al modelo, ejecuta lógica, valida datos), y retorna las actualizaciones.

  2. Edges — Conexiones entre nodos. Definen el orden de ejecución. Pueden ser fijos ("después de A, siempre va B") o condicionales ("después de A, va B o C según el resultado").

  3. Estado — Un diccionario tipado que viaja por todo el grafo. Cada nodo lo lee y lo actualiza. Es la memoria compartida del workflow.

Piensa en un diagrama de flujo que harías en una pizarra:

            ┌─────────┐
            │  START  │
            └────┬────┘
                 │
                 ▼
          ┌──────────────┐
          │  Clasificar  │
          │   intención  │
          └──────┬───────┘
                 │
         ┌───────┼───────┐
         │       │       │
         ▼       ▼       ▼
     ┌───────┐ ┌─────┐ ┌──────┐
     │  Q&A  │ │Code │ │ Chat │
     └───┬───┘ └──┬──┘ └──┬───┘
         │        │       │
         └────────┼───────┘
                  │
                  ▼
            ┌─────────┐
            │   END   │
            └─────────┘

Eso es un grafo de LangGraph. Cada caja es un nodo (función). Cada flecha es un edge (conexión). El flujo va de START a END, pasando por los nodos que correspondan.

No hay teoría de grafos avanzada. No necesitas saber qué es un DAG ni estudiar algoritmos. Si puedes dibujar un diagrama de flujo, puedes construir un grafo en LangGraph.


Side-by-side: el mismo problema, dos enfoques

Imagina que necesitas un asistente que clasifique la pregunta del usuario y responda de forma especializada según el tipo.

Con create_agent: una sola vía

from dotenv import load_dotenv
load_dotenv()

from langchain.agents import create_agent
from langchain_core.tools import tool

@tool
def classify_and_respond(question: str) -> str:
    """Clasifica la pregunta y genera una respuesta especializada."""
    if "código" in question.lower() or "python" in question.lower():
        return f"[CODE] Respuesta técnica sobre: {question}"
    elif "historia" in question.lower() or "cuándo" in question.lower():
        return f"[QA] Respuesta informativa sobre: {question}"
    else:
        return f"[CHAT] Respuesta conversacional sobre: {question}"

agent = create_agent(
    "openai:gpt-4.1-mini",
    tools=[classify_and_respond],
    system_prompt="Usa la tool para clasificar y responder cada pregunta."
)

result = agent.invoke({"messages": [("user", "¿Cómo hago un for loop en Python?")]})
print(result["messages"][-1].content)
# La clasificación y la respuesta están dentro de una sola tool.
# No hay flujo real — solo un agente que llama una tool.

La clasificación está metida dentro de una tool. No hay separación real de responsabilidades. No puedes visualizar el flujo. No puedes agregar un nodo de validación entre la clasificación y la respuesta.

Con StateGraph: flujo diseñado

from typing import TypedDict, Annotated, Literal
import operator
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    messages: Annotated[list[str], operator.add]
    intent: str

def classify(state: State) -> dict:
    last_message = state["messages"][-1].lower()
    if "código" in last_message or "python" in last_message:
        return {"intent": "code"}
    elif "historia" in last_message or "cuándo" in last_message:
        return {"intent": "qa"}
    return {"intent": "chat"}

def handle_code(state: State) -> dict:
    return {"messages": [f"[CODE] Respuesta técnica sobre: {state['messages'][-1]}"]}

def handle_qa(state: State) -> dict:
    return {"messages": [f"[QA] Respuesta informativa sobre: {state['messages'][-1]}"]}

def handle_chat(state: State) -> dict:
    return {"messages": [f"[CHAT] Respuesta conversacional sobre: {state['messages'][-1]}"]}

def route_by_intent(state: State) -> Literal["code", "qa", "chat"]:
    return state["intent"]

graph_builder = StateGraph(State)
graph_builder.add_node("classify", classify)
graph_builder.add_node("code", handle_code)
graph_builder.add_node("qa", handle_qa)
graph_builder.add_node("chat", handle_chat)

graph_builder.add_edge(START, "classify")
graph_builder.add_conditional_edges("classify", route_by_intent)
graph_builder.add_edge("code", END)
graph_builder.add_edge("qa", END)
graph_builder.add_edge("chat", END)

graph = graph_builder.compile()

result = graph.invoke({"messages": ["¿Cómo hago un for loop en Python?"], "intent": ""})
print(result)
# {"messages": ["¿Cómo hago un for loop en Python?", "[CODE] Respuesta técnica sobre: ..."], "intent": "code"}

Cada responsabilidad tiene su propio nodo. El flujo es visible. Puedes agregar nodos intermedios (validación, logging, enriquecimiento) sin reescribir nada. Puedes visualizarlo con draw_mermaid_png().

Más código, pero más control. Esa es la diferencia fundamental.


Qué dominarás en este módulo

Al completar las 8 cápsulas de este módulo, serás capaz de:

  • ✅ Crear grafos con StateGraph y estado tipado (TypedDict + Annotated con reducers)
  • ✅ Definir nodos como funciones que transforman estado
  • ✅ Conectar nodos con edges fijos (add_edge) y conditional edges (add_conditional_edges)
  • ✅ Compilar grafos con graph.compile() y ejecutarlos con invoke y stream
  • ✅ Visualizar grafos con draw_mermaid_png() para debugging y documentación
  • ✅ Decidir con criterio cuándo usar create_agent (80% de casos) vs StateGraph (control total)

Mapa del módulo

CápsulaTemaQué aprenderás
02StateGraph: tu primer grafoStateGraph(State), TypedDict con Annotated y operator.add, START y END, compilar y ejecutar
03Nodos: funciones que transforman estadoDefinir nodos como funciones, add_node(), nodos que llaman al modelo, nodos que ejecutan tools
04Edges y conditional edgesadd_edge() para conexiones fijas, add_conditional_edges() para routing dinámico, funciones de routing
05Estado tipado con TypedDict y AnnotatedDiseñar estado: qué incluir, reducers con Annotated, MessagesState prebuilt, custom state fields
06Compilación y ejecucióngraph.compile(), graph.invoke(), graph.stream(), visualización con draw_mermaid_png()
07create_agent vs StateGraphTabla de decisión, ejemplos de cuándo usar cada uno, trade-offs reales
08Proyecto: Chatbot con estado y routing condicionalChatbot que clasifica intención del usuario y rutea a nodos especializados con conditional edges

Flujo de aprendizaje: Empiezas creando tu primer grafo y entendiendo cómo funciona el estado (02). Luego profundizas en nodos (03) y en cómo conectarlos con edges fijos y condicionales (04). Después dominas el diseño de estado con reducers y campos custom (05). Con esas bases, aprendes a compilar, ejecutar, streamear y visualizar grafos (06). Finalmente, desarrollas criterio para elegir entre create_agent y StateGraph (07) y construyes el proyecto integrador (08).


Conexión con el proyecto

Mini-Proyecto de este módulo: Chatbot con Estado y Routing Condicional

En la Cápsula 08 construirás un chatbot con StateGraph que:

  1. Usa 4+ nodos — clasificador de intención, handler de Q&A, handler de creative writing, handler de code help, nodo de respuesta
  2. Implementa conditional edges basados en la clasificación de intención del usuario
  3. Mantiene estado tipado con TypedDict — mensajes, intención detectada, metadata del proceso
  4. Se visualiza con draw_mermaid_png() — ves exactamente cómo fluyen las decisiones

Este es el último mini-proyecto independiente de la guía. A partir del Módulo 6, todo lo que construyas será parte del AI Research Assistant — un proyecto evolutivo que crece contigo módulo a módulo.

Conexión con la guía completa

Lo que aprendes de StateGraph aquí es la base de todo lo que viene:

  • Módulo 6 (Functional API): Presentará una forma alternativa de construir workflows — usando @entrypoint y @task en vez de grafos explícitos. Ambas APIs coexisten; elegirás según el caso.
  • Módulo 7 (Flujos Avanzados): Agrega ciclos, retries, branching paralelo, subgraphs y error handling — patrones que necesitan la base de StateGraph.
  • Módulos 8-10: Memoria persistente (checkpointing), human-in-the-loop (interrupts), y multi-agente (supervisor, handoffs). Todo se construye sobre grafos.
  • Módulos 11-12: Deep Agents y producción con LangSmith. StateGraph es el motor debajo de todo.

Límites: qué NO cubre este módulo

  • Functional API (@entrypoint, @task) — Se cubre en Módulo 6. Aquí trabajas exclusivamente con la Graph API (StateGraph).
  • Flujos avanzados (ciclos, retries, subgraphs) — Se cubre en Módulo 7. Aquí dominas los fundamentos: nodos, edges, conditional edges.
  • Memoria persistente (checkpointing) — Se cubre en Módulo 8. Aquí el estado vive en memoria durante la ejecución.
  • Human-in-the-loop (interrupts, approvals) — Se cubre en Módulo 9. Mencionaremos que LangGraph lo habilita, pero no profundizamos.
  • Multi-agent systems — Se cubre en Módulo 10. Aquí trabajas con un solo grafo, no con múltiples agentes coordinados.

Setup técnico

Prerequisitos

Antes de continuar, verifica que tienes:

  • Bloque 1 completado — sabes crear agentes con create_agent, tools, middleware, system prompts, streaming, y structured output
  • Python 3.11+ instalado
  • ✅ Al menos una API key de un proveedor (OpenAI o Anthropic recomendado)

Instalación

Si completaste el Bloque 1, ya tienes langchain, langgraph, y langchain-openai instalados. Verifica:

pip install langgraph langchain-openai python-dotenv

Si ya tienes todo, confirma que langgraph es v1.0+:

import langgraph
print(langgraph.__version__)
# Necesitas >= 1.0.0

Verificar que todo funciona

from typing import TypedDict, Annotated
import operator
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    messages: Annotated[list[str], operator.add]

def hello(state: State) -> dict:
    return {"messages": ["¡LangGraph funciona!"]}

graph_builder = StateGraph(State)
graph_builder.add_node("hello", hello)
graph_builder.add_edge(START, "hello")
graph_builder.add_edge("hello", END)

graph = graph_builder.compile()
result = graph.invoke({"messages": []})
print(result)
# Output esperado: {"messages": ["¡LangGraph funciona!"]}

Si ves el mensaje, tu setup está listo para LangGraph.

Si algo falla:

ErrorCausaSolución
ModuleNotFoundError: No module named 'langgraph'langgraph no instaladopip install langgraph
ImportError: cannot import name 'StateGraph'Versión de langgraph muy antiguapip install --upgrade langgraph (necesitas v1.0+)
TypeError: 'type' object is not subscriptablePython < 3.9 con list[str]Actualiza a Python 3.11+ o usa from __future__ import annotations

Evidencia de éxito

Al terminar este módulo, sabrás que tuviste éxito si:

  • ✅ Puedes crear un StateGraph con estado tipado, nodos, y edges desde cero
  • ✅ Entiendes la diferencia entre operator.add (acumular) y sin reducer (reemplazar)
  • ✅ Puedes usar conditional edges para routing dinámico basado en el estado
  • ✅ Tu grafo se visualiza correctamente con draw_mermaid_png()
  • ✅ Sabes cuándo usar create_agent y cuándo usar StateGraph
  • ✅ Tu proyecto de chatbot clasifica intenciones y rutea a nodos especializados

Vista previa: del grafo explícito a la Functional API

En este módulo construirás workflows definiendo grafos explícitos: nodos, edges, conditional edges, compilar, ejecutar. Es el enfoque más visual y explícito — ves exactamente cómo fluye la ejecución.

Pero LangGraph ofrece otra forma de construir lo mismo: la Functional API. En el Módulo 6, aprenderás a escribir workflows como funciones Python normales con @entrypoint y @task:

# Módulo 5: Graph API (lo que aprenderás aquí)
from langgraph.graph import StateGraph, START, END

graph_builder = StateGraph(State)
graph_builder.add_node("classify", classify_input)
graph_builder.add_node("respond", generate_response)
graph_builder.add_edge(START, "classify")
graph_builder.add_edge("classify", "respond")
graph_builder.add_edge("respond", END)
graph = graph_builder.compile()

# Módulo 6: Functional API (lo que viene después)
from langgraph.func import entrypoint, task

@task
def classify_input(message: str) -> str:
    ...

@task
def generate_response(intent: str, message: str) -> str:
    ...

@entrypoint()
def assistant(messages: list) -> str:
    intent = classify_input(messages[-1]).result()
    return generate_response(intent, messages[-1]).result()

Misma lógica, dos formas de expresarla. La Graph API es ideal cuando el flujo es visual y tiene branching. La Functional API es ideal cuando el flujo es más secuencial y quieres usar control flow de Python (loops, conditionals, try/except).

El Módulo 6 también marca el inicio del proyecto evolutivo — a partir de ahí, cada módulo construye sobre el anterior para crear el AI Research Assistant.


Resumen

  • En el Bloque 1 dominaste LangChain Core: modelos, tools, agentes con create_agent, y middleware para personalizarlos
  • create_agent es potente pero fundamentalmente lineal — siempre el mismo ciclo ReAct. No puedes ramificar, pausar, ni diseñar flujos custom
  • LangGraph te da control total del flujo: tú defines cada nodo, cada conexión, cada decisión
  • Un grafo tiene tres componentes: nodos (funciones que transforman estado), edges (conexiones), y estado (diccionario tipado compartido)
  • Piensa en un grafo como un diagrama de flujo — no necesitas teoría de grafos ni ciencia computacional
  • create_agent sigue siendo la herramienta correcta para el 80% de los casos (agentes que razonan y llaman tools). StateGraph es para cuando necesitas control total del workflow
  • El tono de este módulo es transicional y empoderador: "ahora tú decides cómo fluye la ejecución — eso es más poder y más responsabilidad"
  • Este es el último módulo con mini-proyecto independiente. A partir del Módulo 6, el proyecto evolutivo (AI Research Assistant) toma el protagonismo
  • Visualización con draw_mermaid_png() es herramienta de trabajo, no un nice-to-have. Dibujar primero, codear después

Recursos adicionales

  1. LangGraph Overview — Página principal de LangGraph con conceptos y quickstart
  2. StateGraph Reference — Referencia completa de la clase StateGraph
  3. LangGraph Quickstart — Tutorial oficial paso a paso
  4. LangGraph vs LangChain Agents — Cuándo usar cada nivel de abstracción
  5. How to visualize your graph — Guía para draw_mermaid_png() y otras opciones de visualización
  6. TypedDict — Python docs — Referencia de TypedDict que usarás para definir el estado del grafo

Módulo 5 — LangChain & LangGraph: From Chains to Agents

Siguiente cápsula: StateGraph: Tu Primer Grafo — aprenderás a crear un grafo desde cero, definir estado con TypedDict y Annotated, y entender por qué los reducers son críticos.