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

9.9 KiB
Raw Blame History

插件开发指南

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 代码实现自定义逻辑。

代码模板

"""
插件入口函数签名:
    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

{
    "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 插件

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

{
    "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 插件示例

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. 不访问文件系统 — 沙箱已禁用

五、插件测试

在线测试

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
    }
}

响应:

{
    "success": true,
    "outputs": {
        "text": "Hello World",
        "stats": {
            "original_length": 27,
            "cleaned_length": 11,
            "reduction_pct": 59.3
        }
    },
    "execution_time_ms": 12,
    "logs": []
}

本地测试脚本

# 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. 发布:
POST /api/v1/plugins/{plugin_id}/publish
Authorization: Bearer <token>

市场安装

其他用户可以通过插件市场安装你的插件:

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

错误处理

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 插件设置合理的超时时间
  • 大量数据处理使用生成器而非列表
  • 缓存重复计算的结果

相关文档:开发指南 | API 参考 | 架构设计