数据截至 (上游 commit 601093ddfe23)
AutoGPT — 图与数据流(扁平化动态 pin)
本章讲什么: 块是点,连线是边。这一章讲 agent 的静态结构(Node/Link),以及整个项目最聪明的一招:用字符串编码的「动态 pin」,把上游输出里任意深的嵌套值,精确地喂到下游块的某个输入字段。
1. 静态结构:Node 与 Link
一张图(GraphModel,data/graph.py:419)就是一堆节点 + 一堆连线。
-
Node(
data/graph.py:105) = 「画布上的一个块实例」。它记block_id(指向哪个块)、input_default(用户在面板里填死的常量输入)、还有进出的连线。它的.block属性(data/graph.py:124)按 id 取出真正的块;块被删了就返回一个占位的_UnknownBlockBase,而不是崩。 -
Link(
data/graph.py:83) = 一条有向连线,四元组定义:
| 字段 | 含义 |
|---|---|
source_id | 上游节点 |
source_name | 上游的输出 pin 名(可能是个编码过的路径,见下) |
sink_id | 下游节点 |
sink_name | 下游的输入字段名 |
is_static | 这条线的数据可否被下游多次消费 |
1.1 起始节点:从哪开始跑
引擎需要知道「图从哪几个节点开始」。starting_nodes(data/graph.py:436)的规则很直接:
# data/graph.py:402 GraphModel.starting_nodes
outbound_nodes = {link.sink_id for link in self.links} # 所有「被连入」的节点
input_nodes = {n.id for n in self.nodes if n.block.block_type == BlockType.INPUT}
return [n for n in self.nodes
if n.id not in outbound_nodes or n.id in input_nodes]
白话:没有任何输入连线的节点(没人喂它)就是起点;外加显式的 Input 块(图的入参)。
2. 核心机制:扁平化动态 pin
2.1 要解决的小问题
上游块 yield "result", {"items": [{"name": "Alice"}, ...]}。下游块只想要 Alice 这个字符串,塞到它的 username 输入。怎么在「连线」这个静态结构里,表达「取 result 的 items[0].name」?
AutoGPT 的答案:不引入复杂的连线对象,而是把这条取值路径压进 source_name 字符串本身。这就是「扁平化动态 pin」。
2.2 思路/直觉:用分隔符编码路径
它给 dict / list / object 三种取值各定义一个分隔符,把路径写成一个字符串(data/dynamic_fields.py 里 DICT_SPLIT/LIST_SPLIT/OBJC_SPLIT)。于是:
上游 yield 的 pin 名: "result"
Link 的 source_name: "result" + DICT_SPLIT+"items" + LIST_SPLIT+"0" + OBJC_SPLIT+"name"
└─ 基名 ─┘ └──────────── 取值路径( 编码进字符串)────────────┘
路由时,引擎拿 source_name 去剥上游实际产出的那个对象,一层层走下去,取出最终值;取不到就返回 None(那条线这次不传数据)。
2.3 真实实现:解码(出方向)
parse_execution_output(data/dynamic_fields.py:152)就是「按 link_output_selector 从 output 里挖出嵌套值」。核心循环(data/dynamic_fields.py:218):
# data/dynamic_fields.py:218 —— 沿编码路径逐级下钻
cur = data
for delim, ident in tokens:
if delim == LIST_SPLIT: # 列表索引
cur = cur[int(ident)] # 越界/类型不符 → return None
elif delim == DICT_SPLIT: # 字典键
cur = cur[ident]
elif delim == OBJC_SPLIT: # 对象属性
cur = getattr(cur, ident)
return cur
任何一步失配(键不存在、索引越界、类型不对)都安全地返回 None(data/dynamic_fields.py:161 docstring 明确这一点)。这让「上游输出形状和连线期望不完全一致」不会炸引擎,只是那条线不流数据。