# 🧩 自定义节点开发指南 ## 📖 概述 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! 🚀**