StateGraph : le blueprint déclaratif d'une machine à états
Responsabilités
StateGraph est la classe avec laquelle les utilisateurs de LangGraph interagissent le plus directement — c'est un builder, pas un runtime. Vous y enregistrez le schéma d'état, les fonctions de nœuds, les arêtes et les arêtes conditionnelles, puis vous appelez .compile() pour le compiler en un CompiledStateGraph (qui hérite de Pregel) exécutable. Il n'a pas lui-même de invoke / stream : pour exécuter le graphe, il faut d'abord le compiler (avertissement documentation:139-144).
Dans l'architecture, il se situe entre l'API utilisateur et le moteur Pregel : l'utilisateur écrit StateGraph(State).add_node(...).add_edge(...).compile(), et StateGraph traduit chaque champ d'état en instance de canal (channel), enveloppe chaque nœud dans un StateNodeSpec, et range chaque arête dans les trois collections edges / waiting_edges / branches, en attendant la compilation pour confier le tout à CompiledStateGraph.attach_node / attach_edge / attach_branch qui les traduit en abonnements de PregelNode (champs de StateGraph:201-213).
La convention centrale de StateGraph est que la signature d'un nœud est State -> Partial<State>, et que chaque champ d'état peut optionnellement être annoté avec Annotated[type, reducer] pour déclarer une fonction de réduction ; lorsque plusieurs nœuds écrivent simultanément le même champ, ils sont fusionnés selon le reducer plutôt qu'écrasés (docstring de classe:131-138). Ce mécanisme de reducer est l'origine des deux types de canaux LastValue (écrasement par défaut) et BinaryOperatorAggregate (par exemple operator.add).
Motivation de conception
Pourquoi ne pas écrire directement une chaîne d'appels de fonctions, et construire tout cet édifice « machine à états + canaux » ?
- Découplage entre nœuds : un nœud ne connaît que l'état, pas les autres nœuds ; l'ordre d'exécution est décidé par les arêtes, donc modifier le flux ne touche pas le code des nœuds. C'est le bénéfice central d'une machine à états (state machine).
- Le reducer donne une sémantique définie aux écritures parallèles : si plusieurs nœuds écrivent le champ
messagesdans le même superpas (superstep) avecAnnotated[list, operator.add], le moteur sait qu'il doit faire+au lieu d'« écraser » (doc reducer:135-137) ; un champ sans reducer utilise un canalLastValuedont le comportement est « erreur si plusieurs écritures dans le même superpas » ou « écrasement », selon le type de canal. - Validation à la construction : à la compilation,
validate()(validate:1116-1162) vérifie que « source et cible de chaque arête sont bien dans nodes », que « START est bien la source d'une arête », que « les nœuds d'interruption existent », etc., pour bloquer les erreurs avant l'exécution. - Séparation des schémas input/output/state : un même graphe peut avoir
input_schema ≠ state_schema ≠ output_schema(schémas en entrée:217-221), avec une interface externe étroite et un état interne large — un motif courant pour écrire des agents où le schéma d'entrée orienté LLM diffère de l'état accumulé en interne. - Method chaining :
add_node/add_edge/add_conditional_edgesrenvoient tousSelf(Returns Self:749) — ils supportent le chaînage, et construire un graphe ressemble à un fichier de configuration plutôt qu'à une séquence impérative.
Fichiers clés
définition de la classe StateGraph:130-144—Generic[StateT, ContextT, InputT, OutputT], documenté explicitement comme « builder, ne peut pas être invoqué directement ».champs de classe:201-213— les sept conteneurs centrauxedges/nodes/branches/channels/managed/schemas/waiting_edges.__init__:215-269— reçoitstate_schema/context_schema/input_schema/output_schema, convertit les anciens nomsconfig_schema/input/outputen nouveaux noms avec avertissement, puis appelle_add_schemapour enregistrer les trois schémas state/input/output danschannels/managed._add_schema:342-372— utilise_get_channelspour dériver les canaux et managed values du schéma ; en cas de conflit, tout saufLastValuedéclenche une erreur.validate:1116-1162— validation pré-compilation : parcourt toutes les sources/cibles d'arêtes, vérifie la présence de START et des nœuds d'interruption, puis poseself.compiled = True.set_node_defaults:271-334— fixe des stratégies retry / cache / error_handler / timeout par défaut pour tout le graphe, priorité plus basse que les paramètres explicites deadd_node.signature compile:1164-1217— reçoitcheckpointer/store/cache/interrupt_before/interrupt_after/name, etc., renvoie unCompiledStateGraph.CompiledStateGraph:1391-1409— hérite dePregel, ajoute simplement les champsbuilder/schema_to_mapperet les méthodes de JSON schema d'input/output.attach_node:1431-1470— à la compilation, traduit unStateNodeSpecenPregelNodeet définit_get_updatespour décider quels canaux écrire depuis la valeur de retour du nœud.BranchSpec.from_path:83-120— traduit lepath(Runnable) +path_mapdeadd_conditional_edgesen un dict « condition -> nœud cible » ; lorsquepath_mapn'est pas fourni, tente de le déduire du type de retourLiteral[...].
Flux de données
Voici le cœur de StateGraph.__init__ qui enregistre les schémas comme canaux — c'est ici qu'a lieu la traduction « champ d'état -> instance de canal » :
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)(initialisation des champs:251-269)
Chaque schéma est éclaté par _add_schema en un triplet (channels, managed, type_hints) ; les canaux vont dans le dict self.channels, les managed dans self.managed, et lorsqu'une même clé est enregistrée deux fois, seuls les LastValue sont acceptés en compatibilité :
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(logique d'enregistrement _add_schema:353-364)
Limites et échecs
StateGraphne peut pas êtreinvokedirectement (warning:139-144), il faut passer par.compile(); utilisé comme Runnable tel quel, il lui manque les champs runtime commenodes.- Conflit de noms de champs d'état = erreur (
conflit de canal:360-362), sauf si les deux sont desLastValue— pour éviter une incohérence implicite lorsqu'une même clé a des reducers différents dans deux schémas. input_schema/output_scheman'acceptent pas de managed channels (vérification managed:346-352), les canaux managés étant réservés à state : ce ne sont pas de simples canaux de valeur, mais des objets à cycle de vie commeContextManager.- L'ancien nom
config_schemaest obsolète (config_schema deprecated:224-231), depuis v1.0 il faut utilisercontext_schema, et il sera retiré en 2.0. compile()suivi d'un ajout de nœud ne fait qu'un warning, pas d'erreur (compiled warning:932-936),add_node/add_edgeaffichentAdding ... to a graph that has already been compiled, mais la modification n'est pas répercutée sur l'instance compilée — piège classique d'ordre de chaînage.- validate exige au moins une arête partant de START (
vérification entrypoint:1129-1132),Graph must have an entrypoint, sinon l'exécution ne sait pas où démarrer.
Résumé
StateGraph est le blueprint déclaratif d'une machine à états : vous lui fournissez le schéma et des nœuds + arêtes, et il les traduit en PregelNode + abonnements de canaux exécutables par Pregel. Son existence épargne à l'utilisateur le modèle d'actors de Pregel pour ne se concentrer que sur trois choses : champs d'état, signature de nœud et arêtes.
Continuer avec add_node / add_edge / add_conditional_edges pour remplir ce blueprint, et compile pour voir comment le compiler en Pregel. Le modèle d'exécution global est dans moteur Pregel.
Voir la documentation officielle : LangGraph docs · README.