From 1f633608dbbbc77c3dee63f43b06c7b8d423c009 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Wed, 24 Jun 2026 19:37:10 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=8F=92=E4=BB=B6=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E8=A1=A5=E5=85=A8=20=E2=80=94=20Stop/Health/?= =?UTF-8?q?Complexity/ComplexTool/ToolDefinition=E5=85=A8=E5=AD=97?= =?UTF-8?q?=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api/plugin-development.md | 140 ++++++++++++++++++++------------- 1 file changed, 85 insertions(+), 55 deletions(-) diff --git a/docs/api/plugin-development.md b/docs/api/plugin-development.md index 95ed621..65895b9 100644 --- a/docs/api/plugin-development.md +++ b/docs/api/plugin-development.md @@ -1,6 +1,6 @@ # 插件开发指南 -> Cyrene 插件系统允许第三方开发者为昔涟扩展新工具。插件用 Go 编写,只需实现 `Plugin` 和 `Tool` 接口即可被自动发现和调用。 +> Cyrene 插件系统允许第三方开发者为昔涟扩展新工具。插件用 Go 编写,实现 `Plugin` 和 `Tool` 接口后,通过 `plugins.json` 配置文件即可被自动发现和调用——**无需修改 main.go**。 --- @@ -10,10 +10,12 @@ ```go type Plugin interface { - Metadata() PluginMetadata // 插件元信息 + Metadata() PluginMetadata // 插件元信息 Init(ctx context.Context, config PluginConfig) error - Start(ctx context.Context, host HostAPI) error - Tools() []Tool // 返回插件提供的工具列表 + Start(ctx context.Context, host HostAPI) error // 启动时获得 HostAPI 访问权 + Stop(ctx context.Context) error // 服务关闭时清理资源 + Health(ctx context.Context) error // 健康检查 + Tools() []Tool // 返回插件提供的工具列表 } ``` @@ -21,21 +23,65 @@ type Plugin interface { ```go type Tool interface { - Definition() ToolDefinition // 工具的 name/description/parameters + 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 +### ToolDefinition(完整字段) ```go type ToolDefinition struct { - Name string // 工具名(LLM 用 function_call name 匹配) - Description string // 描述——LLM 据此判断要不要调用 - Parameters map[string]interface{} // JSON Schema 参数定义 + 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 + +```go +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 + +```go +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"` } ``` @@ -46,13 +92,17 @@ type ToolDefinition struct { ```go package myplugin -import "git.yeij.top/AskaEth/Cyrene-Plugins/sdk" +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: "your-name", + Name: "hello", Version: "1.0.0", + Author: sdk.PluginAuthor{Name: "your-name"}, Description: "示例插件:向用户问好", } } @@ -60,6 +110,7 @@ func (p *HelloPlugin) Metadata() sdk.PluginMetadata { 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{}} @@ -70,8 +121,9 @@ type HelloTool struct{} func (t *HelloTool) Definition() sdk.ToolDefinition { return sdk.ToolDefinition{ - Name: "hello", + ID: "hello", Name: "hello", DisplayName: "打招呼", Description: "向用户打招呼。当用户说你好/嗨/哈喽时调用。", + Category: "tool", Complexity: sdk.ComplexitySimple, Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ @@ -88,13 +140,12 @@ func (t *HelloTool) Execute(ctx context.Context, args map[string]interface{}) (* } func (t *HelloTool) Validate(args map[string]interface{}) error { return nil } +func (t *HelloTool) Complexity() sdk.ToolComplexity { return sdk.ComplexitySimple } ``` --- -## 注册到 Cyrene - -### 方式一:plugins.json(推荐) +## 注册 1. 将插件代码放到 `backend/plugins/<插件名>/` 2. 在 `backend/ai-core/plugins.json` 中添加条目: @@ -109,52 +160,30 @@ func (t *HelloTool) Validate(args map[string]interface{}) error { return nil } cd backend/ai-core/cmd && go run gen_plugins.go ``` -4. 重新编译 ai-core - -### 方式二:直接注册(需要构造参数的插件) - -在 `ai-core/cmd/main.go` 中添加: - -```go -for _, t := range (&myplugin.HelloPlugin{}).Tools() { - toolRegistry.Register(t) -} -``` - -### 插件目录结构 - -``` -backend/plugins/ ← 第三方插件目录 - my-hello/ - plugin.json ← 插件声明(name, version, struct) - main.go ← 插件入口 -``` - -`plugin.json` 格式: - -```json -{ - "name": "my-hello", - "version": "1.0.0", - "author": "your-name", - "description": "我的第一个插件" -} -``` +4. 重新编译 ai-core。**不需要修改 main.go。** --- -## ToolResult 格式 +## 异步工具(ComplexTool) + +对于需要多轮交互或长时间运行的工具,实现 `ComplexTool` 接口: ```go -type ToolResult struct { - ToolName string `json:"tool_name"` - Success bool `json:"success"` - Output string `json:"output,omitempty"` // 成功时的结果文本 - Error string `json:"error,omitempty"` // 失败时的错误信息 +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"` } ``` -`Output` 会直接注入 LLM 对话,所以要写成自然语言格式。 +`ExecuteAsync` 返回一个进度 channel,前端可实时展示进度条。 --- @@ -170,11 +199,12 @@ type ToolResult struct { → LLM 将结果融入回复 ``` -17 个内置工具也是同样的机制,插件和内置工具对 LLM 来说无区别。 +插件和内置工具对 LLM 来说无区别——都是同一组 `tools` 数组里的 function 定义。 --- -## 相关仓库 +## 相关资源 - [cyrene-plugins](https://git.yeij.top/AskaEth/Cyrene-Plugins) — 插件 SDK + 内置插件源码 - [工具调用系统介绍](tool-system.md) — 完整调用链路说明 +- `backend/plugins/README.md` — 插件目录结构