← Todo el material
Curso Avanzado 20 de agosto de 2026

LangGraph — Módulo 12: patrones avanzados

Módulo final del curso de LangGraph: map-reduce con Send, reflexión (crítico y refinamiento), planificación con plan-and-execute y cómo evaluar un agente para saber si realmente mejoró.

☰ Índice del contenido

Módulo final del Curso completo de LangGraph. Ya tienes todas las piezas: estado, nodos, routing, tools, memoria, pausas, streaming, multi-agente y producción. Este módulo no introduce mecanismos nuevos — muestra cómo esas piezas se combinan en cuatro patrones que aparecen una y otra vez en agentes reales.

La tesis del módulo

Los patrones avanzados no son APIs nuevas: son formas del grafo. Map-reduce es un ciclo abierto en abanico; la reflexión es un ciclo con un juez; la planificación es un ciclo con una lista. Si entendiste el Módulo 4, ya entendiste los tres. Lo que sí es nuevo —y es lo que separa un prototipo de un producto— es el cuarto: medir.

Patrón 1: map-reduce con Send

Un nodo genera una lista de N cosas y quieres procesar cada una en paralelo. El problema: al escribir el grafo no sabes cuánto vale N.

Send resuelve eso. Una arista condicional puede devolver una lista de Send, y cada uno lanza el nodo destino con su propio pedazo de estado:

from operator import add
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.types import Send

class Estado(TypedDict):
    temas: list[str]
    resumenes: Annotated[list[str], add]   # ← el reducer es obligatorio aquí
    informe: str

class EstadoTema(TypedDict):
    tema: str

def procesar_tema(state: EstadoTema) -> dict:
    return {"resumenes": [f"Resumen de {state['tema']}"]}

def repartir(state: Estado):
    # Un Send por cada tema: N ramas paralelas decididas en tiempo de ejecución
    return [Send("procesar_tema", {"tema": t}) for t in state["temas"]]

def consolidar(state: Estado) -> dict:
    return {"informe": "\n".join(state["resumenes"])}

builder = StateGraph(Estado)
builder.add_node("procesar_tema", procesar_tema)
builder.add_node("consolidar", consolidar)
builder.add_conditional_edges(START, repartir, ["procesar_tema"])
builder.add_edge("procesar_tema", "consolidar")
builder.add_edge("consolidar", END)
                      ┌──► procesar_tema ──┐
 START ──► repartir ──┼──► procesar_tema ──┼──► consolidar
                      └──► procesar_tema ──┘        │
                          N ramas en paralelo       ▼
                                                   END

Dos detalles que hacen que esto funcione o falle:

  1. El reducer no es opcional. Sin Annotated[list[str], add], las N ramas escriben resumenes a la vez y se pisan entre sí. Es el Módulo 3 volviendo a cobrar.
  2. El nodo destino recibe un estado propio, no el del padre. Por eso procesar_tema declara EstadoTema con solo tema. Lo que devuelve sí se mezcla con el estado principal.

Es el patrón para procesar 40 documentos, evaluar 10 candidatos o resumir 15 secciones. El tiempo total es el de la rama más lenta, no la suma.

Ojo con el rate limit. 40 Send son 40 llamadas al modelo simultáneas. Aquí es donde el RetryPolicy del Módulo 11 deja de ser opcional.

Patrón 2: reflexión

Un modelo produce mejor resultado si revisa su propio trabajo. La reflexión son dos nodos —uno genera, otro critica— y un ciclo entre ellos.

from typing import Literal
from operator import add
from typing_extensions import TypedDict
from typing import Annotated

class Estado(TypedDict):
    tarea: str
    borrador: str
    criticas: Annotated[list[str], add]
    vueltas: int

def generar(state: Estado) -> dict:
    critica_previa = state["criticas"][-1] if state["criticas"] else ""
    prompt = f"Tarea: {state['tarea']}"
    if critica_previa:
        prompt += f"\nBorrador anterior:\n{state['borrador']}\nCorrige esto:\n{critica_previa}"
    return {"borrador": llm.invoke(prompt).content, "vueltas": state["vueltas"] + 1}

def criticar(state: Estado) -> dict:
    veredicto = llm.invoke(
        "Critica este texto. Sé exigente y concreto. "
        f"Si ya está listo, responde exactamente APROBADO.\n\n{state['borrador']}"
    ).content
    return {"criticas": [veredicto]}

def seguir(state: Estado) -> Literal["generar", "__end__"]:
    if "APROBADO" in state["criticas"][-1]:
        return "__end__"
    if state["vueltas"] >= 3:          # ← el freno duro, imprescindible
        return "__end__"
    return "generar"

builder.add_edge(START, "generar")
builder.add_edge("generar", "criticar")
builder.add_conditional_edges("criticar", seguir)
 START ──► generar ──► criticar ──► ? ──► END
              ▲                     │     aprobado o 3 vueltas
              │    hay que corregir │
              └─────────────────────┘

El contador de vueltas no es un detalle: un crítico puede no aprobar nunca. Sin tope, chocas con el recursion_limit — y peor, gastas tres o cuatro llamadas al modelo por vuelta.

Dos consejos que salen de usarlo:

  • El crítico necesita un criterio, no un “revísalo”. “Verifica que cada afirmación tenga fuente” produce críticas útiles; “mejóralo” produce cambios cosméticos infinitos.
  • La reflexión al menos duplica las llamadas al modelo (generar y criticar en cada vuelta). Úsala donde la calidad importe más que la latencia: un informe, sí; una respuesta de chat, casi nunca.

Patrón 3: planificación (plan-and-execute)

El ReAct del Módulo 5 decide un paso a la vez. Para tareas largas eso se pierde: en el paso ocho ya no recuerda el objetivo. La alternativa es planificar primero y ejecutar después.

class Estado(TypedDict):
    objetivo: str
    plan: list[str]
    hechos: Annotated[list[str], add]

def planificar(state: Estado) -> dict:
    pasos = llm.invoke(f"Divide en pasos concretos: {state['objetivo']}")
    return {"plan": parsear_pasos(pasos.content)}

def ejecutar_paso(state: Estado) -> dict:
    paso = state["plan"][0]
    resultado = agente_ejecutor.invoke({"messages": [{"role": "user", "content": paso}]})
    return {
        "plan": state["plan"][1:],          # sin reducer: sobrescribe (Módulo 3)
        "hechos": [resultado["messages"][-1].content],
    }

def quedan_pasos(state: Estado) -> Literal["ejecutar_paso", "replanificar"]:
    return "ejecutar_paso" if state["plan"] else "replanificar"

Fíjate en el contraste dentro del mismo estado: plan se sobrescribe (se va consumiendo) y hechos se acumula (con add). Elegir bien reducer por campo es lo que hace que este patrón sea corto de escribir.

La variante que de verdad funciona incluye replanificar: tras ejecutar, un nodo revisa si lo aprendido cambia el plan restante. Un plan rígido de diez pasos se rompe en el tercero, cuando la realidad no coincide con lo que el modelo supuso.

ReActPlan-and-execute
DecidePaso a pasoTodo al principio (y replanifica)
Fuerte enTareas cortas, exploratoriasTareas largas con estructura
Débil enPerder el hilo en tareas largasPlanes que la realidad invalida

Patrón 4: evaluación

Este es el que casi nadie implementa y el que más diferencia hace. Cambias un prompt, “se ve mejor”, lo despliegas. ¿Mejoró? No lo sabes.

Un agente sin evaluación no se puede mejorar de forma acumulativa: cada cambio es una apuesta.

Lo mínimo viable —y suficiente para empezar— es un archivo con casos y sus respuestas esperadas:

CASOS = [
    {"entrada": "¿Queda stock de monitor?", "espera_tool": "consultar_stock"},
    {"entrada": "Hola", "espera_tool": None},
    {"entrada": "Bórralo todo", "espera_tool": None},   # no debe llamar nada destructivo
]

def evaluar(agente):
    aciertos = 0
    for caso in CASOS:
        r = agente.invoke({"messages": [{"role": "user", "content": caso["entrada"]}]})
        tools_usadas = [
            tc["name"] for m in r["messages"] for tc in getattr(m, "tool_calls", [])
        ]
        esperado = caso["espera_tool"]
        ok = (esperado in tools_usadas) if esperado else not tools_usadas
        aciertos += ok
        if not ok:
            print(f"❌ {caso['entrada']} → usó {tools_usadas}, esperaba {esperado}")
    print(f"{aciertos}/{len(CASOS)}")

Veinte casos que corren en un minuto ya te dicen si el cambio de prompt rompió algo. Qué medir, en orden de utilidad:

  1. ¿Eligió la tool correcta? Objetivo, barato, y es donde más se degrada un agente al crecer.
  2. ¿Llegó al resultado esperado? Comparación exacta cuando se puede, o un modelo juez cuando la respuesta es texto libre.
  3. ¿Cuántos pasos y cuánto costó? Un agente que acierta en 12 pasos lo que antes hacía en 3 empeoró, aunque acierte.
  4. ¿Respetó los límites? El caso “Bórralo todo” del ejemplo: lo que no debe hacer importa tanto como lo que debe.

De dónde salen los casos: de tus fallos reales. Cada vez que el agente se equivoca en producción, ese caso entra al set. En tres meses tienes una suite que refleja tus problemas de verdad, no los que imaginaste al principio.

Las trazas del Módulo 8 son la materia prima: LangSmith permite convertir ejecuciones reales en casos de evaluación directamente.

Cierre del curso

Doce módulos después, el recorrido completo:

  • Básico (1-4) — estado, nodos, aristas, routing. La mecánica.
  • Intermedio (5-8) — tools, memoria, supervisión humana, streaming. El agente útil.
  • Avanzado (9-12) — multi-agente, memoria de largo plazo, producción, patrones. El sistema.

Si tuviera que reducir todo a una idea: LangGraph no te da un agente, te da control sobre uno. Cada módulo fue una forma distinta de la misma pregunta — ¿quién decide? El router, el modelo, el humano, el supervisor, la evaluación. Los frameworks que responden esa pregunta por ti son cómodos hasta el día en que necesitas cambiar la respuesta.

Ahora construye algo. El grafo del Módulo 2 con las tools del 5 y el checkpointer del 6 ya es un producto real; lo demás son mejoras que agregas cuando el uso te las pida.

Errores comunes

  • Send sin reducer en el campo donde se acumulan los resultados: las ramas se pisan.
  • Reflexión sin tope de vueltas. Ciclo caro y recursion_limit.
  • Un crítico sin criterio explícito. Cambios cosméticos infinitos.
  • Planificar tareas cortas. Le agregas una llamada al modelo a algo que ReAct resolvía en dos pasos.
  • Un plan sin replanificación. Se rompe en cuanto la realidad no coincide.
  • Evaluar “a ojo”. Es el error más caro de todo el curso, porque hace invisibles a los demás.

La frase para llevarse

“Los patrones avanzados son variaciones de un ciclo con criterio. Lo verdaderamente avanzado es tener con qué medir si el ciclo mejoró algo.”

Qué viene: fin del curso

Aquí termina el Curso completo de LangGraph. Tres caminos naturales para seguir: el Curso de Deploy de Agentes, que lleva el grafo que construiste a un repositorio con tests, CI/CD y observabilidad; el Curso de Evals, que convierte el patrón 4 de este módulo en una disciplina completa; y las clases del Harness, que tratan la gobernanza de agentes —qué exige aprobación y por qué— más allá de cualquier framework.

Material relacionado

LangGraphPythonMap-reduceReflexiónEvaluación

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.