14 KiB
English | 中文
HomeAgent 插件开发指南
概述
HomeAgent 的所有外部交互能力都来自插件。插件通过 PluginSDK(Go API)与内核交互。
SDK 仓库:插件开发工具、模板代码和示例插件统一托管在 homeagent-sdk 仓库。
git clone https://gitcode.com/JianFeeeee/homeagent-sdk.git
cd homeagent-sdk
每个插件实现一个三方法接口:
type Plugin interface {
Name() string
Start(sdk *PluginSDK) error
Stop() error
}
三种开发方式
| 方式 | 适用场景 | 复杂度 |
|---|---|---|
| 动态 .so/.dll 插件(推荐) | 独立分发的第三方插件 | 中等,使用 plugindev 工具链生成 |
| 内置插件 | 随 HomeAgent 一起发布 | 简单,需合入主仓库 |
| Lua 脚本插件 | 轻量快速原型 | 简单,使用 plugindev init --lua 生成 |
一、快速开始:使用 plugindev 工具链
plugindev 是 SDK 仓库提供的统一插件开发工具链,支持 Go 和 Lua 两种插件类型。
安装
cd homeagent-sdk/tools/plugindev
go build -o plugindev.exe
# 将 plugindev.exe 加入 PATH 或直接使用
创建 Go 插件
plugindev init myplugin
cd myplugin
# 编辑插件代码
vim plugin.go
# 编译打包
plugindev build
# 输出: dist/myplugin_linux_amd64.hmap (或 windows_amd64)
创建 Lua 插件
plugindev init myluaplugin --lua
cd myluaplugin
# 编辑插件代码
vim main.lua
# 本地测试
lua main.lua
# 编译打包
plugindev build
# 输出: dist/myluaplugin_lua.hmap
模板项目结构
Go 插件:
myplugin/
├── plg.json — 插件元信息(名称、版本、入口、目标平台)
├── main.go — 入口点(非 Windows 或非 cgo 时编译)
├── plugin.go — 插件实现(Plugin 接口)
├── go.mod — Go 模块定义
└── README.md — 说明文档
Lua 插件:
myluaplugin/
├── plg.json — 插件元信息(entry: "main.lua", targets: "lua")
├── main.lua — 插件实现(Plugin 接口的 Lua 版本)
├── sdk.lua — SDK 模拟层(支持 `lua main.lua` 独立测试)
└── README.md — 说明文档
编译打包
plugindev build 会自动完成编译和打包:
cd myplugin
plugindev build
执行过程:
- 读取
plg.json确定目标平台 - Go 插件:执行
go build -buildmode=c-shared(生成.so+ C ABI header) - Lua 插件:直接打包源码,无需编译
- 生成
plugin.json清单文件 - 打包为
.hmap分发包(zip 格式,内含plugin.json+plugin.so+plugin.h+main.lua)
输出在 dist/ 目录:
dist/
├── myplugin_linux_amd64.hmap # Go 插件 Linux 版
├── myplugin_windows_amd64.hmap # Go 插件 Windows 版
└── myplugin_lua.hmap # Lua 插件
安装部署
通过 PluginMgr HTTP API 安装:
# 内核 PluginMgr 监听 :9876
curl -X POST http://127.0.0.1:9876/plugins \
-F "file=@dist/myplugin_linux_amd64.hmap"
或通过 WebUI 插件管理页面上传安装。
二、Go 插件开发详解
插件接口
package main
import "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
type Plugin struct {
name string
sdk *sdk.PluginSDK
}
func (p *Plugin) Name() string { return p.name }
func (p *Plugin) Start(s *sdk.PluginSDK) error {
p.sdk = s
// 注册配置项、工具、阶段钩子等
return nil
}
func (p *Plugin) Stop() error {
// 清理资源
return nil
}
// NewPluginFactory 创建插件实例(由 main.go 或 Windows bridge 调用)
func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) {
return &Plugin{name: name}, nil
}
入口点
main.go 提供了 NewPlugin 导出函数,它是内核加载插件时的入口:
//go:build !windows || !cgo
package main
import "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
func NewPlugin(name string, config map[string]interface{}) (sdk.Plugin, error) {
return NewPluginFactory(name, config)
}
对于 -buildmode=c-shared,plugindev build 自动生成 C ABI bridge 代码(z_bridge_gen.go + z_entry.c),无需手动处理。
PluginSDK 核心 API
工具注册 — 让 LLM 可调用你的能力
s.RegisterTool("weather_query", sdk.ToolDef{
Name: "weather_query",
Description: "查询指定城市的天气",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"city": map[string]interface{}{
"type": "string",
"description": "城市名称,如 北京",
},
},
"required": []string{"city"},
},
}, func(args map[string]interface{}) (interface{}, error) {
city, _ := args["city"].(string)
return map[string]interface{}{
"city": city,
"temp": 25,
"weather": "晴",
}, nil
})
阶段钩子 — 干预消息处理流
7 个阶段:
| 阶段 | 时机 | 用途 |
|---|---|---|
on_input |
消息刚到达 Agent | 黑名单、限流、短路 |
pre_action |
即将调用 LLM | 注入上下文 |
post_action |
LLM 返回结果 | 修改输出/工具列表 |
before_toolcall |
工具调用前 | 审计、拒绝、改参 |
after_toolcall |
工具执行后 | 脱敏、改写结果 |
before_output |
输出前 | 格式适配、泄漏清洗 |
after_output |
输出后 | 统计日志 |
// 全局监听:所有插件的阶段事件
s.RegisterStage(sdk.StagePreAction, func(ctx *sdk.StageContext) error {
ctx.Lock()
ctx.ContextMsgs = append(ctx.ContextMsgs, map[string]interface{}{
"role": "system",
"content": "注入的上下文内容",
})
ctx.Unlock()
return nil
})
// 仅自己工具:仅监听自己注册的 tool 的 before_toolcall/after_toolcall
s.RegisterStage(sdk.StageBeforeToolcall, myHandler, sdk.StageScopeOwnTools)
配置管理
// 注册配置项定义
s.Settings().RegisterDef(sdk.ConfigDef{
Key: "plugin.myplugin.api_key",
Default: "",
Type: "string",
DisplayName: "API Key",
Description: "API 密钥",
Category: "myplugin",
})
// 读写配置
val, err := s.Settings().Get("api_key")
s.Settings().Set("api_key", "new-value")
// 读取核心配置
s.Settings().GetCore("llm.model")
// 读取其他插件配置
s.Settings().GetPlugin("other_plugin", "some_key")
输入投递
// 排队投递(按序处理)
s.InjectInput(source, channel, eventType string, payload map[string]interface{})
// 中断投递(可打断当前 LLM 处理)
s.InjectInterrupt(source, channel, eventType string, payload map[string]interface{})
// 快捷方式
s.InjectText(source, channel, text string)
s.InjectInterruptText(source, channel, text string)
事件订阅
unsub := s.Subscribe("tool_call", func(evt *events.Event) {
log.Printf("工具被调用: %v", evt.Payload)
})
defer unsub()
能力访问
// 记忆
s.Memory().Recall(query string) ([]MemItem, error)
s.Memory().Commit(triples []Triple) error
// 知识
s.Knowledge().Search(query string) ([]string, error)
// LLM 源管理
s.LLM().ListSources() []SourceInfo
s.LLM().SetSource(name string) error
事件订阅(内置插件)
// 订阅系统事件,返回取消订阅函数
unsub := s.Subscribe("tool_call", func(evt *events.Event) {
log.Printf("工具被调用: %v", evt.Payload)
})
defer unsub()
// 发布事件
s.Publish(&events.Event{
Type: "custom_event",
Payload: map[string]interface{}{"key": "value"},
})
IO 通道管理(内置插件)
// 注册通道(绑定设备驱动),dev 必须实现 agentIO.Device 接口:
// Name() string
// Type() DeviceType
// Description() string
// Tools() []ToolDef
// Execute(tool string, args map[string]interface{}) (interface{}, error)
// Start() error
// Stop() error
// OutputCapabilities() OutputCapability
s.RegisterChannel("mydevice", deviceImpl)
// 注销通道
s.UnregisterChannel("mydevice")
// 列出所有通道
channels := s.ListChannels()
注意:
Subscribe、Publish、RegisterChannel、UnregisterChannel、ListChannels、InjectInput、InjectInputSync、InjectInterrupt、InjectTextSync、InjectTextSyncNoMemory、OutputChan这些方法仅在内置插件中可用(internal/sdk包),外部动态插件无法访问。外部插件请使用InjectText、InjectInterruptText、InjectTextNoMemory等公共 API。
三、Lua 插件开发详解
Lua 插件适合轻量级快速原型,无需 Go 编译环境,修改后直接重启内核即可生效。
插件结构
-- main.lua
local plugin = {
name = "myluaplugin"
}
function plugin.start(sdk)
sdk.log("info", "myluaplugin starting...")
sdk.register_tool("myluaplugin_hello", {
description = "A hello world tool",
parameters = {
type = "object",
properties = {}
}
}, function(args)
return { content = "Hello from myluaplugin plugin!" }
end)
sdk.log("info", "myluaplugin started")
end
function plugin.stop()
sdk.log("info", "myluaplugin stopped")
end
return plugin
SDK 模拟层
sdk.lua 提供纯 Lua 的 SDK 模拟实现,支持 lua main.lua 独立测试:
lua main.lua
# 输出:
# [lua-plugin] info: myluaplugin starting...
# [lua-plugin] register_tool: myluaplugin_hello
# [lua-plugin] info: myluaplugin started
在内核中运行时,sdk.* 全局变量由 Go 层注入,所有 -- !impl 标记的函数会被替换为真实实现。
Lua SDK API
| 函数 | 说明 |
|---|---|
sdk.log(level, msg) |
日志输出 |
sdk.register_tool(name, def, handler) |
注册工具 |
sdk.register_stage(stage, handler) |
注册阶段钩子 |
sdk.register_api(name) |
注册 API |
sdk.get_setting(key) |
读取配置 |
sdk.set_setting(key, value) |
写入配置 |
sdk.inject_text(source, channel, text) |
投递文本消息 |
sdk.inject_interrupt(source, channel, text) |
中断投递 |
sdk.json.encode(val) |
JSON 编码 |
sdk.json.decode(str) |
JSON 解码 |
sdk.http.get(url) |
HTTP GET 请求(-- !impl) |
sdk.http.post(url, body, content_type) |
HTTP POST 请求(-- !impl) |
注意:Lua 插件的
sdk.register_stage阶段回调目前仅传递raw_message、user_id、phase三个字段,功能受限。复杂的阶段处理逻辑建议使用 Go 插件。
四、内置插件
内置插件使用 init() 自注册方式,编译进内核,无需单独部署。
目录结构
internal/plugins/yourplugin/
plugin.go — 插件主文件
最小插件示例
package yourplugin
import (
"gitcode.com/JianFeeeee/HomeAgent/internal/plugin"
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
)
func init() {
plugin.RegisterFactory("yourplugin", func(name string, config map[string]interface{}) (sdk.Plugin, error) {
return New(name), nil
})
}
type Plugin struct {
name string
}
func New(name string) *Plugin {
return &Plugin{name: name}
}
func (p *Plugin) Name() string { return p.name }
func (p *Plugin) Start(s *sdk.PluginSDK) error {
// 在这里初始化插件:启动 goroutine、注册工具、订阅事件等
return nil
}
func (p *Plugin) Stop() error {
// 清理资源
return nil
}
注册到内核
在 internal/plugins/all.go 中添加空白导入:
package plugins
import (
_ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/yourplugin"
// ... 其他插件
)
五、最佳实践
Start()非阻塞 — goroutine 启动长任务,不要阻塞 StartStop()清理资源 — 关连接、停 goroutine、取消订阅- 工具名唯一 — 建议插件名前缀避免冲突
- handler 返回
error时 LLM 会收到并可能重试 - 打断用
InjectInterruptText,普通投递用InjectText - 配置用
Settings().Get/Set,不要硬编码 - 外部 Go 插件独立编译,不依赖内核版本;内置插件才需随内核重新编译
六、示例插件参考
SDK 仓库示例(homeagent-sdk/example/)
| 示例 | 类型 | 特点 |
|---|---|---|
| memo | Go | 备忘管理,PreAction 注入 + 定时打断双提醒 |
| files | Go | 文件系统操作,4 种写入模式,沙箱隔离 |
| web | Go | DuckDuckGo 搜索 + 网页抓取,SSRF 防护 |
| Go | NapCat OneBot 对接,17 个工具 | |
| bili | Go | B 站视频下载(you-get) |
| editdoc | Go | Office 文档编辑与格式转换 |
| a2a | Go | Agent-to-Agent 协议 |
| ocr | Go | 离线文字识别(Tesseract) |
| sanitizer | Go | 输出清洗过滤器 |
| luaplugintest | Lua | Lua 插件 Hello World |
| testlua | Lua | Lua 插件示例 |
内置插件
| 插件 | 位置 | 特点 |
|---|---|---|
| Timer | internal/plugins/timer/ |
最简单的完整示例,注册一个工具 + 中断反馈 |
| CLI | internal/plugins/cli/ |
Unix socket 监听 + 同步请求响应 |
| WebUI | internal/plugins/webui/ |
HTTP 服务 + 依赖注入 |
了解项目整体目标?查看 OVERVIEW.md。 了解技术架构?查看 ARCHITECTURE.md。 SDK 仓库与开发工具?查看 homeagent-sdk。