StateGraph: el plano que declara la máquina de estados
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
messagesen el mismo superpaso y se usaAnnotated[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 canalLastValue, 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_edgesdevuelvenSelf(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-144—Generic[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— recibestate_schema/context_schema/input_schema/output_schema, renombra los campos antiguosconfig_schema/input/outputa los nuevos emitiendo warning, y llama a_add_schemapara registrar los tres schemas (state/input/output) enchannels/managed._add_schema:342-372— con_get_channelsextrae channels + managed values del schema; en conflicto, todo lo que no seaLastValueda 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 fijaself.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 deadd_node.compile 签名:1164-1217— recibecheckpointer/store/cache/interrupt_before/interrupt_after/namey devuelve elCompiledStateGraph.CompiledStateGraph:1391-1409— hereda dePregel; solo añade los camposbuilder/schema_to_mappery métodos para el JSON schema de entrada/salida.attach_node:1431-1470— en compilación traduceStateNodeSpecaPregelNodey define_get_updatespara decidir qué canales escribir a partir del valor de retorno del nodo.BranchSpec.from_path:83-120— traducepath(Runnable) +path_mapdeadd_conditional_edgesen un dict «condición → nodo destino»; si no se pasapath_map, intenta deducirlo del tipo de retornoLiteral[...].
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»:
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)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:
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] = channelLímites y fallos
StateGraphno se puedeinvokedirectamente(warning:139-144); hay que.compile()primero; usarla como Runnable directo faltaría a campos de runtime comonodes.- Conflicto de nombres de campo de estado da error(
channel 冲突:360-362), a menos que ambos seanLastValue— evita la inconsistencia de comportamiento implícito cuando «la misma key tiene reducers distintos en dos schemas». input_schema/output_schemano 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 comoContextManager.- El nombre antiguo
config_schemaestá deprecated(config_schema deprecated:224-231); desde v1.0 se avisa de usarcontext_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_edgeimprimenAdding ... 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