340 lines
10 KiB
Markdown
340 lines
10 KiB
Markdown
|
|
# FunctionNode 系统 API 修复总结
|
|||
|
|
|
|||
|
|
## 问题概述
|
|||
|
|
|
|||
|
|
`server/app/core/function_nodes.py` 中的 InputNode、OutputNode 和 FunctionNode 类不符合 TraceNode v2.0 API 规范,导致服务器运行时出现错误。
|
|||
|
|
|
|||
|
|
## 修复的关键问题
|
|||
|
|
|
|||
|
|
### 1. ✅ InputSpec/OutputSpec/ParamSpec 格式错误
|
|||
|
|
|
|||
|
|
**修复前** ❌
|
|||
|
|
```python
|
|||
|
|
from server.app.core.node_base import (
|
|||
|
|
TraceNode, NodeType, NodeCategory,
|
|||
|
|
InputSpec, OutputSpec, ParamSpec, ContextSpec # 错误:这些不是可导入的类
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def define_inputs(cls) -> List[InputSpec]:
|
|||
|
|
return [InputSpec(name="value", type="Any")] # 对象列表
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复后** ✅
|
|||
|
|
```python
|
|||
|
|
from server.app.core.node_base import (
|
|||
|
|
TraceNode, NodeType, NodeCategory
|
|||
|
|
# InputSpec 等不需要导入,而是使用字典格式
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 类属性,使用字典格式
|
|||
|
|
InputSpec = {
|
|||
|
|
"value": ("Any", {"description": "输入值", "required": True})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**关键变化**:
|
|||
|
|
- 移除了错误的 InputSpec、OutputSpec 等导入
|
|||
|
|
- 规范由方法改为类属性
|
|||
|
|
- 格式从对象列表改为字典:`{port_name: (type, config_dict)}`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2. ✅ 类属性命名大小写错误
|
|||
|
|
|
|||
|
|
**修复前** ❌
|
|||
|
|
```python
|
|||
|
|
node_type = NodeType.INPUT # 小写
|
|||
|
|
node_category = NodeCategory.INPUT # 小写
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复后** ✅
|
|||
|
|
```python
|
|||
|
|
NODE_TYPE = NodeType.INPUT # 大写
|
|||
|
|
NODE_CATEGORY = NodeCategory.INPUT # 大写
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**影响范围**: InputNode、OutputNode、FunctionNode 全部修正
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3. ✅ 方法名称和返回格式错误
|
|||
|
|
|
|||
|
|
**修复前** ❌
|
|||
|
|
```python
|
|||
|
|
def execute(self, inputs, params, context):
|
|||
|
|
"""旧API,返回简单值"""
|
|||
|
|
return {"value": result} # 格式错误
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复后** ✅
|
|||
|
|
```python
|
|||
|
|
def process(self, inputs: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|||
|
|
"""新API,返回标准格式"""
|
|||
|
|
return {
|
|||
|
|
"outputs": {"value": result}, # 必须包含 outputs
|
|||
|
|
"context": context or {} # 必须包含 context
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**关键变化**:
|
|||
|
|
- 方法名:`execute()` → `process()`
|
|||
|
|
- 参数:移除 `params` 参数,改用 `self.get_param()`
|
|||
|
|
- 返回格式:`{...}` → `{"outputs": {...}, "context": {...}}`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4. ✅ InputNode 类完全修复
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class InputNode(TraceNode):
|
|||
|
|
NODE_TYPE = NodeType.INPUT
|
|||
|
|
NODE_CATEGORY = NodeCategory.INPUT
|
|||
|
|
CATEGORY = "Function/Input"
|
|||
|
|
DISPLAY_NAME = "函数输入"
|
|||
|
|
DESCRIPTION = "函数的输入入口节点"
|
|||
|
|
|
|||
|
|
InputSpec = {}
|
|||
|
|
OutputSpec = {"value": ("Any", {"description": "输入值", "required": True})}
|
|||
|
|
ParamSpec = {
|
|||
|
|
"param_name": ("String", {"description": "参数名称", "default": "input"}),
|
|||
|
|
"param_type": ("String", {"description": "参数类型", "default": "Any"})
|
|||
|
|
}
|
|||
|
|
ContextSpec = {}
|
|||
|
|
|
|||
|
|
def process(self, inputs, context=None):
|
|||
|
|
param_name = self.get_param("param_name", "input")
|
|||
|
|
external_value = (context or {}).get(f"__function_input_{param_name}", None)
|
|||
|
|
return {"outputs": {"value": external_value}, "context": {}}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**变化**:
|
|||
|
|
- ✅ 类属性大写(NODE_TYPE, NODE_CATEGORY)
|
|||
|
|
- ✅ 规范定义为类属性字典
|
|||
|
|
- ✅ 方法改为 process()
|
|||
|
|
- ✅ 返回 `{"outputs": {...}, "context": {...}}`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5. ✅ OutputNode 类完全修复
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class OutputNode(TraceNode):
|
|||
|
|
NODE_TYPE = NodeType.OUTPUT
|
|||
|
|
NODE_CATEGORY = NodeCategory.OUTPUT
|
|||
|
|
CATEGORY = "Function/Output"
|
|||
|
|
DISPLAY_NAME = "函数输出"
|
|||
|
|
DESCRIPTION = "函数的输出出口节点"
|
|||
|
|
|
|||
|
|
InputSpec = {"value": ("Any", {"description": "输出值", "required": True})}
|
|||
|
|
OutputSpec = {}
|
|||
|
|
ParamSpec = {
|
|||
|
|
"output_name": ("String", {"description": "输出名称", "default": "result"}),
|
|||
|
|
"output_type": ("String", {"description": "输出类型", "default": "Any"})
|
|||
|
|
}
|
|||
|
|
ContextSpec = {}
|
|||
|
|
|
|||
|
|
def process(self, inputs, context=None):
|
|||
|
|
output_name = self.get_param("output_name", "result")
|
|||
|
|
value = inputs.get("value")
|
|||
|
|
if context is None:
|
|||
|
|
context = {}
|
|||
|
|
context[f"__function_output_{output_name}"] = value
|
|||
|
|
return {"outputs": {}, "context": context}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**变化**:
|
|||
|
|
- ✅ 所有类属性大写并正确定义
|
|||
|
|
- ✅ 规范为字典格式
|
|||
|
|
- ✅ 方法签名和返回格式符合新 API
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 6. ✅ FunctionNode 类完全重构
|
|||
|
|
|
|||
|
|
**修复前** ❌
|
|||
|
|
```python
|
|||
|
|
class FunctionNode(TraceNode):
|
|||
|
|
def __init__(self, function_name: str, workflow_data: Dict):
|
|||
|
|
"""错误的初始化签名"""
|
|||
|
|
super().__init__() # 缺少 node_id
|
|||
|
|
|
|||
|
|
def define_inputs(self): # 错误的方式
|
|||
|
|
return [InputSpec(...) for inp in self._inputs]
|
|||
|
|
|
|||
|
|
def execute(self, inputs, params, context): # 错误的方法
|
|||
|
|
return outputs # 错误的返回格式
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复后** ✅
|
|||
|
|
```python
|
|||
|
|
class FunctionNode(TraceNode):
|
|||
|
|
NODE_TYPE = NodeType.FUNCTION
|
|||
|
|
NODE_CATEGORY = NodeCategory.FUNCTION
|
|||
|
|
CATEGORY = "Function/Workflow"
|
|||
|
|
|
|||
|
|
InputSpec = {} # 动态生成
|
|||
|
|
OutputSpec = {} # 动态生成
|
|||
|
|
ParamSpec = {}
|
|||
|
|
ContextSpec = {}
|
|||
|
|
|
|||
|
|
def __init__(self, node_id: str, params=None,
|
|||
|
|
function_name=None, workflow_data=None):
|
|||
|
|
"""正确的初始化签名"""
|
|||
|
|
super().__init__(node_id, params)
|
|||
|
|
self.function_name = function_name or "unnamed_function"
|
|||
|
|
self.workflow_data = workflow_data or {}
|
|||
|
|
self._build_specs()
|
|||
|
|
|
|||
|
|
def _build_specs(self):
|
|||
|
|
"""动态从工作流数据生成规范"""
|
|||
|
|
input_spec = {}
|
|||
|
|
output_spec = {}
|
|||
|
|
|
|||
|
|
# 从 workflow_data.inputs 构建 InputSpec
|
|||
|
|
for inp in self.workflow_data.get("inputs", []):
|
|||
|
|
input_name = inp.get("name", "input")
|
|||
|
|
input_type = inp.get("type", "Any")
|
|||
|
|
input_spec[input_name] = (
|
|||
|
|
input_type,
|
|||
|
|
{"description": ..., "required": True}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 从 workflow_data.outputs 构建 OutputSpec
|
|||
|
|
for out in self.workflow_data.get("outputs", []):
|
|||
|
|
output_name = out.get("name", "output")
|
|||
|
|
output_type = out.get("type", "Any")
|
|||
|
|
output_spec[output_name] = (
|
|||
|
|
output_type,
|
|||
|
|
{"description": ...}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
self.InputSpec = input_spec
|
|||
|
|
self.OutputSpec = output_spec
|
|||
|
|
|
|||
|
|
def process(self, inputs, context=None):
|
|||
|
|
"""正确的处理方法"""
|
|||
|
|
exec_context = context.copy() if context else {}
|
|||
|
|
|
|||
|
|
# 注入输入
|
|||
|
|
for input_name, input_value in inputs.items():
|
|||
|
|
exec_context[f"__function_input_{input_name}"] = input_value
|
|||
|
|
|
|||
|
|
# 执行工作流(需要工作流引擎支持)
|
|||
|
|
# 临时处理:收集输出
|
|||
|
|
outputs = {}
|
|||
|
|
for output_key in self.OutputSpec.keys():
|
|||
|
|
output_value = exec_context.get(f"__function_output_{output_key}")
|
|||
|
|
outputs[output_key] = output_value or None
|
|||
|
|
|
|||
|
|
return {"outputs": outputs, "context": exec_context}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**关键改进**:
|
|||
|
|
- ✅ 正确的 `__init__` 签名:`(node_id, params, function_name, workflow_data)`
|
|||
|
|
- ✅ 调用 `super().__init__(node_id, params)` 初始化基类
|
|||
|
|
- ✅ 添加 `_build_specs()` 动态生成规范
|
|||
|
|
- ✅ 规范由方法改为动态生成的类属性
|
|||
|
|
- ✅ 改为 `process()` 方法
|
|||
|
|
- ✅ 正确的返回格式
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 7. ✅ create_function_node_class 工厂函数修复
|
|||
|
|
|
|||
|
|
**修复前** ❌
|
|||
|
|
```python
|
|||
|
|
def create_function_node_class(function_name, workflow_data):
|
|||
|
|
class DynamicFunctionNode(FunctionNode):
|
|||
|
|
def __init__(self): # 错误:缺少参数
|
|||
|
|
super().__init__(function_name, workflow_data)
|
|||
|
|
|
|||
|
|
return DynamicFunctionNode
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复后** ✅
|
|||
|
|
```python
|
|||
|
|
def create_function_node_class(function_name: str, workflow_data: Dict[str, Any]) -> type:
|
|||
|
|
"""动态创建函数节点类"""
|
|||
|
|
class_name = f"Function_{function_name}"
|
|||
|
|
|
|||
|
|
class DynamicFunctionNode(FunctionNode):
|
|||
|
|
"""动态生成的函数节点"""
|
|||
|
|
|
|||
|
|
def __init__(self, node_id: str, params: Optional[Dict[str, Any]] = None):
|
|||
|
|
"""正确的初始化"""
|
|||
|
|
super().__init__(node_id, params, function_name, workflow_data)
|
|||
|
|
|
|||
|
|
def get_display_name(self) -> str:
|
|||
|
|
return workflow_data.get("display_name", function_name)
|
|||
|
|
|
|||
|
|
def get_node_type_name(self) -> str:
|
|||
|
|
return f"Function_{function_name}"
|
|||
|
|
|
|||
|
|
# 设置类名便于调试
|
|||
|
|
DynamicFunctionNode.__name__ = class_name
|
|||
|
|
DynamicFunctionNode.__qualname__ = class_name
|
|||
|
|
|
|||
|
|
return DynamicFunctionNode
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**关键改进**:
|
|||
|
|
- ✅ 正确的参数签名:`(node_id, params)`
|
|||
|
|
- ✅ 正确传递给父类:`super().__init__(node_id, params, function_name, workflow_data)`
|
|||
|
|
- ✅ 设置类名便于调试
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 修复前后对比表
|
|||
|
|
|
|||
|
|
| 方面 | 修复前 ❌ | 修复后 ✅ |
|
|||
|
|
|-----|---------|--------|
|
|||
|
|
| **InputSpec 定义** | `List[InputSpec(...)]` 对象列表 | `{"port": ("Type", {...})}` 字典 |
|
|||
|
|
| **OutputSpec 定义** | `List[OutputSpec(...)]` 对象列表 | `{"port": ("Type", {...})}` 字典 |
|
|||
|
|
| **类属性命名** | `node_type` (小写) | `NODE_TYPE` (大写) |
|
|||
|
|
| **规范来源** | 方法: `def define_inputs()` | 类属性 + 动态生成 |
|
|||
|
|
| **执行方法** | `execute(inputs, params, context)` | `process(inputs, context)` |
|
|||
|
|
| **返回格式** | `{...}` 直接返回 | `{"outputs": {...}, "context": {...}}` |
|
|||
|
|
| **初始化** | `__init__(function_name, workflow_data)` | `__init__(node_id, params, function_name, workflow_data)` |
|
|||
|
|
| **参数获取** | `params` 参数 | `self.get_param()` 方法 |
|
|||
|
|
| **工厂函数** | `DynamicFunctionNode()` 无参 | `DynamicFunctionNode(node_id, params)` |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 兼容性说明
|
|||
|
|
|
|||
|
|
修复后的 function_nodes.py 完全符合 TraceNode v2.0 API 规范,与以下组件兼容:
|
|||
|
|
|
|||
|
|
- ✅ **node_base.py**: TraceNode 基类和相关类型定义
|
|||
|
|
- ✅ **node_loader.py**: 节点加载和注册机制
|
|||
|
|
- ✅ **endpoints_graph.py**: 图形执行 API
|
|||
|
|
- ✅ **前端组件**: 节点编辑和绑定
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 测试建议
|
|||
|
|
|
|||
|
|
1. **单元测试**: 测试 InputNode/OutputNode/FunctionNode 的 `process()` 方法
|
|||
|
|
2. **集成测试**: 测试包含嵌套函数的完整工作流执行
|
|||
|
|
3. **API 测试**: 测试 `/api/graph/execute` 端点的工作流执行
|
|||
|
|
4. **动态节点**: 测试 `create_function_node_class()` 生成的节点
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 修改文件
|
|||
|
|
|
|||
|
|
- `server/app/core/function_nodes.py` - 完全修复
|
|||
|
|
- InputNode 类: 60 行
|
|||
|
|
- OutputNode 类: 50 行
|
|||
|
|
- FunctionNode 类: 130 行
|
|||
|
|
- create_function_node_class 函数: 50 行
|
|||
|
|
- 总计: ~290 行
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 下一步
|
|||
|
|
|
|||
|
|
1. ✅ 修复 function_nodes.py (已完成)
|
|||
|
|
2. ⏳ 验证 node_loader.py 中 create_function_node_class 的调用
|
|||
|
|
3. ⏳ 服务器集成测试
|
|||
|
|
4. ⏳ 前端工作流测试
|