TraceStudio-dev/docs/studio1.3/CUSTOM_NODES.md
2026-01-09 21:37:02 +08:00

524 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🧩 自定义节点开发指南
## 📖 概述
TraceStudio 支持用户创建自定义节点,扩展系统功能。本文档介绍如何开发、测试和部署自定义节点。
## 🏗️ 架构设计
### 目录结构
```
server/
├── custom_nodes/ # 自定义节点目录
│ ├── __init__.py # 自动生成
│ ├── example_nodes.py # 示例节点
│ └── your_node.py # 你的节点
├── app/
│ ├── core/
│ │ ├── node_base.py # 节点基类
│ │ ├── node_validator.py # 代码验证器
│ │ └── node_loader.py # 动态加载器
│ └── api/
│ └── endpoints_custom_nodes.py # 管理 API
```
### 安全机制
**代码验证**
- AST 语法树分析
- 危险模块黑名单os, subprocess, eval等
- 必须继承 `TraceNode` 基类
- 必须实现 `execute()` 方法
**操作确认**
- 保存/覆盖文件需确认
- 删除文件需二次确认
- 自动备份旧文件
**沙箱执行**
- 执行超时限制30秒
- 内存限制512MB
- 禁止危险操作
## 🚀 快速开始
### 1. 创建节点文件
`custom_nodes/` 目录创建 `.py` 文件:
```python
# custom_nodes/my_filter.py
from app.core.node_base import TraceNode
class MyFilterNode(TraceNode):
"""我的过滤节点"""
@staticmethod
def get_metadata():
return {
"display_name": "我的过滤器",
"description": "根据条件过滤数据",
"category": "Custom/Data",
"author": "Your Name",
"version": "1.0.0",
"inputs": [
{
"name": "data",
"type": "DataFrame",
"description": "输入数据",
"required": True
}
],
"outputs": [
{
"name": "result",
"type": "DataFrame",
"description": "过滤结果"
}
],
"params": [
{
"name": "threshold",
"type": "float",
"default": 0.5,
"description": "过滤阈值"
}
]
}
def execute(self, inputs):
"""执行节点逻辑"""
data = inputs.get('data')
threshold = self.get_param('threshold', 0.5)
# 实现过滤逻辑
result = data[data['value'] > threshold]
return {"result": result}
```
### 2. 使用前端编辑器
1. 打开 TraceStudio Web 界面
2. 进入 **自定义节点编辑器**
3. 点击 **"新建节点"** 或 **"加载示例"**
4. 编写代码
5. 点击 **"验证代码"** 检查语法
6. 点击 **"保存"** 保存并自动加载
### 3. API 直接操作
```bash
# 列出所有节点
curl http://localhost:8000/api/custom-nodes/list
# 验证代码
curl -X POST http://localhost:8000/api/custom-nodes/validate \
-H "Content-Type: application/json" \
-d '{"code": "...", "filename": "test.py"}'
# 保存节点
curl -X POST http://localhost:8000/api/custom-nodes/save \
-H "Content-Type: application/json" \
-d '{"filename": "my_node.py", "code": "...", "force": false}'
# 加载节点
curl -X POST http://localhost:8000/api/custom-nodes/action \
-H "Content-Type: application/json" \
-d '{"filename": "my_node.py", "action": "load"}'
```
## 📝 节点开发规范
### 必须实现的方法
#### 1. `get_metadata()` 静态方法
返回节点的元数据信息:
```python
@staticmethod
def get_metadata():
return {
"display_name": str, # 显示名称(必需)
"description": str, # 功能描述(推荐)
"category": str, # 分类路径(如 "Data/Transform"
"author": str, # 作者(可选)
"version": str, # 版本号(可选)
"inputs": [...], # 输入端口定义
"outputs": [...], # 输出端口定义
"params": [...] # 参数定义
}
```
**输入端口格式**
```python
{
"name": "input_name", # 端口名(唯一)
"type": "DataFrame", # 数据类型
"description": "说明文字", # 描述(可选)
"required": True # 是否必需默认True
}
```
**输出端口格式**
```python
{
"name": "output_name", # 端口名(唯一)
"type": "Any", # 数据类型
"description": "说明文字" # 描述(可选)
}
```
**参数格式**
```python
{
"name": "param_name", # 参数名
"type": "float", # 类型str/int/float/bool
"default": 0.5, # 默认值
"description": "说明文字", # 描述(可选)
"options": [0.1, 0.5, 1.0] # 可选值列表(下拉框)
}
```
#### 2. `execute(inputs)` 方法
执行节点的核心逻辑:
```python
def execute(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
"""
执行节点
Args:
inputs: 输入数据字典 {"input_name": data}
Returns:
输出数据字典 {"output_name": data}
Raises:
Exception: 执行失败时抛出异常
"""
# 1. 获取输入
data = inputs.get('data')
# 2. 获取参数
threshold = self.get_param('threshold', 0.5)
# 3. 执行逻辑
result = process(data, threshold)
# 4. 返回输出
return {"result": result}
```
### 辅助方法
```python
# 获取参数值
value = self.get_param('param_name', default_value)
# 验证输入(可选重写)
def validate_inputs(self, inputs):
if 'required_input' not in inputs:
raise ValueError("缺少必需输入")
return True
```
## 🔒 安全限制
### 禁止使用的模块
**系统操作**
```python
import os # 禁止
import subprocess # 禁止
import sys # 禁止
```
**动态执行**
```python
eval(code) # 禁止
exec(code) # 禁止
__import__(module) # 禁止
```
**文件操作**(需谨慎)
```python
open(file) # 警告:确保路径安全
```
### 允许使用的模块
**数据处理**
```python
import pandas as pd
import numpy as np
import json
```
**数学计算**
```python
import math
from scipy import stats
```
**可视化**
```python
import matplotlib.pyplot as plt
import seaborn as sns
```
## 📦 内置示例节点
### 1. DataFilterNode数据过滤器
```python
from app.core.node_base import TraceNode
class DataFilterNode(TraceNode):
"""根据条件过滤DataFrame"""
def execute(self, inputs):
data = inputs.get('data')
column = self.get_param('column', 'value')
operator = self.get_param('operator', '>')
threshold = self.get_param('threshold', 0.5)
if operator == '>':
filtered = data[data[column] > threshold]
# ... 其他运算符
return {'filtered_data': filtered, 'count': len(filtered)}
```
**使用场景**:过滤性能数据、筛选事件记录
### 2. TextProcessorNode文本处理器
```python
class TextProcessorNode(TraceNode):
"""文本转换处理"""
def execute(self, inputs):
text = inputs.get('text', '')
operation = self.get_param('operation', 'uppercase')
if operation == 'uppercase':
result = text.upper()
elif operation == 'lowercase':
result = text.lower()
# ...
return {'result': result}
```
**使用场景**:日志处理、字符串标准化
## 🧪 测试节点
### 单元测试
```python
# tests/test_custom_nodes.py
import pytest
from custom_nodes.my_node import MyFilterNode
def test_my_filter_node():
node = MyFilterNode('test_node', {'threshold': 0.5})
# 准备测试数据
import pandas as pd
data = pd.DataFrame({'value': [0.3, 0.7, 0.9]})
# 执行节点
result = node.execute({'data': data})
# 断言结果
assert len(result['result']) == 2
assert result['result']['value'].min() > 0.5
```
### 验证测试
```bash
# 使用验证器测试
python -c "
from app.core.node_validator import validate_node_file
result = validate_node_file('custom_nodes/my_node.py')
print(result)
"
```
## 🛠️ API 文档
### 节点管理端点
| 端点 | 方法 | 说明 |
|------|------|------|
| `/api/custom-nodes/list` | GET | 列出所有节点 |
| `/api/custom-nodes/validate` | POST | 验证代码 |
| `/api/custom-nodes/read/{filename}` | GET | 读取节点代码 |
| `/api/custom-nodes/save` | POST | 保存节点 |
| `/api/custom-nodes/action` | POST | 操作节点load/unload/delete |
| `/api/custom-nodes/download/{filename}` | GET | 下载节点文件 |
| `/api/custom-nodes/upload` | POST | 上传节点文件 |
| `/api/custom-nodes/loaded` | GET | 获取已加载节点 |
| `/api/custom-nodes/reload-all` | POST | 重新加载所有节点 |
| `/api/custom-nodes/example` | GET | 获取示例代码 |
### 响应格式
**成功响应**
```json
{
"success": true,
"message": "操作成功",
"data": {...}
}
```
**验证响应**
```json
{
"success": true,
"validation": {
"valid": true,
"errors": [],
"warnings": ["建议实现 get_metadata()"],
"node_classes": ["MyNode"],
"metadata": {...}
}
}
```
## 🐛 常见问题
### 1. 验证失败:未找到 TraceNode 基类
**问题**
```
❌ 未找到继承自 TraceNode 的节点类
```
**解决**
```python
# ❌ 错误
class MyNode:
pass
# ✅ 正确
from app.core.node_base import TraceNode
class MyNode(TraceNode):
pass
```
### 2. 导入错误:模块找不到
**问题**
```
ModuleNotFoundError: No module named 'app'
```
**解决**
```python
# ❌ 错误(绝对导入)
from server.app.core.node_base import TraceNode
# ✅ 正确(相对于项目根目录)
from app.core.node_base import TraceNode
```
### 3. 安全检查失败:禁止导入模块
**问题**
```
❌ 禁止导入危险模块: os (行 3)
```
**解决**:移除危险模块导入,使用安全替代方案。
### 4. 节点未加载到系统
**解决步骤**
1. 检查文件名格式(必须是 `.py`
2. 验证代码通过(`/api/custom-nodes/validate`
3. 手动加载节点(`/api/custom-nodes/action` action=load
4. 重启服务器
## 📚 进阶技巧
### 1. 状态管理
```python
class StatefulNode(TraceNode):
def __init__(self, node_id, params):
super().__init__(node_id, params)
self.cache = {} # 节点私有缓存
def execute(self, inputs):
# 使用缓存加速
if 'data' in self.cache:
return self.cache['data']
result = expensive_computation(inputs)
self.cache['data'] = result
return result
```
### 2. 多输出节点
```python
def execute(self, inputs):
data = inputs.get('data')
# 多个输出
return {
'output1': process_a(data),
'output2': process_b(data),
'stats': {'count': len(data)}
}
```
### 3. 异常处理
```python
def execute(self, inputs):
try:
data = inputs.get('data')
if data is None:
raise ValueError("输入数据不能为空")
result = risky_operation(data)
return {'result': result}
except KeyError as e:
raise ValueError(f"缺少必需的键: {e}")
except Exception as e:
raise RuntimeError(f"处理失败: {str(e)}")
```
## 🎓 最佳实践
1. **清晰的命名**:类名和文件名使用描述性名称
2. **完整的文档**:添加 docstring 说明功能
3. **错误处理**:提供明确的错误信息
4. **输入验证**:检查输入数据类型和范围
5. **性能优化**:避免不必要的计算
6. **测试覆盖**:编写单元测试
7. **版本管理**:在 metadata 中记录版本号
## 📞 技术支持
遇到问题?
- 📖 查看 [API 文档](http://localhost:8000/docs)
- 🐛 提交 [Issue](https://github.com/your-repo/issues)
- 💬 加入讨论组
---
**Happy Coding! 🚀**