Files
aiagent/docs/plugin-development-guide.md
renjianbo beff3fac8d fix: delete agent 500 error + dynamic personality + deployment guide
- Fix delete agent 500: clean up FK records (agent_llm_logs, permissions,
  schedules, executions, team_members) and unbind goals/tasks before delete
- Remove hardcoded personality templates in Android, replace with dynamic
  system prompt generation from name + description
- Set promptSectionsEnabled=false to bypass PromptComposer for personality
- Add Tencent Cloud Linux deployment guide (Docker Compose)
- Accumulated backend service updates, frontend UI fixes, Android app changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-29 01:17:21 +08:00

406 lines
9.9 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.
# 插件开发指南
> **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)