构图阶段保存的是什么
builder保存节点定义、边、条件分支及各Schema对应的Channel。节点名称用于控制流定位,State字段名称用于数据合并,两者是不同维度。节点可以只读取输入Schema的一部分,也可以返回一部分更新。
输入、内部状态与输出可以分别声明Schema。这样可以让内部证据、检查结果与最终对外响应采用不同结构。但Schema本身不构成访问控制:如果把内部对象写入日志、流事件或错误信息,仍会泄漏。应该在接口层明确过滤输出。
compile的真实路径
源码:StateGraph.compile · L1177—L1401,固定提交41c9c22fdc83,完整函数。
def compile(
self,
checkpointer: Checkpointer = None,
*,
cache: BaseCache | None = None,
store: BaseStore | None = None,
interrupt_before: All | list[str] | None = None,
interrupt_after: All | list[str] | None = None,
debug: bool = False,
name: str | None = None,
transformers: Sequence[Callable[[tuple[str, ...]], Any]] | None = None,
) -> CompiledStateGraph[StateT, ContextT, InputT, OutputT]:
"""Compiles the `StateGraph` into a `CompiledStateGraph` object.
The compiled graph implements the `Runnable` interface and can be invoked,
streamed, batched, and run asynchronously.
Args:
checkpointer: A checkpoint saver object or flag.
If provided, this `Checkpointer` serves as a fully versioned "short-term memory" for the graph,
allowing it to be paused, resumed, and replayed from any point.
If `None`, it may inherit the parent graph's checkpointer when used as a subgraph.
If `False`, it will not use or inherit any checkpointer.
**Important**: When a checkpointer is enabled, you should pass a `thread_id`
in the config when invoking the graph:
```python
config = {"configurable": {"thread_id": "my-thread"}}
graph.invoke(inputs, config)
```
The `thread_id` is the key used to store and retrieve checkpoints. Use a
unique ID for independent runs, or reuse the same ID to accumulate state
across invocations (e.g., for conversation memory).
interrupt_before: An optional list of node names to interrupt before.
interrupt_after: An optional list of node names to interrupt after.
debug: A flag indicating whether to enable debug mode.
name: The name to use for the compiled graph.
transformers: Optional sequence of `StreamTransformer` classes or
configured factories. Classes and factories are instantiated
per run whenever `stream_events(version="v3")` / `astream_events(version="v3")` is called and are
propagated to subgraph scopes. Custom factories should follow
the standard `StreamTransformer` constructor shape by
accepting `scope` as their first argument. Appended after the
built-in stream transformers.
Returns:
CompiledStateGraph: The compiled `StateGraph`.
"""
checkpointer = ensure_valid_checkpointer(checkpointer)
serde_allowlist: set[tuple[str, ...]] | None = None
if _serde.STRICT_MSGPACK_ENABLED:
schema_types: list[type[Any]] = [
self.state_schema,
self.input_schema,
self.output_schema,
]
if self.context_schema is not None:
schema_types.append(self.context_schema)
for node in self.nodes.values():
schema_types.append(node.input_schema)
for branches in self.branches.values():
for branch in branches.values():
if branch.input_schema is not None:
schema_types.append(branch.input_schema)
serde_allowlist = _serde.build_serde_allowlist(
schemas=schema_types,
channels=self.channels,
)
checkpointer = _serde.apply_checkpointer_allowlist(
checkpointer, serde_allowlist
)
interrupt_before = interrupt_before or []
interrupt_after = interrupt_after or []
self.validate(
interrupt=(
(interrupt_before if interrupt_before != "*" else []) + interrupt_after
if interrupt_after != "*"
else []
)
)
output_channels = (
"__root__"
if len(self.schemas[self.output_schema]) == 1
and "__root__" in self.schemas[self.output_schema]
else [
key
for key, val in self.schemas[self.output_schema].items()
if not is_managed_value(val)
]
)
stream_channels = (
"__root__"
if len(self.channels) == 1 and "__root__" in self.channels
else [
key for key, val in self.channels.items() if not is_managed_value(val)
]
)
defaults = self._node_defaults
default_handler_name: str | None = None
if defaults.error_handler is not None:
if _DEFAULT_ERROR_HANDLER_NODE in self.nodes:
raise ValueError(
f"Auto-generated default error handler node "
f"`{_DEFAULT_ERROR_HANDLER_NODE}` already exists."
)
default_handler_name = _DEFAULT_ERROR_HANDLER_NODE
self.nodes[default_handler_name] = StateNodeSpec[Any, ContextT](
coerce_to_runnable(
defaults.error_handler,
name=default_handler_name,
trace=False,
),
metadata=None,
input_schema=self.state_schema,
retry_policy=None,
cache_policy=None,
is_error_handler=True,
)
for spec in self.nodes.values():
if (
not spec.is_error_handler
and default_handler_name is not None
and spec.error_handler_node is None
):
spec.error_handler_node = default_handler_name
if defaults.retry_policy is not None and spec.retry_policy is None:
spec.retry_policy = defaults.retry_policy
if (
not spec.is_error_handler
and defaults.cache_policy is not None
and spec.cache_policy is None
):
spec.cache_policy = defaults.cache_policy
if defaults.timeout is not None and spec.timeout is None:
spec.timeout = defaults.timeout
node_error_handler_map = {
node_name: spec.error_handler_node
for node_name, spec in self.nodes.items()
if not spec.is_error_handler and spec.error_handler_node is not None
}
compiled = CompiledStateGraph[StateT, ContextT, InputT, OutputT](
builder=self,
schema_to_mapper={},
context_schema=self.context_schema,
nodes={},
channels={
**self.channels,
**self.managed,
START: EphemeralValue(self.input_schema),
},
input_channels=START,
stream_mode="updates",
output_channels=output_channels,
stream_channels=stream_channels,
checkpointer=checkpointer,
interrupt_before_nodes=interrupt_before,
interrupt_after_nodes=interrupt_after,
auto_validate=False,
debug=debug,
store=store,
cache=cache,
node_error_handler_map=node_error_handler_map,
name=name or "LangGraph",
stream_transformers=transformers,
)
compiled._serde_allowlist = serde_allowlist
compiled.attach_node(START, None)
for key, node in self.nodes.items():
compiled.attach_node(key, node)
compiled._output_mapper = _pick_mapper(
list(output_channels)
if isinstance(output_channels, list)
else [output_channels],
self.output_schema,
)
compiled._state_mapper = _pick_mapper(
list(stream_channels)
if isinstance(stream_channels, list)
else [stream_channels],
self.state_schema,
)
for start, end in self.edges:
compiled.attach_edge(start, end)
for starts, end in self.waiting_edges:
compiled.attach_edge(starts, end)
for start, branches in self.branches.items():
for name, branch in branches.items():
compiled.attach_branch(start, name, branch)
return compiled.validate()
阅读完整函数时关注三个阶段:先校验checkpointer与图结构,再选择输出和流通道,最后构造CompiledStateGraph并接入节点、边、分支。源码中还会处理默认重试、缓存、错误处理等配置;这些分支说明编译会把builder配置落到具体执行规格上。
可按这条调用链定位问题:StateGraph.compile → CompiledStateGraph.attach_node / attach_edge / attach_branch → Pregel运行时。构图错误优先查compile前后的Schema和目标名称;执行后状态不对再查节点更新和Channel。
边如何转换成触发器
源码:CompiledStateGraph.attach_edge · L1551—L1575,固定提交41c9c22fdc83,完整函数。
def attach_edge(self, starts: str | Sequence[str], end: str) -> None:
if isinstance(starts, str):
if end != END:
self.nodes[starts].writers.append(
ChannelWrite(
(ChannelWriteEntry(_CHANNEL_BRANCH_TO.format(end), None),)
)
)
elif end != END:
channel_name = f"join:{'+'.join(starts)}:{end}"
if self.builder.nodes[end].defer:
self.channels[channel_name] = NamedBarrierValueAfterFinish(
str, set(starts)
)
else:
self.channels[channel_name] = NamedBarrierValue(str, set(starts))
self.nodes[end].triggers.append(channel_name)
for start in starts:
self.nodes[start].writers.append(
ChannelWrite((ChannelWriteEntry(channel_name, start),))
)
单条边会向后继节点对应的分支通道写入触发信息。多起点边add_edge(["a", "b"], "join")创建具名屏障,记录需要等待哪些前驱。屏障不是一个业务结果列表:它解决“何时可以触发join”,实际结果仍由State字段承载。
这个区别能解释为什么“图上有箭头”不够。箭头决定调度条件,Reducer决定数据如何合并;要得到正确的并行汇总,两者都必须正确。