Graph 执行引擎:拓扑排序、循环处理与增量构建
学习目标
- 理解 Graph 类的核心数据结构:顶点层、边、路由表
- 掌握拓扑分层算法:同层顶点可并行执行
- 了解循环检测与处理机制(
max_iterations) - 理解同步/异步双模式的桥接实现
前置知识
- 有向图与拓扑排序基础
- Python asyncio 基础
项目实践
1. Graph 的数据结构
Graph(src/lfx/src/lfx/graph/graph/base.py,2462 行)是 Flow 的执行引擎,核心数据结构:
关键状态集合:
| 字段 | 含义 |
|---|---|
vertices_layers | 按拓扑层序分层的顶点 ID,同层可并行 |
vertices_to_run | 需要执行的顶点 ID 集合 |
conditionally_excluded_vertices | 被条件路由排除的顶点 |
inactivated_vertices | 已停用的顶点(不在当前执行路径) |
_run_queue | 待执行顶点 ID 队列 |
2. 拓扑分层
Graph 在 prepare() 阶段执行拓扑排序:
关键优化:同一拓扑层内的顶点可以并行执行,因为它们之间没有数据依赖。
# 执行时按层处理for layer in self.vertices_layers: # 同层顶点并行构建 tasks = [self._build_vertex(vid) for vid in layer if vid in self.vertices_to_run] results = await asyncio.gather(*tasks, return_exceptions=True)3. 循环检测
检测算法:find_all_cycle_edges() 使用 DFS 后序遍历:
处理策略:
- 检测到循环后,
is_cyclic = True - 如果
max_iterations未设置 → 抛出ValueError - 执行时通过
yielded_counts计数器跟踪每个顶点的执行次数 - 任一顶点超过
max_iterations→ 抛出ValueError("Max iterations reached")
4. 条件路由
条件路由在循环检测之外独立工作:
# 条件路由状态self.conditionally_excluded_vertices: set[str] = set()self.conditional_exclusion_sources: dict[str, set[str]] = {}工作流程:
- If-Else 组件执行后,根据输出决定走哪条分支
- 未被选中的分支的顶点 ID 被加入
conditionally_excluded_vertices - 执行引擎在计算
vertices_to_run时排除这些顶点 - 排除信息记录在
conditional_exclusion_sources中,用于调试和追踪
5. 同步/异步双模式桥接
Graph 同时提供 async_start 和 start 两种执行模式:
# 异步模式:直接返回 async generatorasync def async_start(self, inputs=None, max_iterations=None, ...): while should_continue(yielded_counts, max_iterations): result = await self.astep(...) yield result
# 同步模式:新线程 + 事件循环桥接def start(self, inputs=None, max_iterations=None, ...): def run_async_code(): loop = asyncio.new_event_loop() async_gen = self.async_start(inputs, max_iterations, ...) while True: result = loop.run_until_complete(anext(async_gen)) result_queue.put(result)
thread = threading.Thread(target=run_async_code) thread.start() # 主线程从 result_queue 读取结果为什么需要双模式:
async_start:被 FastAPI 异步端点调用start:被同步 API 或 CLI 调用,内部自动创建事件循环
问题与规避
| 陷阱 | 现象 | 对策 |
|---|---|---|
| 循环图未设置 max_iterations | 运行时报错 | 对包含循环的 Flow 必须设置最大迭代次数 |
| 顶点构建顺序错误 | 下游顶点拿到未构建的上游数据 | 确保拓扑排序正确,同层顶点等待所有输入就绪 |
| 条件路由后状态残留 | 上一次执行的条件排除影响下一次 | prepare() 时重置 conditionally_excluded_vertices |
| 同步模式事件循环绑定 | asyncio.Lock 在不同线程间绑定错误 | Graph 的 _lock 使用 lazy 初始化,每次绑定到当前线程的事件循环 |
设计取舍
拓扑分层 vs 事件驱动:
| 维度 | 拓扑分层 | 事件驱动 |
|---|---|---|
| 并行度 | 同层可并行,层间串行 | 顶点就绪即执行 |
| 可预测性 | 执行顺序确定 | 顺序不确定 |
| 循环处理 | 需要额外计数器 | 天然支持 |
Langflow 选择拓扑分层方案,因为可视化画布上的 Flow 对用户是可见的,执行顺序应该与画布上的布局一致(从左到右、从上到下),拓扑排序天然保证这一点。
参考来源
- 源码:
src/lfx/src/lfx/graph/graph/base.py(Graph 类,2462 行) - 源码:
src/lfx/src/lfx/graph/vertex/base.py(Vertex 类,950 行) - 源码:
src/lfx/src/lfx/graph/graph/utils.py(拓扑排序与循环检测工具函数)