跳转到内容

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 后序遍历:

处理策略

  1. 检测到循环后,is_cyclic = True
  2. 如果 max_iterations 未设置 → 抛出 ValueError
  3. 执行时通过 yielded_counts 计数器跟踪每个顶点的执行次数
  4. 任一顶点超过 max_iterations → 抛出 ValueError("Max iterations reached")

4. 条件路由

条件路由在循环检测之外独立工作:

# 条件路由状态
self.conditionally_excluded_vertices: set[str] = set()
self.conditional_exclusion_sources: dict[str, set[str]] = {}

工作流程

  1. If-Else 组件执行后,根据输出决定走哪条分支
  2. 未被选中的分支的顶点 ID 被加入 conditionally_excluded_vertices
  3. 执行引擎在计算 vertices_to_run 时排除这些顶点
  4. 排除信息记录在 conditional_exclusion_sources 中,用于调试和追踪

5. 同步/异步双模式桥接

Graph 同时提供 async_startstart 两种执行模式:

# 异步模式:直接返回 async generator
async 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(拓扑排序与循环检测工具函数)