# 插件开发指南

> Cyrene 插件系统允许第三方开发者为昔涟扩展新工具。插件用 Go 编写，实现 `Plugin` 和 `Tool` 接口后，通过 `plugins.json` 配置文件即可被自动发现和调用——**无需修改 main.go**。

---

## 核心接口

### Plugin（插件入口）

```go
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（工具定义）

```go
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（完整字段）

```go
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

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

---

## 最小示例

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

---

## 注册

1. 将插件目录复制到 `backend/plugins/`（需包含 `go.mod` + `plugin.json`）
2. 运行代码生成器（自动扫描、生成注册代码、补 go.mod）：

```bash
cd backend/ai-core/cmd && go run gen_plugins.go
```

3. 重新编译 ai-core：

```bash
cd backend/ai-core && go build -o main.exe ./cmd/
```

**不需编辑 `plugins.json`，不需修改 `main.go`。** 生成器自动从 `go.mod` 读取模块路径。

---

## 异步工具（ComplexTool）

对于需要多轮交互或长时间运行的工具，实现 `ComplexTool` 接口：

```go
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](https://git.yeij.top/AskaEth/Cyrene-Plugins) — 插件 SDK + 内置插件源码
- [工具调用系统介绍](tool-system.md) — 完整调用链路说明
- `backend/plugins/README.md` — 插件目录结构
