Startseite / Artikel / Bedingte Kanten in LangGraph: Einen Graphen mit State und Entscheidungen steuern

Bedingte Kanten in LangGraph: Einen Graphen mit State und Entscheidungen steuern

So funktionieren bedingte Kanten in LangGraph: einen Router schreiben, der seine Entscheidung im State speichert, Ergebnisse auf Knoten abbilden und jeden Pfad dorthin schicken, wo er hingehört.

1120 Wörter

Bevor wir Entscheidungen hinzufügen, hilft eine kurze Auffrischung des ersten Teils dieser LangGraph-Serie. Im Kern ist LangGraph eine Reihe verbundener Funktionen, von denen jede eine Aufgabe übernimmt. Drei Bausteine tragen die Idee.

State ist das Objekt, das durch den Graphen wandert und alle Informationen enthält. Jeder Knoten kann daraus lesen und hineinschreiben.

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

Knoten (Nodes) sind gewöhnliche Funktionen. Ein Knoten bekommt den State, erledigt eine Arbeit und gibt nur die Werte zurück, die er ändern will.

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

Kanten (Edges) legen fest, welcher Knoten als Nächstes läuft, zum Beispiel:

add → multiply → final

Der State wandert diesen Pfad entlang, während die Knoten nacheinander laufen. Ein Knoten muss übrigens keine gewöhnliche Python-Funktion sein, er kann auch einen LLM-Aufruf kapseln. Bisher ist allerdings jeder Graph einer einzigen festen Route gefolgt. Die nächste Frage ist, wie ein Graph zwischen Routen wählen kann, und die Antwort heißt bedingte Kanten.

Die Idee hinter einer bedingten Kante

Software ist zu einem großen Teil deshalb nützlich, weil sie Entscheidungen trifft: ja oder nein, dies oder das. Graphen brauchen das auch, deshalb gibt es Funktionen, die ausschließlich entscheiden.

Angenommen, ein Graph begrüßt jemanden je nach Tageszeit. Am Morgen lautet die Ausgabe:

Good Morning

In der Nacht lautet sie:

Good Night

Dadurch verändert sich die Form des Graphen. Er ist nicht mehr die gerade Linie

A → B → C

sondern eine Gabelung:

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

Ein Router wählt den Zweig. Das ist, kurz gesagt, eine bedingte Kante.

Zuerst in reinem Python ausprobieren

Bevor LangGraph ins Spiel kommt, hier dieselbe Idee als gewöhnliche Funktion. Sie braucht die aktuelle Uhrzeit:

from datetime import datetime

Und die Funktion selbst:

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

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

Beachten Sie, wie wenig diese Funktion tut. Sie führt keine große Operation aus. Sie entscheidet. Liegt die aktuelle Stunde zwischen 5 Uhr morgens und Mittag, gibt sie Folgendes zurück:

Morning

und in allen anderen Fällen

Night

In einem Graphen kann dieser Rückgabewert den nächsten Knoten bestimmen. Es gibt noch einen zweiten Kniff: Die Entscheidung lässt sich im State speichern. Fügen Sie dafür ein Feld hinzu:

state = {
    'time_per': None
}

Jetzt kann der Router seine Wahl in den State schreiben. Er bekommt den State als Argument:

def router(state):

Darin berechnet er die Stunde und hält das Ergebnis fest:

    hour = datetime.now().hour

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

Danach enthält der State die Entscheidung:

{
    'time_per': 'Morning'
}

Das ist der entscheidende Punkt. Der State speichert Informationen, und die bedingte Kante liest diese Informationen, um den Graphen zu lenken. Zwei Dinge passieren nacheinander:

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

Hier fangen State und Kanten an, zusammenzuarbeiten.

Dasselbe in LangGraph

Zunächst braucht der State eine typisierte Definition. Importieren Sie TypedDict:

from typing import TypedDict

Deklarieren Sie dann das Schema:

class State(TypedDict):
    time_per: str

Als Nächstes kommen die Knoten. Der Router entscheidet, ob Morgen oder Nacht ist, und braucht dafür die Uhr:

from datetime import datetime

Der Router-Knoten selbst:

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

Zwei weitere Knoten behandeln jeweils ein Ergebnis. Einer kümmert sich um den Morgen:

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

Der andere um die Nacht:

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

Jetzt zum wichtigen Teil. LangGraph braucht eine Funktion, die sagt, welchem Pfad gefolgt werden soll. Sie bekommt den State:

def decide_time(state):

und gibt einfach die gespeicherte Entscheidung zurück:

    return state['time_per']

Sie liefert also entweder

Morning

oder

Night

Diese Funktion wird in eine bedingte Kante eingesetzt:

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

Das bedeuten die einzelnen Teile. Der Anfang des Aufrufs lautet

graph.add_conditional_edges(
    "router",

und sagt LangGraph, dass der Graph nach Abschluss des Knotens router nicht zu einem festen Knoten springen soll. Stattdessen übernimmt eine Entscheidung. Das nächste Argument ist

decide_time,

die Funktion, die die Entscheidung trifft. Das letzte Argument ist

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

eine Zuordnung jeder möglichen Entscheidung zu dem Knoten, der folgen soll. Gibt decide_time() Folgendes zurück:

Morning

folgt der Graph

router → morning

und gibt sie Folgendes zurück:

Night

folgt der Graph

router → night

Der vollständige Graph sieht jetzt so aus:

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

Das ist der Unterschied zwischen beiden Arten von Kanten. Eine normale Kante sagt immer

A → B

ohne Ausnahme, während eine bedingte Kante sagt

A → ?

und die Entscheidung das Fragezeichen ausfüllen lässt.

Warum das wichtig ist

Hier wird LangGraph spannend. Der Ablauf funktioniert wie ein Kreislauf von Abhängigkeiten:

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

Ein Knoten kann den State aktualisieren. Die Routing-Funktion kann diesen State lesen. Und dann entscheidet die Routing-Funktion, welcher Knoten als Nächstes läuft. Der Graph ist dann kein Haufen verbundener Funktionen mehr, sondern einer, in dem der aktuelle State seinen eigenen Pfad ändern kann. Genau das brauchen komplexe Workflows.

Ein realistischer Fall ist ein Router für Fragen:

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

Der Klassifikator könnte etwa Folgendes in den State schreiben:

{
    'type': 'coding'
}

Die bedingte Kante liest diesen Wert und entscheidet, ob die Anfrage an den Coding-Knoten oder an den allgemeinen Knoten geht. An dieser Stelle ist der Workflow keine einfache Abfolge mehr. Er ist ein Graph, der Entscheidungen trifft. Der nächste Schritt sind Schleifen, bei denen ein Graph zurückgeht, einen Schritt wiederholt und es erneut versucht.