mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
`abort_signal` (asyncio.Event) is passed via **kwargs into payloads during tool_call streaming, causing "Object of type Event is not JSON serializable" when the OpenAI client tries to serialize the request body. Regression test added: test_prepare_chat_payload_strips_non_json_serializable_kwargs
503 lines
12 KiB
Markdown
503 lines
12 KiB
Markdown
# ABP (AstrBot Plugin) 协议规范
|
||
|
||
## 概述
|
||
|
||
ABP 是 AstrBot 的插件通信协议,用于插件的注册、消息处理、工具调用和生命周期管理。
|
||
ABP 采用**插件作为独立服务**的设计,插件通过配置加载,可使用任意编程语言实现。
|
||
|
||
**核心原则**:
|
||
- **主进程不读取插件配置文件**:插件配置通过协议获取
|
||
- **数据目录由主进程分配**:插件的数据存储目录由主进程在握手时告知
|
||
- **零侵入性**:插件无需了解 AstrBot 内部结构
|
||
|
||
> **⚠️ 实现说明**:ABP 协议的核心实现(握手、消息路由、生命周期管理)在 Rust 核心运行时中。
|
||
> 插件本身可使用任意语言实现,只需遵循 ABP 协议即可。
|
||
> 进程内插件(`load_mode: in_process`)示例使用 Python,但核心逻辑由 Rust 承担。
|
||
|
||
## 实现状态
|
||
|
||
| 组件 | 状态 | 说明 |
|
||
|------|------|------|
|
||
| ABP 握手协议 | ⚠️ 待实现 | Rust 核心尚未提交源码 |
|
||
| out_of_process 插件加载 | ⚠️ 待实现 | stdio/Unix Socket/HTTP 传输 |
|
||
| in_process 插件接口 | ⚠️ 待实现 | Python 插件示例待迁移到 Rust |
|
||
| 配置 Schema 生成 | ⚠️ 待实现 | UI 配置表单生成 |
|
||
| 插件生命周期管理 | ⚠️ 待实现 | start/stop/reload |
|
||
|
||
> 相关协议: [MCP](mcp.md)(工具/数据源连接), [agent-message](agent-message.md)(消息处理框架)
|
||
|
||
## 与 MCP 的关系
|
||
|
||
| 维度 | ABP | MCP |
|
||
|------|-----|-----|
|
||
| 定位 | 插件(功能扩展) | 工具/数据源连接 |
|
||
| 通信模式 | 双向(消息+工具+事件) | 单向(工具调用+资源访问) |
|
||
| 配置获取 | 通过协议握手获取 | 通过配置文件声明 |
|
||
| 数据目录 | 主进程分配 | 插件自行管理 |
|
||
| 消息处理 | 支持 | 不支持 |
|
||
|
||
## 插件加载配置
|
||
|
||
AstrBot 配置文件声明插件基本信息(不包含插件配置):
|
||
|
||
```json
|
||
{
|
||
"plugins": [
|
||
{
|
||
"name": "weather-plugin",
|
||
"load_mode": "out_of_process",
|
||
"command": "python",
|
||
"args": ["/path/to/weather_server.py"]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**配置字段**:
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `name` | string | 是 | 插件名称 |
|
||
| `load_mode` | string | 是 | `in_process` 或 `out_of_process` |
|
||
| `command` | string | 跨进程时必填 | 启动命令 |
|
||
| `args` | array | 跨进程时可选 | 命令参数 |
|
||
| `transport` | string | 跨进程时可选 | `stdio` / `unix_socket` / `http` |
|
||
| `url` | string | HTTP 传输时必填 | 服务器地址 |
|
||
|
||
## 初始化握手
|
||
|
||
插件配置通过 `initialize` 握手交换,**主进程不读取插件的配置文件**。
|
||
|
||
### Initialize(主进程 → 插件)
|
||
|
||
```json
|
||
{
|
||
"jsonrpc": "2.0",
|
||
"id": 1,
|
||
"method": "initialize",
|
||
"params": {
|
||
"protocolVersion": "1.0.0",
|
||
"clientInfo": {
|
||
"name": "astrbot",
|
||
"version": "4.16.0"
|
||
},
|
||
"capabilities": {
|
||
"streaming": true,
|
||
"events": true
|
||
},
|
||
"pluginConfig": {
|
||
"user_config": {}
|
||
},
|
||
"dataDirs": {
|
||
"root": "/var/lib/astrbot",
|
||
"plugin_data": "/var/lib/astrbot/data/plugins/my-plugin",
|
||
"temp": "/var/cache/astrbot/temp"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Initialize Response(插件 → 主进程)
|
||
|
||
```json
|
||
{
|
||
"jsonrpc": "2.0",
|
||
"id": 1,
|
||
"result": {
|
||
"protocolVersion": "1.0.0",
|
||
"serverInfo": {
|
||
"name": "weather-plugin",
|
||
"version": "1.0.0"
|
||
},
|
||
"capabilities": {
|
||
"tools": true,
|
||
"handlers": true,
|
||
"events": true,
|
||
"resources": false
|
||
},
|
||
"configSchema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"api_key": {
|
||
"type": "string",
|
||
"description": "Weather API Key",
|
||
"required": true
|
||
},
|
||
"default_location": {
|
||
"type": "string",
|
||
"description": "默认查询城市",
|
||
"default": "北京"
|
||
}
|
||
}
|
||
},
|
||
"metadata": {
|
||
"display_name": "天气插件",
|
||
"description": "提供天气查询功能",
|
||
"author": "Author",
|
||
"support_platforms": ["telegram", "discord"]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 握手参数说明
|
||
|
||
**主进程 → 插件(Initialize params)**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `pluginConfig.user_config` | object | 用户配置(来自 secrets.yaml 等) |
|
||
| `dataDirs.root` | string | AstrBot 数据根目录 |
|
||
| `dataDirs.plugin_data` | string | 插件专用数据目录 |
|
||
| `dataDirs.temp` | string | 插件临时文件目录 |
|
||
|
||
**插件 → 主进程(Initialize result)**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `configSchema` | object | 插件配置 Schema(用于 UI 生成配置表单) |
|
||
| `metadata.*` | object | 插件元数据 |
|
||
|
||
## 数据目录
|
||
|
||
插件的数据存储目录由主进程分配,插件通过握手获取:
|
||
|
||
```
|
||
dataDirs.plugin_data/
|
||
├── config.json # 插件配置(主进程写入)
|
||
├── data/ # 插件业务数据
|
||
└── cache/ # 插件缓存
|
||
```
|
||
|
||
**原则**:
|
||
- 插件不自行决定数据目录
|
||
- 配置由主进程管理,插件通过 `dataDirs.plugin_data/config.json` 读取
|
||
- 插件只需读写自己的目录,无需了解 AstrBot 目录结构
|
||
|
||
## 配置传递
|
||
|
||
### 1. 用户配置(主进程 → 插件)
|
||
|
||
用户通过 WebUI 或配置文件设置的配置,通过握手传递给插件:
|
||
|
||
```json
|
||
{
|
||
"pluginConfig": {
|
||
"user_config": {
|
||
"api_key": "xxx",
|
||
"default_location": "上海"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 配置更新(主进程 → 插件)
|
||
|
||
配置变更时,主进程通知插件:
|
||
|
||
```json
|
||
{
|
||
"jsonrpc": "2.0",
|
||
"method": "plugin.config_update",
|
||
"params": {
|
||
"user_config": {
|
||
"api_key": "yyy"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 配置 Schema(插件声明)
|
||
|
||
插件声明配置 Schema,用于主进程生成配置 UI:
|
||
|
||
```json
|
||
{
|
||
"configSchema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"api_key": {
|
||
"type": "string",
|
||
"description": "API Key",
|
||
"secret": true
|
||
},
|
||
"default_location": {
|
||
"type": "string",
|
||
"description": "默认城市"
|
||
}
|
||
},
|
||
"required": ["api_key"]
|
||
}
|
||
}
|
||
```
|
||
|
||
## 传输协议
|
||
|
||
### Stdio(进程启动)
|
||
|
||
```
|
||
{"jsonrpc":"2.0","method":"initialize","params":{...},"id":1}
|
||
{"jsonrpc":"2.0","method":"plugin.handle_event","params":{...}}
|
||
```
|
||
|
||
### Unix Socket
|
||
|
||
```
|
||
Content-Length: <字节数>\r\n
|
||
\r\n
|
||
<json_body>
|
||
```
|
||
|
||
### HTTP/SSE
|
||
|
||
用于远程插件或需要 Webhook 通知的场景。
|
||
|
||
## 消息格式
|
||
|
||
### 1. 工具调用
|
||
|
||
**请求**:
|
||
|
||
```json
|
||
{
|
||
"jsonrpc": "2.0",
|
||
"id": "req-001",
|
||
"method": "tools/call",
|
||
"params": {
|
||
"name": "get_weather",
|
||
"arguments": {
|
||
"location": "北京"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**响应**:
|
||
|
||
```json
|
||
{
|
||
"jsonrpc": "2.0",
|
||
"id": "req-001",
|
||
"result": {
|
||
"content": [
|
||
{ "type": "text", "text": "北京天气:晴,25°C" }
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 消息处理
|
||
|
||
**请求**:
|
||
|
||
```json
|
||
{
|
||
"jsonrpc": "2.0",
|
||
"id": "req-002",
|
||
"method": "plugin.handle_event",
|
||
"params": {
|
||
"event_type": "message",
|
||
"event": {
|
||
"message_id": "msg-123",
|
||
"unified_msg_origin": "telegram:private:12345",
|
||
"message_str": "/weather 北京",
|
||
"sender": { "user_id": "12345", "nickname": "用户" },
|
||
"message_chain": [{ "type": "plain", "text": "/weather 北京" }]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**响应**:
|
||
|
||
```json
|
||
{
|
||
"jsonrpc": "2.0",
|
||
"id": "req-002",
|
||
"result": {
|
||
"handled": true,
|
||
"results": [{ "type": "plain", "text": "北京天气:晴,25°C" }],
|
||
"stop_propagation": false
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 事件订阅
|
||
|
||
**通知**(主进程 → 插件):
|
||
|
||
```json
|
||
{
|
||
"jsonrpc": "2.0",
|
||
"method": "plugin.subscribe",
|
||
"params": { "event_type": "llm_request" }
|
||
}
|
||
```
|
||
|
||
**通知**(插件 → 主进程):
|
||
|
||
```json
|
||
{
|
||
"jsonrpc": "2.0",
|
||
"method": "plugin.notify",
|
||
"params": {
|
||
"event_type": "tool_called",
|
||
"data": { "tool": "get_weather", "args": { "location": "北京" } }
|
||
}
|
||
}
|
||
```
|
||
|
||
## 核心方法
|
||
|
||
### 生命周期
|
||
|
||
| 方法 | 方向 | 描述 |
|
||
|------|------|------|
|
||
| `initialize` | C→P | 初始化连接,交换配置和数据目录 |
|
||
| `initialized` | P→C | 初始化完成通知 |
|
||
| `plugin.start` | C→P | 启动插件 |
|
||
| `plugin.stop` | C→P | 停止插件 |
|
||
| `plugin.reload` | C→P | 重载插件 |
|
||
| `plugin.config_update` | C→P | 配置更新通知 |
|
||
|
||
### 工具
|
||
|
||
| 方法 | 方向 | 描述 |
|
||
|------|------|------|
|
||
| `tools/list` | C→P | 列出可用工具 |
|
||
| `tools/call` | C→P | 调用工具 |
|
||
|
||
### 消息处理
|
||
|
||
| 方法 | 方向 | 描述 |
|
||
|------|------|------|
|
||
| `plugin.handle_event` | C→P | 处理事件 |
|
||
| `plugin.handle_command` | C→P | 处理命令 |
|
||
| `plugin.handle_message` | C→P | 处理消息 |
|
||
|
||
### 事件
|
||
|
||
| 方法 | 方向 | 描述 |
|
||
|------|------|------|
|
||
| `plugin.subscribe` | C→P | 订阅事件 |
|
||
| `plugin.unsubscribe` | C→P | 取消订阅 |
|
||
| `plugin.notify` | P→C | 发送事件通知 |
|
||
|
||
## 插件元数据
|
||
|
||
插件在 `initialize` 响应中返回元数据:
|
||
|
||
```json
|
||
{
|
||
"serverInfo": {
|
||
"name": "weather-plugin",
|
||
"version": "1.0.0"
|
||
},
|
||
"capabilities": {
|
||
"tools": true,
|
||
"handlers": true,
|
||
"events": true
|
||
},
|
||
"configSchema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"api_key": { "type": "string", "secret": true }
|
||
}
|
||
},
|
||
"metadata": {
|
||
"display_name": "天气插件",
|
||
"description": "提供天气查询功能",
|
||
"author": "Author",
|
||
"support_platforms": ["telegram", "discord"]
|
||
}
|
||
}
|
||
```
|
||
|
||
## 错误码
|
||
|
||
| 码值 | 名称 | 描述 |
|
||
|------|------|------|
|
||
| -32700 | Parse Error | 无效的 JSON |
|
||
| -32600 | Invalid Request | 格式错误的请求 |
|
||
| -32601 | Method Not Found | 未知方法 |
|
||
| -32602 | Invalid Params | 无效参数 |
|
||
| -32603 | Internal Error | 服务器内部错误 |
|
||
| -32200 | Plugin Not Found | 未找到插件 |
|
||
| -32201 | Plugin Not Ready | 插件未就绪 |
|
||
| -32202 | Plugin Crashed | 插件崩溃 |
|
||
| -32203 | Tool Not Found | 未找到工具 |
|
||
| -32204 | Tool Call Failed | 工具调用失败 |
|
||
| -32205 | Handler Not Found | 未找到处理器 |
|
||
| -32206 | Handler Error | 处理器执行错误 |
|
||
| -32207 | Event Subscribe Failed | 事件订阅失败 |
|
||
| -32208 | Permission Denied | 权限不足 |
|
||
| -32209 | Config Error | 配置错误 |
|
||
| -32210 | Dependency Missing | 依赖缺失 |
|
||
| -32211 | Version Mismatch | 版本不兼容 |
|
||
|
||
## 进程内插件
|
||
|
||
对于进程内插件(load_mode: in_process),主进程直接调用插件方法:
|
||
|
||
```python
|
||
class MyPlugin:
|
||
def __init__(self, context, user_config: dict, data_dirs: dict):
|
||
self.context = context
|
||
self.user_config = user_config
|
||
self.data_dir = data_dirs["plugin_data"]
|
||
|
||
async def handle_event(self, event):
|
||
return [PlainResult("Hello!")]
|
||
|
||
def get_tools(self):
|
||
return [MyTool()]
|
||
|
||
def get_metadata(self):
|
||
return {
|
||
"name": "my-plugin",
|
||
"version": "1.0.0",
|
||
"capabilities": {"tools": True, "handlers": True, "events": False},
|
||
"configSchema": {...}
|
||
}
|
||
```
|
||
|
||
## 设计原则
|
||
|
||
1. **主进程不读取插件配置**:插件配置通过协议传递
|
||
2. **数据目录由主进程分配**:插件通过握手获取 `dataDirs`
|
||
3. **配置 Schema 声明**:插件声明配置结构,主进程生成 UI
|
||
4. **插件无侵入性**:插件无需了解 AstrBot 内部结构
|
||
5. **热重载支持**:配置更新通过 `plugin.config_update` 通知
|
||
|
||
## 序列化与校验(RFC #3210)
|
||
|
||
参考 RFC #3210,定义序列化格式和数据校验规范。
|
||
|
||
### 序列化格式
|
||
|
||
| 格式 | 适用场景 | 说明 |
|
||
|------|---------|------|
|
||
| **JSON** | 调试、跨语言 | 兼容性优先,文本可读 |
|
||
| **MessagePack** | 高性能、大数据量 | 二进制,更小数据包 |
|
||
|
||
### 数据校验
|
||
|
||
| 层级 | 说明 |
|
||
|------|------|
|
||
| **消息格式** | 序列化时内建校验 |
|
||
| **配置数据** | pydantic v2 数据模型 |
|
||
| **工具参数** | JSON Schema Draft-07 |
|
||
|
||
## 与 RFC #3210 的关系
|
||
|
||
本规范与 [RFC #3210](https://github.com/AstrBot/AstrBot/issues/3210) 保持一致:
|
||
|
||
| RFC #3210 提案 | ABP 规范对应 |
|
||
|---------------|-------------|
|
||
| 进程间隔离(stdio/websocket) | ✅ `out_of_process` 传输层 |
|
||
| JSON-RPC / MessagePack | ✅ RPC 协议 + 序列化 |
|
||
| 高性能序列化 | ✅ MessagePack 方案 |
|
||
| pydantic 数据校验 | ✅ 配置模型校验 |
|
||
| SDK 解耦 | 插件开发依赖 SDK,与本体解耦 |
|
||
| 与旧插件共存 | ✅ Stars 迁移下阶段 |
|