406 lines
9.9 KiB
Markdown
406 lines
9.9 KiB
Markdown
|
|
# 插件开发指南
|
|||
|
|
|
|||
|
|
> **Plugin Development Guide** — 天工智能体平台插件系统
|
|||
|
|
|
|||
|
|
本文档面向希望为天工智能体平台开发自定义节点的开发者。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、插件系统概览
|
|||
|
|
|
|||
|
|
插件系统允许开发者扩展工作流中的节点类型,自定义处理逻辑。插件注册后可在工作流编辑器中像内置节点一样使用。
|
|||
|
|
|
|||
|
|
### 插件模型
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
NodePlugin
|
|||
|
|
├── id: UUID
|
|||
|
|
├── name: 插件名称
|
|||
|
|
├── description: 描述
|
|||
|
|
├── plugin_type: code | http | transform | trigger | output
|
|||
|
|
├── category: 分类
|
|||
|
|
├── config_schema: 配置项 JSON Schema
|
|||
|
|
├── code: 插件代码(code 类型)
|
|||
|
|
├── endpoint: HTTP 端点(http 类型)
|
|||
|
|
├── inputs: 输入定义 [{name, type, required, default}]
|
|||
|
|
├── outputs: 输出定义 [{name, type, description}]
|
|||
|
|
├── is_public: 是否公开
|
|||
|
|
├── version: 版本号
|
|||
|
|
├── workspace_id: 所属工作区
|
|||
|
|
└── user_id: 创建者
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### API 端点
|
|||
|
|
|
|||
|
|
| 方法 | 路径 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| GET | `/api/v1/plugins` | 插件列表 |
|
|||
|
|
| POST | `/api/v1/plugins` | 创建插件 |
|
|||
|
|
| GET | `/api/v1/plugins/{id}` | 插件详情 |
|
|||
|
|
| PUT | `/api/v1/plugins/{id}` | 更新插件 |
|
|||
|
|
| DELETE | `/api/v1/plugins/{id}` | 删除插件 |
|
|||
|
|
| POST | `/api/v1/plugins/{id}/test` | 测试插件 |
|
|||
|
|
| POST | `/api/v1/plugins/{id}/publish` | 发布到市场 |
|
|||
|
|
| POST | `/api/v1/plugins/market/install` | 从市场安装 |
|
|||
|
|
| POST | `/api/v1/plugins/{id}/toggle` | 启用/禁用 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、Code 类型插件
|
|||
|
|
|
|||
|
|
Code 插件是最灵活的插件类型,直接编写 Python 代码实现自定义逻辑。
|
|||
|
|
|
|||
|
|
### 代码模板
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
"""
|
|||
|
|
插件入口函数签名:
|
|||
|
|
def run(inputs: dict, config: dict, context: dict) -> dict
|
|||
|
|
|
|||
|
|
参数:
|
|||
|
|
inputs — 上游节点传递的输入数据
|
|||
|
|
config — 用户在编辑器中配置的参数
|
|||
|
|
context — 运行时上下文 {execution_id, agent_id, user_id, workspace_id, secrets}
|
|||
|
|
|
|||
|
|
返回:
|
|||
|
|
dict — 输出数据,传递给下游节点
|
|||
|
|
|
|||
|
|
异常:
|
|||
|
|
抛出 PluginError("message") 会被框架捕获并记录到执行日志
|
|||
|
|
"""
|
|||
|
|
import json
|
|||
|
|
import re
|
|||
|
|
from typing import Any, Dict
|
|||
|
|
|
|||
|
|
|
|||
|
|
class PluginError(Exception):
|
|||
|
|
"""插件自定义异常"""
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
|
|||
|
|
def run(inputs: Dict[str, Any], config: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
|
|||
|
|
"""
|
|||
|
|
示例:文本清洗 + 格式化插件
|
|||
|
|
"""
|
|||
|
|
text = inputs.get("text", "")
|
|||
|
|
if not text:
|
|||
|
|
raise PluginError("输入 text 不能为空")
|
|||
|
|
|
|||
|
|
# 读取配置
|
|||
|
|
remove_html = config.get("remove_html", True)
|
|||
|
|
remove_extra_spaces = config.get("remove_extra_spaces", True)
|
|||
|
|
max_length = config.get("max_length", 0)
|
|||
|
|
|
|||
|
|
result = text
|
|||
|
|
|
|||
|
|
if remove_html:
|
|||
|
|
result = re.sub(r"<[^>]+>", "", result)
|
|||
|
|
|
|||
|
|
if remove_extra_spaces:
|
|||
|
|
result = re.sub(r"\s+", " ", result).strip()
|
|||
|
|
|
|||
|
|
if max_length > 0 and len(result) > max_length:
|
|||
|
|
result = result[:max_length] + "..."
|
|||
|
|
|
|||
|
|
# 统计信息
|
|||
|
|
original_length = len(text)
|
|||
|
|
cleaned_length = len(result)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"text": result,
|
|||
|
|
"stats": {
|
|||
|
|
"original_length": original_length,
|
|||
|
|
"cleaned_length": cleaned_length,
|
|||
|
|
"reduction_pct": round((1 - cleaned_length / max(original_length, 1)) * 100, 1),
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 配置 Schema
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "object",
|
|||
|
|
"properties": {
|
|||
|
|
"remove_html": {
|
|||
|
|
"type": "boolean",
|
|||
|
|
"title": "移除 HTML 标签",
|
|||
|
|
"default": true
|
|||
|
|
},
|
|||
|
|
"remove_extra_spaces": {
|
|||
|
|
"type": "boolean",
|
|||
|
|
"title": "合并多余空白",
|
|||
|
|
"default": true
|
|||
|
|
},
|
|||
|
|
"max_length": {
|
|||
|
|
"type": "integer",
|
|||
|
|
"title": "最大长度(0=不限制)",
|
|||
|
|
"default": 0,
|
|||
|
|
"minimum": 0
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 创建 Code 插件
|
|||
|
|
|
|||
|
|
```http
|
|||
|
|
POST /api/v1/plugins
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"name": "文本清洗器",
|
|||
|
|
"description": "去除 HTML 标签、合并空白、截断过长文本",
|
|||
|
|
"plugin_type": "code",
|
|||
|
|
"category": "transform",
|
|||
|
|
"config_schema": { ... },
|
|||
|
|
"code": "def run(inputs, config, context): ...",
|
|||
|
|
"inputs": [
|
|||
|
|
{"name": "text", "type": "string", "required": true, "description": "待清洗的原始文本"}
|
|||
|
|
],
|
|||
|
|
"outputs": [
|
|||
|
|
{"name": "text", "type": "string", "description": "清洗后的文本"},
|
|||
|
|
{"name": "stats", "type": "object", "description": "清洗统计信息"}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、HTTP 类型插件
|
|||
|
|
|
|||
|
|
HTTP 插件封装外部 API 调用,可在工作流中直接使用。
|
|||
|
|
|
|||
|
|
### 配置 Schema
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "object",
|
|||
|
|
"properties": {
|
|||
|
|
"method": {
|
|||
|
|
"type": "string",
|
|||
|
|
"enum": ["GET", "POST", "PUT", "DELETE"],
|
|||
|
|
"title": "请求方法",
|
|||
|
|
"default": "POST"
|
|||
|
|
},
|
|||
|
|
"url": {
|
|||
|
|
"type": "string",
|
|||
|
|
"title": "请求 URL",
|
|||
|
|
"description": "支持变量 {{变量名}}"
|
|||
|
|
},
|
|||
|
|
"headers": {
|
|||
|
|
"type": "object",
|
|||
|
|
"title": "自定义请求头"
|
|||
|
|
},
|
|||
|
|
"timeout": {
|
|||
|
|
"type": "integer",
|
|||
|
|
"title": "超时(秒)",
|
|||
|
|
"default": 30
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"required": ["url"]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 创建 HTTP 插件示例
|
|||
|
|
|
|||
|
|
```http
|
|||
|
|
POST /api/v1/plugins
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"name": "天气查询",
|
|||
|
|
"description": "通过 OpenWeather API 查询城市天气",
|
|||
|
|
"plugin_type": "http",
|
|||
|
|
"category": "integration",
|
|||
|
|
"endpoint": "https://api.openweathermap.org/data/2.5/weather",
|
|||
|
|
"config_schema": {
|
|||
|
|
"type": "object",
|
|||
|
|
"properties": {
|
|||
|
|
"method": {"type": "string", "default": "GET"},
|
|||
|
|
"url": {"type": "string", "default": "https://api.openweathermap.org/data/2.5/weather"},
|
|||
|
|
"headers": {"type": "object", "default": {}},
|
|||
|
|
"timeout": {"type": "integer", "default": 15}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"inputs": [
|
|||
|
|
{"name": "city", "type": "string", "required": true, "description": "城市名称"},
|
|||
|
|
{"name": "units", "type": "string", "required": false, "default": "metric", "description": "单位制"}
|
|||
|
|
],
|
|||
|
|
"outputs": [
|
|||
|
|
{"name": "temperature", "type": "number", "description": "温度"},
|
|||
|
|
{"name": "humidity", "type": "number", "description": "湿度"},
|
|||
|
|
{"name": "description", "type": "string", "description": "天气描述"}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、插件沙箱安全
|
|||
|
|
|
|||
|
|
### Code 插件执行环境
|
|||
|
|
|
|||
|
|
- 运行在受限的 Python 沙箱中
|
|||
|
|
- 禁用模块:`os.system`, `subprocess`, `shutil`, `importlib`
|
|||
|
|
- 允许模块:`json`, `re`, `math`, `datetime`, `hashlib`, `base64`, `collections`, `itertools`, `typing`
|
|||
|
|
- 执行超时:默认 30 秒
|
|||
|
|
- 内存限制:64MB
|
|||
|
|
|
|||
|
|
### 安全最佳实践
|
|||
|
|
|
|||
|
|
1. **不要硬编码密钥** — 通过 `context.secrets` 获取
|
|||
|
|
2. **验证所有输入** — 不信任上游数据
|
|||
|
|
3. **异常处理** — 抛 `PluginError` 而非裸 `Exception`
|
|||
|
|
4. **避免无限循环** — 注意循环条件,配合超时
|
|||
|
|
5. **不访问文件系统** — 沙箱已禁用
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、插件测试
|
|||
|
|
|
|||
|
|
### 在线测试
|
|||
|
|
|
|||
|
|
```http
|
|||
|
|
POST /api/v1/plugins/{plugin_id}/test
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"inputs": {
|
|||
|
|
"text": "<p>Hello <b>World</b></p>"
|
|||
|
|
},
|
|||
|
|
"config": {
|
|||
|
|
"remove_html": true,
|
|||
|
|
"remove_extra_spaces": true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
响应:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"success": true,
|
|||
|
|
"outputs": {
|
|||
|
|
"text": "Hello World",
|
|||
|
|
"stats": {
|
|||
|
|
"original_length": 27,
|
|||
|
|
"cleaned_length": 11,
|
|||
|
|
"reduction_pct": 59.3
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"execution_time_ms": 12,
|
|||
|
|
"logs": []
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 本地测试脚本
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# test_plugin.py
|
|||
|
|
from app.models.plugin import NodePlugin
|
|||
|
|
from app.services.plugin_service import execute_code_plugin
|
|||
|
|
|
|||
|
|
|
|||
|
|
def test_my_plugin():
|
|||
|
|
plugin = NodePlugin(
|
|||
|
|
name="测试插件",
|
|||
|
|
plugin_type="code",
|
|||
|
|
code="""
|
|||
|
|
def run(inputs, config, context):
|
|||
|
|
text = inputs.get("text", "")
|
|||
|
|
return {"result": text.upper()}
|
|||
|
|
"""
|
|||
|
|
)
|
|||
|
|
result = execute_code_plugin(
|
|||
|
|
plugin,
|
|||
|
|
inputs={"text": "hello world"},
|
|||
|
|
config={},
|
|||
|
|
context={"execution_id": "test-123"},
|
|||
|
|
)
|
|||
|
|
assert result["result"] == "HELLO WORLD"
|
|||
|
|
print("测试通过")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
test_my_plugin()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、发布到插件市场
|
|||
|
|
|
|||
|
|
### 发布流程
|
|||
|
|
|
|||
|
|
1. 创建并测试插件
|
|||
|
|
2. 设置为公开:`is_public = true`
|
|||
|
|
3. 添加标签和分类
|
|||
|
|
4. 编写清晰的描述和文档
|
|||
|
|
5. 发布:
|
|||
|
|
|
|||
|
|
```http
|
|||
|
|
POST /api/v1/plugins/{plugin_id}/publish
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 市场安装
|
|||
|
|
|
|||
|
|
其他用户可以通过插件市场安装你的插件:
|
|||
|
|
|
|||
|
|
```http
|
|||
|
|
POST /api/v1/plugins/market/install
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"plugin_id": "source-plugin-uuid",
|
|||
|
|
"workspace_id": "target-workspace-uuid"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、最佳实践
|
|||
|
|
|
|||
|
|
### 命名规范
|
|||
|
|
|
|||
|
|
- **插件名称**:简洁描述功能,如"文本清洗器"/"天气查询"
|
|||
|
|
- **输入输出**:使用语义化的字段名,如 `text`/`result` 而非 `data`/`out`
|
|||
|
|
- **分类**:选择最匹配的类型(code/http/transform/trigger/output)
|
|||
|
|
|
|||
|
|
### 错误处理
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
def run(inputs, config, context):
|
|||
|
|
# 1. 必填输入检查
|
|||
|
|
if not inputs.get("required_field"):
|
|||
|
|
raise PluginError("缺少必填输入: required_field")
|
|||
|
|
|
|||
|
|
# 2. 类型验证
|
|||
|
|
value = inputs.get("number_field")
|
|||
|
|
if not isinstance(value, (int, float)):
|
|||
|
|
raise PluginError("number_field 必须是数字")
|
|||
|
|
|
|||
|
|
# 3. 业务逻辑...
|
|||
|
|
try:
|
|||
|
|
result = do_work(value, config)
|
|||
|
|
except Exception as e:
|
|||
|
|
raise PluginError(f"处理失败: {str(e)}")
|
|||
|
|
|
|||
|
|
# 4. 返回结构化输出
|
|||
|
|
return {"result": result}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 性能优化
|
|||
|
|
|
|||
|
|
- 避免在输入循环中使用正则(预编译 `re.compile`)
|
|||
|
|
- HTTP 插件设置合理的超时时间
|
|||
|
|
- 大量数据处理使用生成器而非列表
|
|||
|
|
- 缓存重复计算的结果
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
> 相关文档:[开发指南](./development-guide.md) | [API 参考](./api-reference.md) | [架构设计](./architecture.md)
|