Skip to content

StateGraph: el plano que declara la máquina de estados

源码版本1.2.9

Responsabilidades

StateGraph es la clase con la que el usuario de LangGraph más se encuentra directamente — es un constructor (builder), no un runtime. Registras en ella el state schema, las funciones de nodo, los bordes (edge) y los bordes condicionales, y luego llamas a .compile() para compilarla en una instancia ejecutable CompiledStateGraph (que hereda de Pregel). Por sí misma no tiene invoke / stream; para correr el grafo hay que compilar primero(文档警告:139-144).

En la arquitectura se sitúa entre la API de usuario y el motor Pregel: el usuario escribe StateGraph(State).add_node(...).add_edge(...).compile() y StateGraph se encarga de traducir cada campo de estado en una instancia de canal (channel), envolver cada nodo en un StateNodeSpec y guardar cada borde en los tres conjuntos edges / waiting_edges / branches, para que en tiempo de compilación CompiledStateGraph.attach_node / attach_edge / attach_branch lo traduzcan a las relaciones de suscripción de PregelNode(StateGraph 字段:201-213).

La convención central de StateGraph es: la signatura del nodo es State -> Partial<State>, y cada campo de estado puede marcarse opcionalmente con Annotated[type, reducer] para indicar una función de reducción; cuando varios nodos escriben el mismo campo, se fusionan por el reducer en lugar de sobreescribir(类 docstring:131-138). Este mecanismo de reducer es el origen de los dos tipos de canal LastValue (por defecto, sobreescribe) y BinaryOperatorAggregate (operator.add, etc.).

Motivación de diseño

¿Por qué no escribir directamente una cadena de llamadas a funciones y montar en cambio este sistema de «máquina de estados + canales»?

  • Desacoplar nodos: un nodo solo conoce state, no a otros nodos; quién corre antes y quién después lo deciden los bordes, de modo que cambiar el flujo no toca el código de los nodos. Ese es el beneficio central de la máquina de estados (state machine).
  • El reducer da semántica a las escrituras paralelas: si varios nodos escriben el campo messages en el mismo superpaso y se usa Annotated[list, operator.add], el motor sabe que debe hacer + en lugar de «el último en escribir gana»(reducer 文档:135-137). Los campos sin reducer van al canal LastValue, cuyo comportamiento es «en el mismo superpaso, múltiples escrituras dan error» o «sobreescritura», según el tipo de canal.
  • Validación en construcción: al compilar, validate()(validate:1116-1162) comprueba que «el origen y el destino de cada borde estén en nodes», que «START sea el origen de algún borde», que «los nodos con interrupt existan», etc., frenando los errores antes de correr.
  • Separar schemas input/output/state: un mismo grafo puede tener input_schema ≠ state_schema ≠ output_schema(schema 入参:217-221), con una interfaz exterior estrecha y un state interno amplio — patrón habitual en agentes cuyo «schema de entrada orientado al LLM ≠ state acumulado interno».
  • Method chaining: add_node / add_edge / add_conditional_edges devuelven Self(Returns Self:749) — permite encadenar llamadas y construir el grafo como un fichero de configuración, no como una secuencia imperativa.

Archivos clave

  • StateGraph 类定义:130-144Generic[StateT, ContextT, InputT, OutputT]; el docstring deja claro «builder, no se puede invocar directamente».
  • 类字段:201-213 — siete contenedores centrales: edges / nodes / branches / channels / managed / schemas / waiting_edges.
  • __init__:215-269 — recibe state_schema / context_schema / input_schema / output_schema, renombra los campos antiguos config_schema / input / output a los nuevos emitiendo warning, y llama a _add_schema para registrar los tres schemas (state/input/output) en channels / managed.
  • _add_schema:342-372 — con _get_channels extrae channels + managed values del schema; en conflicto, todo lo que no sea LastValue da error.
  • validate:1116-1162 — validación pre-compilación: recorre todos los edge source/target, comprueba que START exista y que existan los nodos con interrupt; al final fija self.compiled = True.
  • set_node_defaults:271-334 — fija políticas por defecto de retry / cache / error_handler / timeout para todo el grafo, con prioridad menor que los parámetros explícitos de add_node.
  • compile 签名:1164-1217 — recibe checkpointer / store / cache / interrupt_before / interrupt_after / name y devuelve el CompiledStateGraph.
  • CompiledStateGraph:1391-1409 — hereda de Pregel; solo añade los campos builder / schema_to_mapper y métodos para el JSON schema de entrada/salida.
  • attach_node:1431-1470 — en compilación traduce StateNodeSpec a PregelNode y define _get_updates para decidir qué canales escribir a partir del valor de retorno del nodo.
  • BranchSpec.from_path:83-120 — traduce path (Runnable) + path_map de add_conditional_edges en un dict «condición → nodo destino»; si no se pasa path_map, intenta deducirlo del tipo de retorno Literal[...].

Flujo de datos

El siguiente es el tramo central donde StateGraph.__init__ registra el schema como canales — es donde ocurre la traducción «campo de estado → instancia de canal»:

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)

(字段初始化:251-269)

Cada schema se descompone en _add_schema en un trío (channels, managed, type_hints); channels entra en el dict self.channels y managed en self.managed. Si se registra la misma key dos veces, solo se admite compatibilidad con LastValue:

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 注册逻辑:353-364)

Límites y fallos

  • StateGraph no se puede invoke directamente(warning:139-144); hay que .compile() primero; usarla como Runnable directo faltaría a campos de runtime como nodes.
  • Conflicto de nombres de campo de estado da error(channel 冲突:360-362), a menos que ambos sean LastValue — evita la inconsistencia de comportamiento implícito cuando «la misma key tiene reducers distintos en dos schemas».
  • input_schema / output_schema no pueden tener managed channels(managed 检查:346-352); managed es exclusivo de state porque no son canales simples de valor, sino objetos con ciclo de vida como ContextManager.
  • El nombre antiguo config_schema está deprecated(config_schema deprecated:224-231); desde v1.0 se avisa de usar context_schema, y se eliminará en 2.0.
  • compile() sobre un grafo ya compilado solo hace warning, no error(compiled warning:932-936); add_node / add_edge imprimen Adding ... to a graph that has already been compiled, pero los cambios no se reflejan en la instancia ya compilada — trampa habitual al equivocarse en el orden del encadenamiento.
  • validate exige al menos un borde saliendo de START(entrypoint 检查:1129-1132); Graph must have an entrypoint, si no, no se sabe por dónde empezar a correr.

Resumen

StateGraph es el plano declarativo de la máquina de estados: le das schema y nodos + bordes, y ella los traduce a PregelNode + suscripciones de canal que Pregel puede ejecutar. Su existencia evita que el usuario tenga que enfrentarse directamente al modelo de actores de Pregel y le permite centrarse en tres cosas: «campos de state + signatura de nodo + bordes».

Sigue con add_node / add_edge / add_conditional_edges para ver cómo se llena de contenido este plano, y con compile para ver cómo se compila en un Pregel. El modelo de ejecución global en Motor Pregel.

Véase la documentación oficial: documentación de LangGraph · README