LangGraph — Módulo 4: ramificación y routing
Cuarto módulo del curso de LangGraph: aristas condicionales para que el grafo decida su camino, funciones de routing con retorno Literal, path_map y cómo crear ciclos (bucles de reintento) sin quedar en loop infinito.
☰ Índice del contenido
- La tesis del módulo
- Aristas normales vs condicionales
- Ejemplo: un clasificador que ramifica
- El path_map: separar la decisión del destino
- Ciclos: volver atrás para reintentar
- Cuidado: el ciclo infinito y el recursion_limit
- Errores comunes
- Cierre del bloque básico
- La frase para llevarse
- Qué viene
- Material relacionado
Cuarto y último módulo del bloque básico del Curso completo de LangGraph. Hasta ahora nuestros grafos iban en línea recta: A → B → fin. Pero lo que separa un grafo de una cadena es poder decidir el camino. Ese es el tema de hoy: las aristas condicionales.
La tesis del módulo
Una arista condicional es una bifurcación con criterio. En vez de “después de A siempre B”, dices “después de A, una función mira el estado y decide a dónde ir”. Esa función de routing es donde vive la inteligencia de flujo de tu agente — y donde nacen los ciclos.
Aristas normales vs condicionales
- Arista normal (
add_edge): fija. “De A siempre se va a B.” - Arista condicional (
add_conditional_edges): dinámica. “De A se va a donde diga esta función de routing, según el estado.”
La función de routing (router) es una función que recibe el estado y devuelve el nombre del siguiente nodo (un string).
Ejemplo: un clasificador que ramifica
Imagina un agente de soporte que, según el tipo de mensaje, va a un nodo u otro.
from typing import Literal
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
mensaje: str
categoria: str
respuesta: str
def clasificar(state: State) -> dict:
texto = state["mensaje"].lower()
categoria = "factura" if "factura" in texto or "pago" in texto else "tecnico"
return {"categoria": categoria}
def responder_factura(state: State) -> dict:
return {"respuesta": "Te derivo con el área de facturación 💳"}
def responder_tecnico(state: State) -> dict:
return {"respuesta": "Veamos ese problema técnico paso a paso 🛠️"}
Ahora la pieza nueva: la función de routing. Fíjate en el -> Literal[...]: declara los destinos posibles. No es obligatorio, pero LangGraph lo usa para dibujar bien el grafo y para avisarte de errores.
def elegir_ruta(state: State) -> Literal["responder_factura", "responder_tecnico"]:
if state["categoria"] == "factura":
return "responder_factura"
return "responder_tecnico"
Y la conectamos con add_conditional_edges:
builder = StateGraph(State)
builder.add_node("clasificar", clasificar)
builder.add_node("responder_factura", responder_factura)
builder.add_node("responder_tecnico", responder_tecnico)
builder.add_edge(START, "clasificar")
# Después de "clasificar", el router decide a dónde ir:
builder.add_conditional_edges("clasificar", elegir_ruta)
# Ambas ramas terminan:
builder.add_edge("responder_factura", END)
builder.add_edge("responder_tecnico", END)
graph = builder.compile()
print(graph.invoke({"mensaje": "tengo un problema con mi factura"})["respuesta"])
# → Te derivo con el área de facturación 💳
El grafo quedó así:
┌──► responder_factura ──► END
START ──► clasificar ──┤
└──► responder_tecnico ──► END
El path_map: separar la decisión del destino
A veces tu router devuelve algo que no es el nombre del nodo (un True/False, una categoría). El tercer argumento, el path_map, traduce ese valor al nodo destino:
def es_factura(state: State) -> bool:
return state["categoria"] == "factura"
builder.add_conditional_edges(
"clasificar",
es_factura,
{True: "responder_factura", False: "responder_tecnico"},
)
Esto mantiene la función de routing limpia (devuelve un booleano) y deja el mapeo a nodos en un solo lugar.
Ciclos: volver atrás para reintentar
Aquí está el superpoder que una cadena no tiene. Como una arista condicional puede apuntar a un nodo anterior, puedes crear un ciclo: intentar algo, evaluarlo y, si no sirve, volver a intentar.
from typing import Literal
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
intentos: int
aprobado: bool
def trabajar(state: State) -> dict:
intentos = state["intentos"] + 1
# Simulamos que recién al tercer intento queda bien:
return {"intentos": intentos, "aprobado": intentos >= 3}
def revisar(state: State) -> Literal["trabajar", "__end__"]:
# Si no está aprobado, vuelve a "trabajar". Si sí, termina.
return "__end__" if state["aprobado"] else "trabajar"
builder = StateGraph(State)
builder.add_node("trabajar", trabajar)
builder.add_edge(START, "trabajar")
builder.add_conditional_edges("trabajar", revisar)
graph = builder.compile()
print(graph.invoke({"intentos": 0, "aprobado": False}))
# → {'intentos': 3, 'aprobado': True}
El nodo trabajar se ejecuta una y otra vez hasta que la condición se cumple. Ese "__end__" es el nombre interno del nodo END (puedes devolver el string "__end__" o importar y usar END).
START ──► trabajar ──► ? ──(aprobado)──► END
▲ │
└─────────┘ (no aprobado: reintenta)
Cuidado: el ciclo infinito y el recursion_limit
Un ciclo mal diseñado nunca termina. Para protegerte, LangGraph corta la ejecución tras un número máximo de pasos (por defecto 25) y lanza un error. Puedes ajustarlo, pero si lo alcanzas casi siempre es señal de un bug en tu condición de salida:
graph.invoke({"intentos": 0, "aprobado": False}, {"recursion_limit": 10})
Siempre asegúrate de que tu router tenga un camino claro hacia END. En el ejemplo, el contador de intentos garantiza que en algún momento se apruebe. Sin una condición de salida real, el recursion_limit es tu red de seguridad, no tu plan.
Errores comunes
- Router que devuelve un nombre de nodo que no existe. El string debe coincidir exactamente con un
add_node(o ser"__end__"). - Olvidar terminar las ramas. Cada rama debe llevar eventualmente a
END, o el grafo no sabe cuándo parar. - Ciclo sin condición de salida. Si la condición nunca cambia el estado hacia la salida, chocarás con el
recursion_limit. - Usar
add_edgecuando queríasadd_conditional_edges.add_edgees fijo; para decidir necesitas la versión condicional.
Cierre del bloque básico
Con esto ya sabes los cuatro pilares de LangGraph: estado, nodos, aristas y routing con ciclos. Con solo esto puedes construir agentes que deciden y reintentan — la base de todo lo que viene.
La frase para llevarse
“La arista normal ejecuta; la arista condicional decide. Y una arista condicional que apunta hacia atrás es un agente que aprende a reintentar.”
Qué viene
Arranca el bloque intermedio (Módulo 5): conectamos un LLM de verdad y le damos tools con el patrón ReAct, para que el agente no solo decida el flujo, sino que actúe sobre el mundo.
Material relacionado
Preguntas sobre este curso
Regístrate gratis para hacer preguntas y guardar tu progreso.
✉ Entérate de lo nuevo
Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.