7.3 KiB
7.3 KiB
插件开发指南
Cyrene 插件系统允许第三方开发者为昔涟扩展新工具。插件用 Go 编写,实现
Plugin和Tool接口后,通过plugins.json配置文件即可被自动发现和调用——无需修改 main.go。
核心接口
Plugin(插件入口)
type Plugin interface {
Metadata() PluginMetadata // 插件元信息
Init(ctx context.Context, config PluginConfig) error
Start(ctx context.Context, host HostAPI) error // 启动时获得 HostAPI 访问权
Stop(ctx context.Context) error // 服务关闭时清理资源
Health(ctx context.Context) error // 健康检查
Tools() []Tool // 返回插件提供的工具列表
}
Tool(工具定义)
type Tool interface {
Definition() ToolDefinition
Execute(ctx context.Context, args map[string]interface{}) (*ToolResult, error)
Validate(args map[string]interface{}) error
Complexity() ToolComplexity // simple 或 complex
}
LLM 通过 Definition() 返回的 description 判断何时调用此工具——所以描述要写得清晰准确。
ToolDefinition(完整字段)
type ToolDefinition struct {
ID string `json:"id"` // 唯一标识,通常与 Name 相同
Name string `json:"name"` // 工具名(LLM 用 function_call name 匹配)
DisplayName string `json:"displayName"` // 人类可读名称
Description string `json:"description"` // 描述——LLM 据此判断要不要调用
Category string `json:"category"` // 分类:tool / iot / knowledge / reminder 等
Complexity ToolComplexity `json:"complexity"` // simple(单轮<2s)或 complex(多轮异步)
Parameters map[string]interface{} `json:"parameters"` // JSON Schema 参数定义
Returns map[string]interface{} `json:"returns,omitempty"` // 返回值 JSON Schema(可选)
TimeoutMs int `json:"timeout_ms,omitempty"` // 超时毫秒(默认 8000)
MaxRetries int `json:"max_retries,omitempty"` // 最大重试次数
DangerLevel string `json:"danger_level,omitempty"` // low / medium / high
}
ToolResult
type ToolResult struct {
ToolName string `json:"tool_name"`
Success bool `json:"success"`
Output string `json:"output,omitempty"` // 成功时结果文本(注入 LLM 对话)
Error string `json:"error,omitempty"` // 失败时错误信息
DurationMs int64 `json:"duration_ms,omitempty"`
}
Output 会直接注入 LLM 对话,写成自然语言格式效果更好。
PluginMetadata
type PluginMetadata struct {
Name string `json:"name"`
DisplayName string `json:"displayName"`
Version string `json:"version"`
MinCyreneVersion string `json:"minCyreneVersion"`
Author PluginAuthor `json:"author"`
Description string `json:"description"`
License string `json:"license"`
Keywords []string `json:"keywords,omitempty"`
Category string `json:"category"`
Dependencies map[string]string `json:"dependencies,omitempty"`
Homepage string `json:"homepage,omitempty"`
Repository string `json:"repository,omitempty"`
}
最小示例
package myplugin
import (
"context"
"git.yeij.top/AskaEth/Cyrene-Plugins/sdk"
)
type HelloPlugin struct{}
func (p *HelloPlugin) Metadata() sdk.PluginMetadata {
return sdk.PluginMetadata{
Name: "hello", Version: "1.0.0",
Author: sdk.PluginAuthor{Name: "your-name"},
Description: "示例插件:向用户问好",
}
}
func (p *HelloPlugin) Init(ctx context.Context, cfg sdk.PluginConfig) error { return nil }
func (p *HelloPlugin) Start(ctx context.Context, host sdk.HostAPI) error { return nil }
func (p *HelloPlugin) Stop(ctx context.Context) error { return nil }
func (p *HelloPlugin) Health(ctx context.Context) error { return nil }
func (p *HelloPlugin) Tools() []sdk.Tool {
return []sdk.Tool{&HelloTool{}}
}
// --- Tool ---
type HelloTool struct{}
func (t *HelloTool) Definition() sdk.ToolDefinition {
return sdk.ToolDefinition{
ID: "hello", Name: "hello", DisplayName: "打招呼",
Description: "向用户打招呼。当用户说你好/嗨/哈喽时调用。",
Category: "tool", Complexity: sdk.ComplexitySimple,
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"name": map[string]string{"type": "string", "description": "用户名字(可选)"},
},
},
}
}
func (t *HelloTool) Execute(ctx context.Context, args map[string]interface{}) (*sdk.ToolResult, error) {
name, _ := args["name"].(string)
if name == "" { name = "开拓者" }
return &sdk.ToolResult{Success: true, Output: name + ",昔涟向你问好♪"}, nil
}
func (t *HelloTool) Validate(args map[string]interface{}) error { return nil }
func (t *HelloTool) Complexity() sdk.ToolComplexity { return sdk.ComplexitySimple }
注册
- 将插件目录复制到
backend/plugins/(需包含go.mod+plugin.json) - 运行代码生成器(自动扫描、生成注册代码、补 go.mod):
cd backend/ai-core/cmd && go run gen_plugins.go
- 重新编译 ai-core:
cd backend/ai-core && go build -o main.exe ./cmd/
不需编辑 plugins.json,不需修改 main.go。 生成器自动从 go.mod 读取模块路径。
异步工具(ComplexTool)
对于需要多轮交互或长时间运行的工具,实现 ComplexTool 接口:
type ComplexTool interface {
Tool
ExecuteAsync(ctx context.Context, args map[string]interface{}) (<-chan ToolProgress, error)
Cancel(ctx context.Context, executionID string) error
}
type ToolProgress struct {
ExecutionID string `json:"execution_id"`
Status string `json:"status"` // running / completed / failed / cancelled
Progress float64 `json:"progress"` // 0.0 ~ 1.0
Message string `json:"message,omitempty"`
Error string `json:"error,omitempty"`
Result *ToolResult `json:"result,omitempty"`
}
ExecuteAsync 返回一个进度 channel,前端可实时展示进度条。
调用机制
用户: "帮我打个招呼"
→ LLM 看到所有工具的 Definition()
→ 判断需要调用 hello 工具
→ function_call: {name: "hello", arguments: {name: "开拓者"}}
→ ToolRegistry.Execute("hello", {name: "开拓者"})
→ HelloTool.Execute() → "开拓者,昔涟向你问好♪"
→ LLM 将结果融入回复
插件和内置工具对 LLM 来说无区别——都是同一组 tools 数组里的 function 定义。
相关资源
- cyrene-plugins — 插件 SDK + 内置插件源码
- 工具调用系统介绍 — 完整调用链路说明
backend/plugins/README.md— 插件目录结构