Skip to content

插件开发指南

云雀 Agent 的插件系统让你可以扩展 Agent 本身的能力——不是简单地"加一个工具",而是像 Chrome 扩展一样为平台添加全新功能。

架构概览

插件可以做什么?
├── 消费 Agent 能力(调 LLM、搜索、发消息...)
├── 扩展 Agent 能力(注册新技能、新渠道、新 Provider...)
├── 参与 Agent 思考(注入上下文、接管消息、影响记忆...)
└── 独立 UI 面板(Dashboard 标签页、自定义 API)

快速开始

Go 插件(推荐)

go
package main

import (
    "context"
    "yunque-agent/sdk/go/yunque"
)

func main() {
    // 注册一个新技能——Agent 本来没有这个能力
    yunque.RegisterSkill("generate_pdf", "生成 PDF 报告", generatePDF)
    
    // 注册钩子——每次对话后自动执行
    yunque.OnChatAfter(func(ctx context.Context, data map[string]any) {
        reply, _ := data["reply"].(string)
        // 对话后检查是否需要生成报告
        if needsReport(reply) {
            yunque.Send(ctx, "telegram", chatID, "📊 报告已自动生成")
        }
    })
    
    yunque.Run() // 启动插件,等待 Agent 调用
}

func generatePDF(ctx context.Context, args map[string]any) (string, error) {
    title, _ := args["title"].(string)
    content, _ := args["content"].(string)
    
    // 用你喜欢的任何 Go 库(gopdf、gofpdf...)
    // Agent 本身没有 PDF 能力,你的插件赋予了它
    pdf := createPDF(title, content)
    
    return "PDF 已生成: " + pdf.Path(), nil
}

Python 插件

python
import yunque

# 调用 Agent 的 LLM
reply = yunque.llm("你是翻译专家", "Translate this to Japanese: Hello World")

# 搜索网络
results = yunque.search("latest AI research", limit=5)

# 发消息到 Telegram
yunque.send("telegram", "chat_123", "Hello from plugin!")

# 私有记忆
yunque.memory.set("last_run", "2024-03-24")

插件目录结构

data/plugins/my-plugin/
├── plugin.yaml          # 必需:声明文件
├── my-plugin            # Go 二进制(Linux)
├── my-plugin.exe        # Go 二进制(Windows)
├── my-plugin.so         # Go 共享库(Linux,可选优化)
├── handler.py           # Python 处理器(可选)
└── README.md            # 说明文档

plugin.yaml 声明

yaml
name: my-plugin
description: 我的自定义插件
version: 1.0.0
author: your-name

# 插件类型
type: cognitive     # cognitive | function | service

# 语言和入口
language: go        # go | python | node | shell
entrypoint: my-plugin  # 可执行文件名

# 权限声明(类似 Chrome 扩展的 permissions)
permissions:
  - llm              # 调用 LLM
  - search            # 联网搜索
  - memory            # 私有记忆读写
  - memory.read       # 读取 Agent 共享记忆
  - memory.write      # 写入 Agent 共享记忆
  - channel.send      # 发送消息到渠道
  - knowledge         # 访问知识库
  - knowledge.write   # 写入知识库
  - cron              # 定时任务
  - network           # 任意 HTTP 请求
  - filesystem        # 文件读写
  - shell             # 执行命令行 ⚠️ 需管理员审批
  - system.provider   # 注册新 LLM Provider ⚠️
  - system.channel    # 注册新渠道适配器 ⚠️
  - system.skill      # 注册新技能
  - system.slot       # 替换核心模块 ⚠️

# 声明的技能
skills:
  - name: generate_pdf
    description: 生成 PDF 报告
  - name: parse_excel
    description: 解析 Excel 表格

# 生命周期钩子
hooks:
  - chat.before
  - chat.after
  - memory.extract

# 独占槽位(替换核心模块)
slot: ""  # 例如 "memory" 表示替换默认记忆系统

# 认知插件配置
cognitive:
  # ShouldHandle 关键词匹配
  domain_keywords: ["理财", "投资", "股票"]
  handle_confidence: 0.85
  
  # DynamicContext 注入
  context_template: |
    用户的投资偏好和历史建议都在插件记忆中。

# UI 面板
ui:
  tabs:
    - key: my-panel
      label: 我的面板
      icon: Layout
  api:
    - path: /data
      method: GET
    - path: /upload
      method: POST

# 定时任务
cron:
  - expression: "0 8 * * *"
    name: morning_task
    message: "执行每日早间任务"

权限安全模型

权限分级

级别权限安装时
🟢 安全llm, search, memory自动通过
🟡 需确认channel.send, memory.write, network弹窗确认
🔴 危险shell, system.*需管理员密码

Trust Score(信任积分)

  • 新插件起始 trust score = 0
  • 每次成功执行技能 +1
  • 执行失败 -1,危险行为 -50
  • shell 权限需要 score ≥ 80
  • network 权限需要 score ≥ 60
  • memory.write 权限需要 score ≥ 30

沙箱隔离

  • 每个插件有独立的 API Token(只能调声明的权限)
  • 插件间不能互相访问记忆
  • 所有 API 调用走审计链(Merkle Chain)

SDK API 参考

Go SDK (yunque-agent/sdk/go/yunque)

go
// LLM
yunque.LLM(ctx, systemPrompt, userInput) → (string, error)
yunque.Chat(ctx, messages, temperature) → (string, error)

// 搜索
yunque.Search(ctx, query, limit) → ([]SearchResult, error)

// 渠道消息
yunque.Send(ctx, channelType, target, content) → error

// HTTP 请求
yunque.HTTP(ctx, method, url, body, headers) → ([]byte, int, error)

// 插件私有记忆
yunque.Memory.Get(ctx, key) → (string, error)
yunque.Memory.Set(ctx, key, value) → error
yunque.Memory.Delete(ctx, key) → error
yunque.Memory.List(ctx, prefix) → (map[string]string, error)
yunque.Memory.Search(ctx, query, limit) → ([]string, error)

// Agent 共享记忆
yunque.AgentMemory.Search(ctx, query) → (string, error)
yunque.AgentMemory.Add(ctx, fact) → error

// 知识库
yunque.Knowledge.Search(ctx, query, limit) → ([]map[string]any, error)
yunque.Knowledge.Ingest(ctx, content, filename) → error

// 定时任务
yunque.Cron.Add(ctx, expr, name, message) → (string, error)
yunque.Cron.Remove(ctx, id) → error

// 技能注册
yunque.RegisterSkill(name, description, handler)

// 生命周期钩子
yunque.OnChatBefore(handler)
yunque.OnChatAfter(handler)
yunque.OnMemoryExtract(handler)

// 启动
yunque.Run()

Python SDK (yunque)

python
# LLM
yunque.llm(prompt, user_input, model="", temperature=0.7) → str
yunque.chat(messages, temperature=0.7) → str

# 搜索
yunque.search(query, limit=5) → list[dict]

# 渠道消息
yunque.send(channel_type, target, content) → bool

# 插件私有记忆
yunque.memory.get(key) → str
yunque.memory.set(key, value)
yunque.memory.delete(key)
yunque.memory.list(prefix="") → dict
yunque.memory.search(query, limit=10) → list[str]

# Agent 共享记忆
yunque.agent_memory.search(query) → str
yunque.agent_memory.add(fact)

# 知识库
yunque.knowledge.search(query, limit=5) → list[dict]
yunque.knowledge.ingest(content, filename="")

# 定时任务
yunque.cron.add(expr, name, message="") → dict
yunque.cron.remove(job_id) → bool

CognitivePlugin(认知插件)

认知插件是最强大的插件类型,它能参与 Agent 的思考过程:

能力描述普通 Skill 能做到?
DynamicContext每次对话注入领域知识
ShouldHandle接管消息,绕过 Planner
OnMemoryExtract变换记忆提取的事实
PluginMemory独立记忆命名空间
生命周期钩子响应 chat/memory 事件

用 Go 实现 CognitivePlugin

go
package main

import (
    "context"
    "strings"
    "yunque-agent/pkg/plugin"
)

// 实现 CognitivePlugin 接口
type FinancePlugin struct{}

func (p *FinancePlugin) Name() string        { return "finance-advisor" }
func (p *FinancePlugin) Description() string { return "个人理财顾问" }
func (p *FinancePlugin) Skills() []skills.Skill { return nil }
func (p *FinancePlugin) SystemPrompt() string { return "你具备理财领域专业知识。" }

// 每次对话注入用户的投资偏好
func (p *FinancePlugin) DynamicContext(ctx context.Context, msg string) string {
    portfolio := loadFromMemory("user_portfolio")
    if portfolio == "" {
        return ""
    }
    return "用户投资组合:\n" + portfolio
}

// 接管理财相关消息
func (p *FinancePlugin) ShouldHandle(ctx context.Context, msg string) float64 {
    keywords := []string{"理财", "投资", "股票", "基金", "债券"}
    for _, kw := range keywords {
        if strings.Contains(msg, kw) {
            return 0.9
        }
    }
    return 0
}

// 直接处理(绕过 Planner)
func (p *FinancePlugin) Handle(ctx context.Context, msg string, env *plugin.CognitiveEnv) (string, error) {
    context := env.MemorySearch(ctx, msg)
    return env.LLMCall(ctx, "你是资深理财顾问。\n\n历史记录:\n"+context, msg)
}

// 提取记忆时标记金融相关事实
func (p *FinancePlugin) OnMemoryExtract(ctx context.Context, facts []plugin.ExtractedFact) []plugin.ExtractedFact {
    for i := range facts {
        if isFinanceFact(facts[i].Value) {
            facts[i].Tags["domain"] = "finance"
        }
    }
    return facts
}

部署

Linux 服务器(推荐)

Go 插件可编译为 .so 共享库,零进程开销:

bash
# 编译为共享库
go build -buildmode=plugin -o my-plugin.so ./plugins/my-plugin/

# 放到插件目录
cp my-plugin.so data/plugins/my-plugin/

Windows / 跨平台

编译为独立二进制:

bash
go build -o my-plugin.exe ./plugins/my-plugin/

Docker

dockerfile
FROM yunque-agent:latest
COPY my-plugin.so /app/data/plugins/my-plugin/
COPY plugin.yaml /app/data/plugins/my-plugin/

发布到 ToriHub

bash
# 打包插件
tar czf my-plugin-v1.0.0.tar.gz -C data/plugins/my-plugin .

# 发布(需要 ToriHub 账号)
curl -X POST https://torihub.yunque.dev/api/v1/publish \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "package=@my-plugin-v1.0.0.tar.gz"

© 2025 云鸢科技(青岛)有限公司 × Dream Lab