Inicio / Artículos / Aristas condicionales en LangGraph: enrutar un grafo con estado y decisiones

Aristas condicionales en LangGraph: enrutar un grafo con estado y decisiones

Aprenda cómo funcionan las aristas condicionales de LangGraph: escriba un router que guarde su decisión en el estado, asocie cada resultado a un nodo y envíe cada camino a donde debe ir.

1120 palabras

Antes de añadir decisiones, conviene repasar rápidamente la primera parte de esta serie sobre LangGraph. En esencia, LangGraph es un conjunto de funciones conectadas, cada una de las cuales se ocupa de una tarea. Tres piezas sostienen la idea.

El estado (State) es el objeto que recorre el grafo con toda la información. Cualquier nodo puede leer de él y escribir en él.

state = {
    'a': 2,
    'b': 3,
    'sum': None
}

Los nodos (Nodes) son funciones normales. Un nodo recibe el estado, hace algún trabajo y devuelve solo los valores que quiere cambiar.

def add(state):
    total = state['a'] + state['b']
    return {'sum': total}

Las aristas (Edges) deciden qué nodo se ejecuta a continuación, por ejemplo:

add → multiply → final

El estado viaja por ese camino mientras los nodos se ejecutan por turnos. Además, un nodo no tiene por qué ser una función de Python corriente, ya que puede envolver una llamada a un LLM. Sin embargo, hasta ahora todos los grafos han seguido una única ruta fija. La siguiente pregunta es cómo dejar que un grafo elija entre rutas, y la respuesta son las aristas condicionales.

La idea detrás de una arista condicional

Buena parte de la utilidad del software viene de tomar decisiones: sí o no, esto o aquello. Los grafos también lo necesitan, así que algunas funciones existen solo para decidir.

Suponga que un grafo saluda a alguien según la hora del día. Por la mañana, la salida es:

Good Morning

Por la noche, es:

Good Night

Como resultado, cambia la forma del grafo. Ya no es la línea recta

A → B → C

sino una bifurcación:

→ Morning → Good Morning
            /
User → Router
            \
             → Night → Good Night

Un router elige la rama. Eso, en pocas palabras, es una arista condicional.

Probarlo antes en Python puro

Antes de meter LangGraph, aquí está la misma idea como una función normal. Necesita la hora actual:

from datetime import datetime

Y la función en sí:

def router():    hour = datetime.now().hour

    if 5 <= hour < 12:
        return "Morning"
    else:
        return "Night"

Fíjese en lo poco que hace esta función. No realiza ninguna gran operación. Decide. Si la hora actual está entre las 5 de la mañana y el mediodía, devuelve

Morning

y en cualquier otro caso devuelve

Night

Dentro de un grafo, ese valor de retorno puede elegir el siguiente nodo. Hay además un segundo truco: la decisión puede guardarse en el estado. Añada un campo para ella:

state = {
    'time_per': None
}

Ahora el router puede escribir su elección en el estado. Recibe el estado como argumento:

def router(state):

Y dentro calcula la hora y registra el resultado:

    hour = datetime.now().hour

    if 5 <= hour < 12:
        return {'time_per': 'Morning'}
    else:
        return {'time_per': 'Night'}

Después, el estado contiene la decisión:

{
    'time_per': 'Morning'
}

Esa es la clave. El estado guarda información, y la arista condicional lee esa información para dirigir el grafo. Ocurren dos cosas una detrás de otra:

Router
  ↓
updates state
  ↓
time_per = "Morning"
  ↓
conditional edge checks it
  ↓
Morning node

Aquí es donde el estado y las aristas empiezan a colaborar.

Lo mismo en LangGraph

Primero, el estado necesita una definición con tipos. Importe TypedDict:

from typing import TypedDict

Después declare el esquema:

class State(TypedDict):
    time_per: str

A continuación vienen los nodos. El router decide si es de mañana o de noche, y necesita el reloj:

from datetime import datetime

El nodo router en sí:

def router(state):    hour = datetime.now().hour    if 5 <= hour < 12:
        return {'time_per': 'Morning'}
    else:
        return {'time_per': 'Night'}

Otros dos nodos se encargan de cada resultado. Uno cubre la mañana:

def morning(state):
    return {'time_per': 'Good Morning'}

El otro cubre la noche:

def night(state):
    return {'time_per': 'Good Night'}

Ahora viene la parte importante. LangGraph necesita una función que diga qué camino seguir. Recibe el estado:

def decide_time(state):

y simplemente devuelve la decisión guardada:

    return state['time_per']

Así que devuelve o bien

Morning

o bien

Night

Esa función se conecta a una arista condicional:

graph.add_conditional_edges(
    "router",
    decide_time,
    {
        "Morning": "morning",
        "Night": "night"
    }
)

Esto es lo que significa cada pieza. El comienzo de la llamada es

graph.add_conditional_edges(
    "router",

lo que le indica a LangGraph que, cuando termine el nodo router, el grafo no debe saltar a un nodo fijo. Toma el control una decisión. El siguiente argumento es

decide_time,

la función que toma la decisión. El último argumento es

{
    "Morning": "morning",
    "Night": "night"
}

una correspondencia entre cada decisión posible y el nodo que debe venir después. Si decide_time() devuelve

Morning

el grafo sigue

router → morning

y si devuelve

Night

el grafo sigue

router → night

El grafo completo queda así:

→ morning → END
                 /
START → router
                 \
                  → night → END

Esa es la diferencia entre los dos tipos de arista. Una arista normal siempre dice

A → B

sin excepciones, mientras que una arista condicional dice

A → ?

y deja que la decisión rellene el signo de interrogación.

Por qué importa

Aquí es donde LangGraph se vuelve interesante. El flujo funciona como un ciclo de dependencias:

updates
           State
             ↑
             |
          Router
             |
             ↓
     Conditional Edge
        ↙         ↘
   Morning        Night

Un nodo puede actualizar el estado. La función de enrutamiento puede leer ese estado. Y luego la función de enrutamiento decide qué nodo se ejecuta a continuación. El grafo deja de ser un montón de funciones conectadas y se convierte en uno en el que el estado actual puede cambiar su propio camino. Los flujos de trabajo complejos necesitan exactamente eso.

Un caso realista es un router de preguntas:

User Question
      ↓
   Classifier
      ↓
   State
      ↓
 ┌────┴─────┐
 ↓          ↓
Coding    General
 ↓          ↓
Code      Normal
Answer    Answer

El clasificador podría escribir algo así en el estado:

{
    'type': 'coding'
}

La arista condicional lee ese valor y decide si enviar la petición al nodo de programación o al general. En este punto, el flujo de trabajo ya no es una simple secuencia. Es un grafo que toma decisiones. El siguiente paso son los bucles, en los que un grafo vuelve atrás, repite un paso y lo intenta de nuevo.