Skip to content

StateGraph: Blaupause für deklarative Zustandsautomaten

源码版本1.2.9

Verantwortung

StateGraph ist die Klasse, mit der LangGraph-Nutzer am häufigsten direkt zu tun haben – sie ist ein Builder (builder), keine Laufzeit. Man registriert an ihr das State-Schema, die Knotenfunktionen, Kanten und bedingten Kanten und ruft dann .compile() auf, um sie in ein CompiledStateGraph (das von Pregel erbt) zu übersetzen. Sie selbst hat kein invoke / stream; um den Graphen laufen zu lassen, muss vorher kompiliert werden (Dokumentwarnung:139-144).

In der Architektur sitzt sie zwischen der Nutzer-API und der Pregel-Engine: Der Nutzer schreibt StateGraph(State).add_node(...).add_edge(...).compile(), und StateGraph ist dafür verantwortlich, jedes State-Feld in eine Channel-Instanz (channel) zu übersetzen, jeden Knoten in ein StateNodeSpec zu wickeln und jede Kante in den Sets edges / waiting_edges / branches abzulegen, bis beim Kompilieren CompiledStateGraph.attach_node / attach_edge / attach_branch die Übersetzung in PregelNode-Abonnements übernimmt (StateGraph Felder:201-213).

Die Kernkonvention von StateGraph ist: Die Signatur eines Knotens ist State -> Partial<State>, und an jedem State-Feld kann optional über Annotated[type, reducer] eine Reduce-Funktion angegeben werden; wenn mehrere Knoten dasselbe Feld schreiben, wird über reducer zusammengeführt statt überschrieben (Klassen-Docstring:131-138). Dieser reducer-Mechanismus ist der Ursprung der beiden Channel-Typen LastValue (Default: überschreiben) und BinaryOperatorAggregate (z. B. operator.add).

Entwurfsmotivation

Warum nicht einfach Funktionsaufrufketten schreiben, sondern der Umweg über «Zustandsautomat + Channel»?

  • Knoten sind entkoppelt: Ein Knoten kennt nur den State, nicht andere Knoten; wer wann läuft, wird durch Kanten bestimmt – eine Änderung des Ablaufs erfordert keine Änderung des Knotencodes. Das ist der zentrale Gewinn eines Zustandsautomaten (state machine).
  • reducer gibt parallelen Schreibvorgängen eine Definition: Schreiben mehrere Knoten im selben Superstep in das Feld messages und ist es als Annotated[list, operator.add] markiert, weiß die Engine, dass + anzuwenden ist statt «der spätere Write überschreibt den früheren» (reducer Doku:135-137). Felder ohne reducer verwenden den Channel LastValue; das Verhalten ist «mehrere Schreibvorgänge im selben Superstep werfen einen Fehler» oder «überschreiben», je nach Channel-Typ.
  • Validierung zur Build-Zeit: Beim compile führt validate() (validate:1116-1162) Prüfungen durch – «Start- und Endpunkt jeder Kante sind in nodes vorhanden», «START muss Startpunkt einer Kante sein», «Interrupt-Knoten existieren» – und fängt Fehler vor dem Lauf ab.
  • Schema-Trennung input/output/state: Derselbe Graph kann input_schema ≠ state_schema ≠ output_schema haben (schema Eingaben:217-221); eine schmale Außenschnittstelle bei breitem internen State ist ein gängiges Muster für Agenten, bei denen das LLM-gerichtete Eingabe-Schema nicht dem intern akkumulierten State entspricht.
  • Method Chaining: add_node / add_edge / add_conditional_edges geben alle Self zurück (Returns Self:749) – verkettete Aufrufe werden unterstützt, und der Aufbau eines Graphen liest sich wie eine Konfigurationsdatei statt wie eine imperative Sequenz.

Schlüsseldateien

  • StateGraph Klassendefinition:130-144Generic[StateT, ContextT, InputT, OutputT]; der Docstring stellt klar «Builder, nicht direkt invoke-fähig».
  • Klassenfelder:201-213 — Die sieben zentralen Container edges / nodes / branches / channels / managed / schemas / waiting_edges.
  • __init__:215-269 — Empfängt state_schema / context_schema / input_schema / output_schema; alte Namen config_schema / input / output werden in neue Namen umgewandelt und mit Warnung versehen; über _addSchema werden die drei Schemas state/input/output in channels / managed registriert.
  • _add_schema:342-372 — Nutzt _get_channels, um aus einem Schema Channels + Managed Values abzuleiten; bei Konflikten ist alles außer LastValue ein Fehler.
  • validate:1116-1162 — Validierung vor dem compile: iteriert über alle Edge-Quellen/-Ziele, prüft das Vorhandensein von START, prüft, ob Interrupt-Knoten existieren; setzt am Ende self.compiled = True.
  • set_node_defaults:271-334 — Setzt Default-Policies für retry / cache / error_handler / timeout für den gesamten Graphen; niedrigere Priorität als explizite Parameter an add_node.
  • compile Signatur:1164-1217 — Empfängt checkpointer / store / cache / interrupt_before / interrupt_after / name etc. und gibt ein CompiledStateGraph zurück.
  • CompiledStateGraph:1391-1409 — Erbt von Pregel; fügt lediglich die Felder builder / schema_to_mapper und input/output-JSON-Schema-Methoden hinzu.
  • attach_node:1431-1470 — Übersetzt zur Compile-Zeit den StateNodeSpec in einen PregelNode und definiert _get_updates, das entscheidet, welche Channels aus dem Rückgabewert des Knotens beschrieben werden.
  • BranchSpec.from_path:83-120 — Übersetzt path (Runnable) und path_map von add_conditional_edges in ein Dict «Bedingung → Zielknoten»; ohne path_map wird versucht, aus dem Rückgabetyp Literal[...] abzuleiten.

Datenfluss

Nachstehend der zentrale Abschnitt, in dem StateGraph.__init__ das Schema als Channels registriert – hier findet die Übersetzung «State-Feld → Channel-Instanz» statt:

python
self.nodes = {}
self.edges = set()
self.branches = defaultdict(dict)
self.schemas = {}
self.channels = {}
self.managed = {}
self.compiled = False
self.waiting_edges = set()

self.state_schema = state_schema
self.input_schema = cast(type[InputT], input_schema or state_schema)
self.output_schema = cast(type[OutputT], output_schema or state_schema)
self.context_schema = context_schema

self._node_defaults: _NodeDefaults = _NodeDefaults()

self._add_schema(self.state_schema)
self._add_schema(self.input_schema, allow_managed=False)
self._add_schema(self.output_schema, allow_managed=False)

(Feldinitialisierung:251-269)

Jedes Schema wird von _add_schema in das Tripel (channels, managed, type_hints) zerlegt; channels geht ins Dict self.channels, managed ins self.managed. Bei wiederholter Registrierung desselben Keys ist nur LastValue kompatibel:

python
self.schemas[schema] = {**channels, **managed}
for key, channel in channels.items():
    if key in self.channels:
        if self.channels[key] != channel:
            if isinstance(channel, LastValue):
                pass
            else:
                raise ValueError(
                    f"Channel '{key}' already exists with a different type"
                )
    else:
        self.channels[key] = channel

(_add_schema Registrierlogik:353-364)

Grenzen und Fehler

  • StateGraph ist nicht direkt invoke-fähig (warning:139-144); es muss .compile() aufgerufen werden; die direkte Nutzung als Runnable lässt Laufzeitfelder wie nodes vermissen.
  • Konflikte bei State-Feldnamen werfen (channel Konflikt:360-362), es sei denn, beide sind LastValue – das verhindert inkonsistentes implizites Verhalten, wenn derselbe Key in zwei Schemas unterschiedliche reducer hat.
  • input_schema / output_schema dürfen keine Managed Channels haben (managed Prüfung:346-352); managed ist dem State vorbehalten, da diese Channels keine reinen Wert-Kanäle sind, sondern Objekte mit Lebenszyklus wie ContextManager.
  • Der alte Name config_schema ist veraltet (config_schema deprecated:224-231); seit v1.0 wird auf context_schema verwiesen, in 2.0 wird er entfernt.
  • compile() auf einem bereits kompilierten Graphen fügt nur eine Warnung hinzu, keinen Fehler (compiled warning:932-936); add_node / add_edge geben Adding ... to a graph that has already been compiled aus, ohne die bereits kompilierte Instanz zu beeinflussen – eine häufige Falle bei falscher Reihenfolge in verketteten Aufrufen.
  • validate verlangt mindestens eine Kante, die von START ausgeht (entrypoint Prüfung:1129-1132); Graph must have an entrypoint, sonst ist nicht klar, wo die Ausführung beginnen soll.

Zusammenfassung

StateGraph ist eine deklarative Blaupause für Zustandsautomaten: Man übergibt Schema, Knoten und Kanten, und es übersetzt sie in PregelNode + Channel-Abonnements, die Pregel ausführen kann. Seine Existenz erspart es dem Nutzer, direkt mit dem Actor-Modell von Pregel konfrontiert zu werden; er kümmert sich nur um drei Dinge – State-Felder, Knotensignaturen und Kanten.

Weiter geht es mit add_node / add_edge / add_conditional_edges, wie diese Blaupause mit Inhalt gefüllt wird, sowie mit compile, wie sie zu Pregel kompiliert wird. Das gesamte Ausführungsmodell steht in Pregel 引擎.

Siehe offizielle Dokumentation: LangGraph 文档 · README.