GenAINextVercel AI SDKLangchainGoogle Gemini

Arquitecturas agentes avanzadas con LangGraph.js. Parte 1

Antonio Pérez
2025-09-13
7
Arquitecturas agentes avanzadas con LangGraph.js. Parte 1

Introducción: Las limitaciones de los ejecutores lineales

En el capítulo anterior, adoptamos el AgentExecutor de LangChain. Esta abstracción es potente y nos permitió simplificar enormemente nuestro código. Sin embargo, impone un modelo de ejecución específico y relativamente rígido: el bucle ReAct (Razonamiento -> Acción -> Observación -> ...). Este ciclo, aunque efectivo para muchas tareas, es fundamentalmente lineal y cíclico.

Para un ingeniero de sistemas experto, esta rigidez presenta varias limitaciones a medida que los requisitos se vuelven más complejos:

  1. Falta de control de flujo explícito: Es difícil forzar al agente a seguir una secuencia de pasos específica. Por ejemplo, ¿cómo garantizamos que un agente de e-commerce siempre verifique el inventario antes de procesar un pago, incluso si el LLM intenta hacerlo en el orden incorrecto?
  2. Dificultad en la lógica condicional compleja: ¿Qué pasa si el siguiente paso depende de una combinación de resultados de múltiples herramientas? El AgentExecutor estándar no maneja bien los "joins" o las bifurcaciones complejas en el flujo de control.
  3. El desafío de los sistemas Multi-Agente: El AgentExecutor modela un único agente. ¿Cómo orquestamos una colaboración entre múltiples agentes especializados? Por ejemplo, un agente "Planificador" que descompone una tarea y la delega a agentes "Ejecutores" que trabajan en paralelo.
  4. Visibilidad y depuración: Aunque LangSmith nos da una gran trazabilidad, el flujo de control interno del AgentExecutor puede ser una "caja negra". Es difícil visualizar y depurar la lógica de enrutamiento del agente.

LangGraph.js es la solución de LangChain a estos problemas. Es una biblioteca para construir agentes stateful (con estado) y multi-actores utilizando grafos. Nos permite modelar el flujo de trabajo de un agente no como un bucle implícito, sino como un Grafo de estados dirigido.

En este capítulo, desmontaremos nuestro AgentExecutor y reconstruiremos su lógica (y mucho más) usando los primitivos de LangGraph. Nuestro objetivo es obtener un control total y explícito sobre cada paso del ciclo de vida del agente, permitiendo arquitecturas mucho más sofisticadas y fiables.

El modelo mental de LangGraph.js: Agentes como máquinas de estado

LangGraph nos obliga a pensar en nuestro agente como una máquina de estados finitos. El "estado" de la conversación o tarea evoluciona a medida que se transita por el grafo.

Los componentes fundamentales de un grafo en LangGraph son:

  1. Estado (State): Un objeto global que representa el estado actual de la ejecución. Es la única fuente de verdad. Típicamente, contiene el historial de mensajes, pero puede enriquecerse con cualquier dato estructurado que necesitemos (ej. plan-de-accion, resultados-de-herramientas, usuario-validado).
  2. Nodos (Nodes): Son funciones o Runnables de LangChain que representan una unidad de trabajo. Un nodo recibe el estado actual, realiza una acción (llamar a un LLM, ejecutar una herramienta, etc.) y devuelve un objeto que actualiza el estado.
  3. Aristas (Edges): Son las conexiones entre los nodos. Definen el flujo de control. Una arista puede ser:
  • Incondicional: Siempre va del Nodo A al Nodo B.
  • Condicional: Contiene una función de enrutamiento que recibe el estado actual y decide a qué nodo (o nodos) ir a continuación. Aquí reside la mayor parte del poder de LangGraph.

El ciclo de ejecución de LangGraph:

La ejecución de un grafo es un ciclo continuo:

  1. Se invoca al grafo con un estado inicial.
  2. Se ejecuta el nodo de entrada (START).
  3. Se sigue la arista desde el nodo actual.
  4. Si es una arista condicional, se ejecuta la función de enrutamiento para decidir el siguiente nodo.
  5. Se ejecuta el siguiente nodo, que actualiza el estado.
  6. El ciclo se repite desde el paso 3 hasta que se alcanza un nodo final (END).

Diagrama Conceptual de un Agente ReAct en LangGraph:

alt text

Este diagrama representa exactamente la lógica que estaba oculta dentro del AgentExecutor. Con LangGraph, esta lógica se vuelve explícita, visible y modificable.

El poder de las aristas condicionales: Control de flujo inteligente

La verdadera flexibilidad de LangGraph proviene de las aristas condicionales. Una función de enrutamiento en una arista condicional es una simple función que recibe el estado y devuelve un string (o una lista de strings) que corresponde al nombre del siguiente nodo a ejecutar.

typescript
function shouldContinue(state: AgentState): "action" | "__end__" {
  const lastMessage = state.messages[state.messages.length - 1];
  // Si la última respuesta de la IA contiene llamadas a herramientas, vamos al nodo 'action'.
  if (lastMessage.tool_calls?.length) {
    return "action";
  }
  // De lo contrario, hemos terminado.
  return "__end__";
}

// ... en la definición del grafo ...
graph.addConditionalEdges("agent", shouldContinue);

Esto nos permite implementar lógicas de enrutamiento complejas que un AgentExecutor no puede manejar:

  • Validación de salida: Podemos tener un nodo "Validador" después del nodo del LLM. La arista condicional podría enrutar de nuevo al LLM con un mensaje de error si la validación falla, o continuar al siguiente paso si tiene éxito.
  • Planificación y ejecución separadas: Podemos tener un nodo "Planificador" que genera un plan de varios pasos. La arista condicional podría entonces enrutar a un bucle de nodos "Ejecutores" que procesan cada paso del plan.
  • Enrutamiento Multi-Agente: Podemos tener una arista condicional que actúa como un "supervisor", analizando el estado y decidiendo cuál de varios agentes especializados debe manejar la tarea a continuación.

Arquitecturas Multi-Agente con LangGraph.js

LangGraph es la herramienta natural para construir sistemas multi-agente, ya que cada agente puede ser modelado como un nodo (o un sub-grafo completo).

Patrón 1: Colaboración Secuencial

Un agente "Investigador" busca información y actualiza el estado. Luego, el flujo pasa a un agente "Escritor" que toma esa información del estado y redacta un informe.

alt text

Patrón 2: Supervisor / Enrutador de Agentes

Este es un patrón mucho más potente.

  1. Nodo supervisor: Un LLM que actúa como gestor de proyectos. Recibe la tarea principal. Su única función es analizar la tarea y el estado actual, y decidir qué agente es el más adecuado para el siguiente paso.
  2. Nodos de agentes especializados: Cada uno es un agente (posiblemente un sub-grafo ReAct completo) especializado en una tarea: AgenteDeBusqueda, AgenteDeCodigo, AgenteDeEmail.
  3. Flujo de control:
  • La tarea entra al Supervisor.
  • El Supervisor decide delegar a AgenteDeBusqueda. La arista condicional enruta el flujo a ese nodo.
  • AgenteDeBusqueda ejecuta su ciclo ReAct y devuelve el resultado al estado.
  • La arista de salida de AgenteDeBusqueda siempre vuelve al Supervisor.
  • El Supervisor revisa el nuevo estado (con los resultados de la búsqueda) y decide el siguiente paso: quizás ahora es el turno de AgenteDeEmail para enviar los resultados.

Este patrón nos permite construir "equipos" de agentes de IA, orquestados por un supervisor, lo que resulta en una modularidad y especialización que aumenta drásticamente la fiabilidad del sistema general.

Diagrama conceptual del supervisor:

alt text

Conclusión teórica

Hemos evolucionado nuestra comprensión de los agentes desde un bucle lineal implícito a un modelo de ejecución explícito y controlable basado en grafos. LangGraph.js nos devuelve el control como arquitectos de software, permitiéndonos diseñar, visualizar y depurar flujos de trabajo de IA de cualquier complejidad.

Al pensar en términos de Estado, Nodos y Aristas, podemos construir sistemas que son:

  • Más fiables: Al forzar secuencias de pasos lógicas y añadir validaciones.
  • Más potentes: Al permitir la colaboración entre múltiples agentes especializados.
  • Más mantenibles: Al descomponer la lógica compleja en unidades de trabajo modulares y conectadas de forma explícita.

En la sección práctica, tomaremos el agente de soporte técnico que refactorizamos con AgentExecutor y lo deconstruiremos y reconstruiremos una vez más, esta vez usando LangGraph.js. El resultado será un código que no solo es más potente, sino también mucho más claro sobre su propio funcionamiento interno.