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.
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.