插件开发指南
云雀 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 ≥ 80network权限需要 score ≥ 60memory.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) → boolCognitivePlugin(认知插件)
认知插件是最强大的插件类型,它能参与 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"