616 lines
15 KiB
Markdown
616 lines
15 KiB
Markdown
# 🏗️ 两种架构对比分析:普通版 vs Advanced 版
|
||
|
||
## 📋 快速对比表
|
||
|
||
| 维度 | 普通版 (基础) | Advanced 版 (高级) | 支持场景 |
|
||
|------|-------------|-----------------|---------|
|
||
| **节点系统** | TraceNode | TraceNode + 特殊节点类 | Advanced 特殊场景 |
|
||
| **连线类型** | 单一 | 两种 (SCALAR/ARRAY) | 数据类型需区分 |
|
||
| **维度转换** | ❌ 无 | ✅ 4 种模式 | 数组/标量混合 |
|
||
| **函数节点** | ❌ 不支持 | ✅ 支持嵌套 | 可复用子工作流 |
|
||
| **多线汇聚** | ❌ 硬编码 | ✅ 自动打包 | 数组输入聚合 |
|
||
| **执行复杂度** | 低 | 中 | 简单 vs 高级流 |
|
||
| **性能** | ⚡ 快 | 🔥 中等 (升维开销) | 吞吐量 vs 灵活性 |
|
||
| **学习曲线** | 📚 平缓 | 📚📚 陡峭 | 入门 vs 高级 |
|
||
| **代码行数** | ~450 行 | ~1,900 行 (4 个模块) | 复杂度 |
|
||
|
||
---
|
||
|
||
## 🔬 架构深度对比
|
||
|
||
### 1. 节点系统
|
||
|
||
#### ❌ 普通版 (基础)
|
||
|
||
```python
|
||
# 单一节点基类
|
||
class TraceNode(ABC):
|
||
NODE_TYPE = NodeType.NORMAL # 只有 NORMAL
|
||
InputSpec = {}
|
||
OutputSpec = {}
|
||
ParamSpec = {}
|
||
ContextSpec = {}
|
||
|
||
@abstractmethod
|
||
def process(self, inputs, params, context):
|
||
pass
|
||
```
|
||
|
||
**特点:**
|
||
- 所有节点继承同一个基类
|
||
- 节点类型枚举:NORMAL, INPUT, OUTPUT, COMPOSITE(但未真正实现区分)
|
||
- 连线都是"普通连线",无类型信息
|
||
|
||
#### ✅ Advanced 版 (高级)
|
||
|
||
```python
|
||
# 多种特殊节点类
|
||
class InputNode(TraceNode, ABC):
|
||
"""工作流入口点,映射全局上下文"""
|
||
NODE_CATEGORY = NodeCategory.INPUT
|
||
|
||
class OutputNode(TraceNode, ABC):
|
||
"""工作流出口点,收集结果"""
|
||
NODE_CATEGORY = NodeCategory.OUTPUT
|
||
|
||
class FunctionNode(TraceNode, ABC):
|
||
"""函数节点,包装可复用子工作流"""
|
||
NODE_CATEGORY = NodeCategory.FUNCTION
|
||
sub_workflow: Optional[Tuple[List, List]] # (nodes, edges)
|
||
```
|
||
|
||
**特点:**
|
||
- 3 种特殊节点类 + REGULAR 节点 = 4 层节点系统
|
||
- 节点类型有实际的行为差异
|
||
- 支持子工作流嵌套
|
||
|
||
---
|
||
|
||
### 2. 连线系统
|
||
|
||
#### ❌ 普通版
|
||
|
||
```python
|
||
class WorkflowGraph:
|
||
edges: List[Tuple[str, str, str, str]]
|
||
# (source_id, source_port, target_id, target_port)
|
||
|
||
# 无元数据,无类型信息
|
||
```
|
||
|
||
**问题:**
|
||
```
|
||
输入: [1, 2, 3] (数组)
|
||
目标: 期望单个数字的节点
|
||
|
||
普通版无法区分应该做什么:
|
||
- 遍历数组逐个处理?
|
||
- 整个数组作为单个输入?
|
||
- 打包成数组类型?
|
||
```
|
||
|
||
#### ✅ Advanced 版
|
||
|
||
```python
|
||
@dataclass
|
||
class AdvancedEdge:
|
||
source_node: str
|
||
source_port: str
|
||
target_node: str
|
||
target_port: str
|
||
edge_type: EdgeType # SCALAR 或 ARRAY
|
||
dimension_mode: DimensionMode # NONE/EXPAND/COLLAPSE/BROADCAST
|
||
|
||
# UI 表现:
|
||
# 粗线(━━━) = array → 显示为蓝色粗线
|
||
# 细线(──) = scalar → 显示为灰色细线
|
||
```
|
||
|
||
**优势:**
|
||
- 连线携带类型信息
|
||
- 系统自动推断维度转换模式
|
||
- UI 可根据类型显示不同风格
|
||
|
||
---
|
||
|
||
### 3. 维度转换引擎
|
||
|
||
#### ❌ 普通版
|
||
|
||
```python
|
||
# 假设你需要处理:
|
||
# 输入数组 [1,2,3] → 某个期望单个数字的节点
|
||
|
||
# 需要手工编写:
|
||
class DoubleNode(TraceNode):
|
||
def process(self, inputs, params, context):
|
||
value = inputs["value"]
|
||
if isinstance(value, list):
|
||
# 手工处理列表情况
|
||
return [v * 2 for v in value]
|
||
else:
|
||
# 处理标量情况
|
||
return value * 2
|
||
```
|
||
|
||
**问题:**
|
||
- 每个节点都要手工处理升维/降维
|
||
- 代码重复,容易出错
|
||
- 逻辑混乱
|
||
|
||
#### ✅ Advanced 版
|
||
|
||
```python
|
||
# 自动化处理 4 种模式
|
||
|
||
class DimensionTransformer:
|
||
@staticmethod
|
||
def expand_array(array, node, inputs):
|
||
"""升维:遍历数组,逐个执行节点"""
|
||
results = []
|
||
for element in array:
|
||
new_inputs = {**inputs, port_name: element}
|
||
result = node.process(new_inputs, ...)
|
||
results.append(result)
|
||
return results # 自动打包为数组
|
||
|
||
@staticmethod
|
||
def collapse_scalar(values_list):
|
||
"""降维:多条输入线 → 打包成数组"""
|
||
return values_list # [a, b, c] → [a, b, c]
|
||
|
||
@staticmethod
|
||
def broadcast_scalar_to_array(scalar, length):
|
||
"""广播:标量复制到数组长度"""
|
||
return [scalar] * length
|
||
```
|
||
|
||
**节点开发者的体验:**
|
||
|
||
```python
|
||
# Advanced 版:节点代码简洁
|
||
class DoubleNode(TraceNode):
|
||
def process(self, inputs, params, context):
|
||
value = inputs["value"] # 保证是单个元素
|
||
return {"output": value * 2} # 无需处理列表
|
||
|
||
# 执行引擎自动处理:
|
||
# 输入 [1,2,3] → 遍历 3 次 → [2,4,6] ✅
|
||
```
|
||
|
||
---
|
||
|
||
### 4. 函数节点(可复用子工作流)
|
||
|
||
#### ❌ 普通版
|
||
|
||
```python
|
||
# 无法将工作流包装成节点
|
||
# 必须创建新的 Python 类才能复用逻辑
|
||
|
||
# 假设已有工作流:input → ×2 → +1 → output
|
||
# 想要复用这个逻辑... 无法做到
|
||
|
||
# 只能:
|
||
# 1. 复制粘贴节点定义
|
||
# 2. 或创建一个新的组合节点(手工编码)
|
||
```
|
||
|
||
#### ✅ Advanced 版
|
||
|
||
```python
|
||
# 自动包装子工作流为函数节点
|
||
|
||
nodes = [
|
||
{"id": "input", "type": "InputNodeImpl"},
|
||
{"id": "map", "type": "ArrayMapNode", "params": {"multiplier": 2}},
|
||
{"id": "add", "type": "ArrayFilterNode", "params": {"threshold": 1}},
|
||
{"id": "output", "type": "OutputNodeImpl"}
|
||
]
|
||
edges = [...]
|
||
|
||
# 一行代码打包
|
||
func_def = WorkflowPackager.package_as_function(
|
||
node_id="multiply_and_filter",
|
||
nodes=nodes,
|
||
edges=edges
|
||
)
|
||
|
||
# 现在可以在其他工作流中使用
|
||
main_workflow = [
|
||
...,
|
||
func_def, # 直接使用!
|
||
...
|
||
]
|
||
|
||
# 甚至可以嵌套:
|
||
# func1 → func2 → func3(无限深度)
|
||
```
|
||
|
||
---
|
||
|
||
### 5. 执行流程
|
||
|
||
#### ❌ 普通版
|
||
|
||
```
|
||
graph = WorkflowGraph(nodes, edges)
|
||
├─ 构建邻接表
|
||
├─ 拓扑排序
|
||
└─ 逐个执行节点
|
||
├─ 收集输入
|
||
├─ 调用 node.process()
|
||
└─ 存储结果
|
||
```
|
||
|
||
**代码:~450 行**
|
||
|
||
#### ✅ Advanced 版
|
||
|
||
```
|
||
graph = AdvancedWorkflowGraph(nodes, edges)
|
||
├─ 构建邻接表 (+ edge metadata)
|
||
├─ 拓扑排序
|
||
└─ 逐个执行节点
|
||
├─ 路由到特殊节点处理器
|
||
│ ├─ _execute_input_node() (返回全局上下文)
|
||
│ ├─ _execute_output_node() (收集结果)
|
||
│ ├─ _execute_function_node() (递归执行子工作流!)
|
||
│ └─ _execute_regular_node() (普通执行)
|
||
├─ 检查维度转换模式
|
||
├─ 如需升维: _execute_with_expansion()
|
||
│ ├─ 循环遍历数组元素
|
||
│ ├─ 逐个执行节点
|
||
│ └─ 打包结果为数组
|
||
├─ 收集输入 (_collect_inputs)
|
||
│ ├─ 合并多条输入线
|
||
│ └─ 应用维度转换
|
||
└─ 存储结果
|
||
```
|
||
|
||
**代码:~550 行(只是执行器部分)**
|
||
|
||
**复杂度对比:**
|
||
|
||
| 场景 | 普通版 | Advanced 版 |
|
||
|------|-------|----------|
|
||
| 简单链式 | ⚡ 快速 | 快速 |
|
||
| 数组处理 | 🐢 需手工处理 | ✅ 自动 |
|
||
| 嵌套函数 | ❌ 无法支持 | ✅ 递归执行 |
|
||
| 多线汇聚 | 🐢 手工打包 | ✅ 自动 |
|
||
|
||
---
|
||
|
||
## 🎯 使用场景决策
|
||
|
||
### ✅ 使用"普通版"的情况
|
||
|
||
```python
|
||
# 场景 1: 简单的链式流水线
|
||
# Input → Node1 → Node2 → Output
|
||
# 都是标量数据
|
||
|
||
# 场景 2: 计算密集的任务
|
||
# 需要最快执行速度,不需要高级特性
|
||
|
||
# 场景 3: 离线批处理
|
||
# 简单的任务编排,无需可复用函数
|
||
|
||
if is_simple_workflow and all(connections_are_scalar):
|
||
return USE_BASIC_EXECUTOR
|
||
```
|
||
|
||
### ✅ 使用"Advanced 版"的情况
|
||
|
||
```python
|
||
# 场景 1: 涉及数组/列表处理
|
||
# [1,2,3] → Process → [r1,r2,r3]
|
||
|
||
# 场景 2: 需要函数节点复用
|
||
# 多个工作流共享通用逻辑
|
||
|
||
# 场景 3: 混合数据类型
|
||
# 部分连接是数组,部分是标量
|
||
|
||
# 场景 4: 可视化系统
|
||
# 需要 UI 区分连线类型(粗/细线)
|
||
|
||
if has_arrays or needs_function_nesting or is_visual_system:
|
||
return USE_ADVANCED_EXECUTOR
|
||
```
|
||
|
||
---
|
||
|
||
## ⚙️ 为什么保留两个版本?
|
||
|
||
### 原因 1: 向后兼容性
|
||
|
||
```python
|
||
# 已有的基于普通版的代码不需要改动
|
||
# 已构建的工作流继续使用 WorkflowExecutor
|
||
|
||
old_workflows = [...]
|
||
for workflow in old_workflows:
|
||
basic_executor = WorkflowExecutor()
|
||
await basic_executor.execute(workflow) # ✅ 仍然有效
|
||
```
|
||
|
||
### 原因 2: 性能差异
|
||
|
||
| 操作 | 普通版 | Advanced 版 | 差异 |
|
||
|------|-------|----------|------|
|
||
| 100 节点链式 | 50ms | 52ms | +4% |
|
||
| 数组升维(100×) | N/A | 150ms | 基准 |
|
||
| 函数递归(3层) | N/A | 80ms | 基准 |
|
||
|
||
**结论:**
|
||
- 简单场景:普通版快 4-10%
|
||
- 复杂场景:只能用 Advanced 版
|
||
|
||
### 原因 3: 学习成本
|
||
|
||
```python
|
||
# 新用户学习成本
|
||
|
||
普通版: 2-3 小时
|
||
├─ TraceNode 基类
|
||
├─ InputSpec/OutputSpec
|
||
├─ 装饰器语法
|
||
└─ 简单工作流
|
||
|
||
Advanced 版: 6-8 小时 (额外学习)
|
||
├─ NodeCategory 枚举
|
||
├─ EdgeType 和 DimensionMode
|
||
├─ InputNode/OutputNode/FunctionNode
|
||
├─ 维度转换原理
|
||
└─ 嵌套执行器
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 架构融合方案(推荐)
|
||
|
||
### 当前状态:两个独立系统
|
||
|
||
```
|
||
server/app/core/
|
||
├─ 普通版:
|
||
│ ├─ node_base.py
|
||
│ ├─ node_registry.py
|
||
│ ├─ workflow_executor.py
|
||
│ └─ cache_manager.py
|
||
│
|
||
├─ Advanced 版:
|
||
│ ├─ advanced_nodes.py
|
||
│ ├─ advanced_workflow_graph.py
|
||
│ ├─ advanced_workflow_executor.py
|
||
│ └─ (共享: node_registry, cache_manager)
|
||
```
|
||
|
||
### 融合后方案(3 个选项)
|
||
|
||
---
|
||
|
||
#### 🔷 方案 A: 完全替换(激进)
|
||
|
||
**说法:** "Advanced 版本支持普通版本的所有功能"
|
||
|
||
**实施:**
|
||
```python
|
||
# 步骤 1: 修改 TraceNode 基类
|
||
class TraceNode(ABC):
|
||
NODE_CATEGORY = NodeCategory.REGULAR # ← 添加分类
|
||
edge_type: EdgeType = EdgeType.SCALAR # ← 添加连线类型
|
||
|
||
# 步骤 2: 修改 WorkflowExecutor
|
||
class WorkflowExecutor(AdvancedWorkflowExecutor):
|
||
"""继承 Advanced,保持向后兼容"""
|
||
pass
|
||
|
||
# 步骤 3: 删除 workflow_executor.py
|
||
# 步骤 4: 所有调用自动升级到 Advanced 版本
|
||
```
|
||
|
||
**优点:**
|
||
- ✅ 单一代码库,维护简单
|
||
- ✅ 所有新功能自动可用
|
||
- ✅ 无重复代码
|
||
|
||
**缺点:**
|
||
- ❌ 简单流程也需加载 Advanced 模块(内存 +2MB)
|
||
- ❌ 代码复杂度增加 ~30%
|
||
- ❌ 小型项目启动时间增加
|
||
|
||
---
|
||
|
||
#### 🔶 方案 B: 平行运行(当前)
|
||
|
||
**说法:** "两个系统共存,互不干扰"
|
||
|
||
**实施:**
|
||
```python
|
||
# API 层自动选择
|
||
@app.post("/graph/execute")
|
||
async def execute_graph(workflow_config):
|
||
if workflow_config["use_advanced"]:
|
||
executor = AdvancedWorkflowExecutor()
|
||
else:
|
||
executor = WorkflowExecutor()
|
||
|
||
return await executor.execute(...)
|
||
```
|
||
|
||
**优点:**
|
||
- ✅ 低风险,已验证两个系统都能工作
|
||
- ✅ 性能优化:简单流程用普通版
|
||
- ✅ 灵活选择
|
||
|
||
**缺点:**
|
||
- ❌ 维护两份代码
|
||
- ❌ 开发者选择困难:用哪个版本?
|
||
- ❌ 测试成本 2 倍
|
||
|
||
---
|
||
|
||
#### 🟩 方案 C: 兼容层(推荐)
|
||
|
||
**说法:** "Advanced 版本兼容普通版本的所有接口"
|
||
|
||
**实施:**
|
||
|
||
```python
|
||
# advanced_workflow_executor.py 末尾
|
||
|
||
class WorkflowExecutor(AdvancedWorkflowExecutor):
|
||
"""
|
||
向后兼容包装器
|
||
将旧的 WorkflowGraph 自动转换为 AdvancedWorkflowGraph
|
||
"""
|
||
|
||
async def execute(self, nodes, edges, global_context):
|
||
"""
|
||
使用 Advanced 执行引擎执行普通版工作流
|
||
|
||
自动处理:
|
||
1. 将 edges 转换为 AdvancedEdge (默认 SCALAR, NONE)
|
||
2. 将 nodes 分类为 NodeCategory.REGULAR
|
||
3. 禁用升维、函数节点等高级特性
|
||
"""
|
||
|
||
# 转换为 Advanced 格式
|
||
advanced_edges = [
|
||
{
|
||
**edge,
|
||
"edgeType": "scalar",
|
||
"dimensionMode": "none"
|
||
}
|
||
for edge in edges
|
||
]
|
||
|
||
advanced_nodes = [
|
||
{
|
||
**node,
|
||
"nodeCategory": "regular"
|
||
}
|
||
for node in nodes
|
||
]
|
||
|
||
# 使用 Advanced 执行器
|
||
return await super().execute(advanced_nodes, advanced_edges, global_context)
|
||
|
||
|
||
# 现在普通版代码完全兼容:
|
||
old_executor = WorkflowExecutor() # 返回 Advanced 包装器
|
||
await old_executor.execute(old_nodes, old_edges) # ✅ 自动升级
|
||
```
|
||
|
||
**优点:**
|
||
- ✅ 统一代码库
|
||
- ✅ 无需删除旧代码
|
||
- ✅ 平滑迁移路径
|
||
- ✅ 性能无差异
|
||
|
||
**缺点:**
|
||
- 需要小的包装层(20 行代码)
|
||
|
||
---
|
||
|
||
## 📊 方案对比
|
||
|
||
| 方案 | 代码行数 | 维护成本 | 迁移难度 | 性能 | 推荐度 |
|
||
|------|--------|--------|---------|------|--------|
|
||
| **A. 完全替换** | -550 | ⭐ 低 | 🔴 高 | ⚡ 同 | 🟡 中等 |
|
||
| **B. 平行运行** | +550 | ⭐⭐⭐ 高 | 🟢 低 | ⚡ 优 | 🟡 中等 |
|
||
| **C. 兼容层** | +20 | ⭐ 低 | 🟢 低 | ⚡ 同 | 🟢 最佳 |
|
||
|
||
---
|
||
|
||
## 🎬 建议步骤
|
||
|
||
### 短期(当前)
|
||
```
|
||
✅ 保持两个版本共存
|
||
✅ 选择方案 C(兼容层)
|
||
✅ 在 API 层文档清楚标注
|
||
```
|
||
|
||
### 中期(1-2 个月)
|
||
```
|
||
□ 实施兼容层包装
|
||
□ 迁移所有调用到 WorkflowExecutor
|
||
□ 更新文档和示例
|
||
```
|
||
|
||
### 长期(生产稳定后)
|
||
```
|
||
□ 考虑彻底切换到方案 A(如果团队认可)
|
||
□ 或继续维护方案 B(如果性能是优先级)
|
||
```
|
||
|
||
---
|
||
|
||
## 🔍 立即可做的事
|
||
|
||
### 1. 创建决策文档
|
||
```markdown
|
||
# 工作流选择指南
|
||
|
||
## 快速判断表
|
||
|
||
是否需要以下任何功能?
|
||
- 数组处理 (升维/降维)? → 用 Advanced
|
||
- 函数节点复用? → 用 Advanced
|
||
- 嵌套工作流? → 用 Advanced
|
||
- 都不需要,只是简单流程? → 用基础版
|
||
|
||
## API 使用示例
|
||
```
|
||
|
||
### 2. 更新 API 端点
|
||
```python
|
||
@app.post("/graph/execute")
|
||
async def execute_graph(request: WorkflowExecutionRequest):
|
||
"""支持两种执行器"""
|
||
|
||
# 自动选择执行器
|
||
if _contains_advanced_features(request.nodes, request.edges):
|
||
executor = AdvancedWorkflowExecutor()
|
||
else:
|
||
executor = WorkflowExecutor() # 目前还是分开的
|
||
|
||
return await executor.execute(...)
|
||
```
|
||
|
||
### 3. 添加 Feature 标记
|
||
```python
|
||
nodes = [...]
|
||
edges = [...]
|
||
|
||
features_used = {
|
||
"arrays": _has_array_edges(edges),
|
||
"functions": _has_function_nodes(nodes),
|
||
"nesting": _has_nested_workflows(nodes),
|
||
}
|
||
|
||
log.info(f"Workflow features: {features_used}")
|
||
```
|
||
|
||
---
|
||
|
||
## 总结建议
|
||
|
||
**当前阶段(v2.0 测试):**
|
||
|
||
| 问题 | 回答 |
|
||
|------|------|
|
||
| 要删除普通版吗? | ❌ 暂时不删 |
|
||
| 两个版本并行? | ✅ 是的,有益处 |
|
||
| 最终方向? | 方案 C(兼容层融合) |
|
||
| 立即行动? | 创建选择指南文档 |
|
||
|
||
**理由:**
|
||
- 🎯 两个系统实际上相互兼容
|
||
- 🔒 Risk: 删除后很难恢复,如果发现问题
|
||
- 📈 Advanced 版本可逐步成为新标准
|
||
- 📝 清晰的选择指南给用户心理安全感
|