mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-17 01:49:15 +08:00
Add architecture doc and refine API compat
- Add PROJECT_ARCHITECTURE.md documenting architecture, compat surface, and testing notes. - Update astrbot_sdk.api.__init__ to clarify it is a compatibility implementation layer, not a simple facade, and list migration targets. - Normalize platform in AstrMessageEvent.to_payload to emit a string id by using get_platform_id().
This commit is contained in:
981
PROJECT_ARCHITECTURE.md
Normal file
981
PROJECT_ARCHITECTURE.md
Normal file
@@ -0,0 +1,981 @@
|
||||
# AstrBot SDK 项目完整架构分析文档
|
||||
|
||||
> 作者:whatevertogo
|
||||
> 更新时间:2026-03-14
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [项目概述](#项目概述)
|
||||
2. [目录结构](#目录结构)
|
||||
3. [核心架构层次](#核心架构层次)
|
||||
4. [协议层设计](#协议层设计)
|
||||
5. [运行时架构](#运行时架构)
|
||||
6. [客户端层设计](#客户端层设计)
|
||||
7. [新旧架构对比](#新旧架构对比)
|
||||
8. [兼容层设计](#兼容层设计)
|
||||
9. [插件开发指南](#插件开发指南)
|
||||
10. [关键设计模式](#关键设计模式)
|
||||
|
||||
---
|
||||
|
||||
## 项目概述
|
||||
|
||||
AstrBot SDK 是一个基于 Python 3.12+ 的机器人插件开发框架,采用**进程隔离**和**能力路由**架构,支持插件的动态加载、独立运行和跨进程通信。
|
||||
|
||||
### 核心特性
|
||||
|
||||
| 特性 | 描述 |
|
||||
|------|------|
|
||||
| 进程隔离 | 每个插件运行在独立 Worker 进程,崩溃不影响其他插件 |
|
||||
| 环境分组 | 多插件可共享同一 Python 虚拟环境,节省资源 |
|
||||
| 能力路由 | 显式声明的 Capability 系统,支持 JSON Schema 验证 |
|
||||
| 流式支持 | 原生支持流式 LLM 调用和增量结果返回 |
|
||||
| 向后兼容 | 完整的旧版 API 兼容层,支持无修改迁移 |
|
||||
| 协议优先 | 基于 v4 协议的统一通信模型,支持多种传输方式 |
|
||||
|
||||
### 技术栈
|
||||
|
||||
- **Python**: 3.12+
|
||||
- **异步框架**: asyncio
|
||||
- **Web 框架**: aiohttp
|
||||
- **数据验证**: pydantic
|
||||
- **日志**: loguru
|
||||
- **配置**: pyyaml
|
||||
- **LLM**: openai, anthropic, google-genai
|
||||
- **包管理**: uv (环境分组)
|
||||
|
||||
---
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
astrbot-sdk/
|
||||
├── src/ # 旧版实现 (已停止更新)
|
||||
│ └── astrbot_sdk/ # 旧版 SDK
|
||||
├── src-new/ # 新版 v4 实现 (当前活跃)
|
||||
│ └── astrbot_sdk/ # v4 SDK 主包
|
||||
│ ├── __init__.py # 顶层公共 API
|
||||
│ ├── star.py # v4 原生插件基类
|
||||
│ ├── context.py # 运行时上下文
|
||||
│ ├── decorators.py # v4 原生装饰器
|
||||
│ ├── events.py # v4 原生事件对象
|
||||
│ ├── errors.py # 统一错误模型
|
||||
│ ├── cli.py # 命令行工具
|
||||
│ ├── testing.py # 测试辅助模块
|
||||
│ │
|
||||
│ ├── clients/ # 能力客户端层
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── _proxy.py # CapabilityProxy 能力代理
|
||||
│ │ ├── llm.py # LLM 客户端
|
||||
│ │ ├── memory.py # 记忆存储客户端
|
||||
│ │ ├── db.py # KV 存储客户端
|
||||
│ │ ├── platform.py # 平台消息客户端
|
||||
│ │ ├── http.py # HTTP 注册客户端
|
||||
│ │ └── metadata.py # 插件元数据客户端
|
||||
│ │
|
||||
│ ├── protocol/ # 协议层
|
||||
│ │ ├── messages.py # v4 协议消息模型
|
||||
│ │ ├── descriptors.py # Handler/Capability 描述符
|
||||
│ │ └── legacy_adapter.py # JSON-RPC ↔ v4 适配器
|
||||
│ │
|
||||
│ ├── runtime/ # 运行时层
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── peer.py # 协议对等端
|
||||
│ │ ├── transport.py # 传输抽象与实现
|
||||
│ │ ├── handler_dispatcher.py # Handler 执行分发
|
||||
│ │ ├── capability_router.py # Capability 路由
|
||||
│ │ ├── loader.py # 插件加载
|
||||
│ │ ├── bootstrap.py # 启动引导
|
||||
│ │ └── environment_groups.py # 环境分组管理
|
||||
│ │
|
||||
│ ├── api/ # 旧版 API 兼容 facade
|
||||
│ │ ├── basic/ # 基础配置与对话管理
|
||||
│ │ ├── components/ # 命令组件
|
||||
│ │ ├── event/ # 事件类型与过滤器
|
||||
│ │ ├── message/ # 消息链
|
||||
│ │ ├── message_components.py # 消息组件别名
|
||||
│ │ ├── platform/ # 平台元数据
|
||||
│ │ ├── provider/ # Provider 实体
|
||||
│ │ └── star/ # Star 基类与上下文
|
||||
│ │
|
||||
│ ├── compat.py # 顶层兼容入口
|
||||
│ ├── _legacy_api.py # 旧版 API 兼容实现
|
||||
│ ├── _legacy_runtime.py # Legacy 执行边界
|
||||
│ ├── _legacy_loader.py # Legacy 插件发现
|
||||
│ ├── _legacy_llm.py # Legacy LLM/tool 兼容
|
||||
│ ├── _session_waiter.py # session_waiter 兼容
|
||||
│ └── _shared_preferences.py # 共享偏好兼容
|
||||
│
|
||||
├── src-new/astrbot/ # 旧包名兼容 facade
|
||||
│ ├── api/ # astrbot.api.* 兼容
|
||||
│ │ ├── event/
|
||||
│ │ ├── star/
|
||||
│ │ ├── components/
|
||||
│ │ ├── platform/
|
||||
│ │ ├── provider/
|
||||
│ │ └── util/
|
||||
│ └── core/ # astrbot.core.* 兼容
|
||||
│ ├── platform/
|
||||
│ ├── provider/
|
||||
│ ├── message/
|
||||
│ ├── utils/
|
||||
│ ├── agent/
|
||||
│ └── db/
|
||||
│
|
||||
├── tests_v4/ # v4 测试套件
|
||||
│ ├── unit/ # 单元测试
|
||||
│ ├── integration/ # 集成测试
|
||||
│ └── external_plugin_matrix.json # 外部插件兼容矩阵
|
||||
│
|
||||
├── test_plugin/ # 测试插件样本
|
||||
│ ├── new/ # v4 原生插件示例
|
||||
│ │ ├── plugin.yaml
|
||||
│ │ └── commands/
|
||||
│ │ └── hello.py
|
||||
│ │
|
||||
│ └── old/ # 旧版兼容插件示例
|
||||
│ ├── plugin.yaml
|
||||
│ └── main.py
|
||||
│
|
||||
├── astrBot/ # 参考 AstrBot 应用
|
||||
│
|
||||
├── pyproject.toml # 项目配置
|
||||
├── ARCHITECTURE.md # 架构文档
|
||||
├── refactor.md # 重构历史
|
||||
└── run_tests.py # 测试入口
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心架构层次
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 用户层 (Plugin Developer) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 新代码入口: astrbot_sdk.{Star, Context, MessageEvent} │
|
||||
│ 旧代码入口: astrbot_sdk.api.* / astrbot.api.* │
|
||||
└────────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
┌──────────────────▼───────────────────────────────────────────┐
|
||||
│ 高层 API (High-Level API) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 原生客户端: clients/{llm, memory, db, platform, ...} │
|
||||
│ 兼容 Facade: _legacy_api.py (LegacyContext, ...) │
|
||||
└──────────────────┬───────────────────────────────────────────┘
|
||||
│
|
||||
┌──────────────────▼───────────────────────────────────────────┐
|
||||
│ 执行边界 (Execution Boundary) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ runtime 主干: │
|
||||
│ - loader.py (插件发现、加载) │
|
||||
│ - bootstrap.py (Supervisor/Worker 启动) │
|
||||
│ - handler_dispatcher.py (Handler 执行分发) │
|
||||
│ - capability_router.py (Capability 路由) │
|
||||
│ - peer.py (协议对等端) │
|
||||
│ - transport.py (传输抽象) │
|
||||
│ compat 私有实现: │
|
||||
│ - _legacy_runtime.py (legacy 执行适配) │
|
||||
│ - _legacy_loader.py (legacy 插件发现) │
|
||||
│ - _session_waiter.py (session_waiter 兼容) │
|
||||
└──────────────────┬───────────────────────────────────────────┘
|
||||
│
|
||||
┌──────────────────▼───────────────────────────────────────────┐
|
||||
│ 协议与传输 (Protocol & Transport) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ protocol/ │
|
||||
│ - messages.py (协议消息模型) │
|
||||
│ - descriptors.py (Handler/Capability 描述符) │
|
||||
│ - legacy_adapter.py (JSON-RPC ↔ v4 适配器) │
|
||||
│ transport 实现: │
|
||||
│ - StdioTransport (标准输入输出) │
|
||||
│ - WebSocketServerTransport (WebSocket 服务端) │
|
||||
│ - WebSocketClientTransport (WebSocket 客户端) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 层次职责
|
||||
|
||||
| 层次 | 职责 | 主要模块 |
|
||||
|------|------|---------|
|
||||
| 用户层 | 插件开发者 API | `Star`, `Context`, `MessageEvent`, 装饰器 |
|
||||
| 高层 API | 类型化的能力客户端 | `clients/`, `_legacy_api.py` |
|
||||
| 执行边界 | 插件加载、路由、分发 | `runtime/loader.py`, `runtime/bootstrap.py` |
|
||||
| 协议层 | 消息模型、描述符 | `protocol/` |
|
||||
| 传输层 | 底层通信抽象 | `runtime/transport.py` |
|
||||
|
||||
---
|
||||
|
||||
## 协议层设计
|
||||
|
||||
### 消息模型
|
||||
|
||||
v4 协议定义了 5 种消息类型:
|
||||
|
||||
| 消息类型 | 用途 | 关键字段 |
|
||||
|---------|------|---------|
|
||||
| `InitializeMessage` | 握手初始化 | `protocol_version`, `peer`, `handlers`, `provided_capabilities` |
|
||||
| `InvokeMessage` | 调用能力 | `capability`, `input`, `stream` |
|
||||
| `ResultMessage` | 返回结果 | `success`, `output`, `error` |
|
||||
| `EventMessage` | 流式事件 | `phase` (started/delta/completed/failed), `data` |
|
||||
| `CancelMessage` | 取消调用 | `reason` |
|
||||
|
||||
### 握手流程
|
||||
|
||||
```
|
||||
Worker (Plugin) Supervisor (Core)
|
||||
| |
|
||||
| InitializeMessage |
|
||||
|----------------------------->|
|
||||
| | 创建 CapabilityRouter
|
||||
| | 注册 handler.invoke
|
||||
| |
|
||||
| ResultMessage(kind="init") |
|
||||
|<-----------------------------|
|
||||
| | 等待 handler.invoke 调用
|
||||
| | 执行 CapabilityRouter.execute()
|
||||
| |
|
||||
| InvokeMessage(handler.invoke) |
|
||||
|<-----------------------------|
|
||||
| HandlerDispatcher.invoke() |
|
||||
| 执行用户 handler |
|
||||
| |
|
||||
| ResultMessage(output) |
|
||||
|----------------------------->|
|
||||
```
|
||||
|
||||
### 描述符模型
|
||||
|
||||
#### HandlerDescriptor
|
||||
|
||||
```python
|
||||
{
|
||||
"id": "plugin.module:handler_name",
|
||||
"trigger": {
|
||||
"type": "command",
|
||||
"command": "hello",
|
||||
"aliases": ["hi"],
|
||||
"description": "打招呼命令"
|
||||
},
|
||||
"priority": 0,
|
||||
"permissions": {
|
||||
"require_admin": False,
|
||||
"level": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### CapabilityDescriptor
|
||||
|
||||
```python
|
||||
{
|
||||
"name": "llm.chat",
|
||||
"description": "发送对话请求,返回文本",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"prompt": {"type": "string"}
|
||||
},
|
||||
"required": ["prompt"]
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {"type": "string"}
|
||||
},
|
||||
"required": ["text"]
|
||||
},
|
||||
"supports_stream": False,
|
||||
"cancelable": False
|
||||
}
|
||||
```
|
||||
|
||||
### 内置 Capabilities (18个)
|
||||
|
||||
| 命名空间 | 能力 | 说明 |
|
||||
|----------|------|------|
|
||||
| `llm` | `chat` | 同步对话,返回文本 |
|
||||
| `llm` | `chat_raw` | 同步对话,返回完整响应 |
|
||||
| `llm` | `stream_chat` | 流式对话 |
|
||||
| `memory` | `search` | 搜索记忆 |
|
||||
| `memory` | `save` | 保存记忆 |
|
||||
| `memory` | `get` | 读取单条记忆 |
|
||||
| `memory` | `delete` | 删除记忆 |
|
||||
| `db` | `get` | 读取 KV |
|
||||
| `db` | `set` | 写入 KV |
|
||||
| `db` | `delete` | 删除 KV |
|
||||
| `db` | `list` | 列出 KV 键 |
|
||||
| `db` | `get_many` | 批量读取 KV |
|
||||
| `db` | `set_many` | 批量写入 KV |
|
||||
| `db` | `watch` | 订阅 KV 变更 |
|
||||
| `platform` | `send` | 发送消息 |
|
||||
| `platform` | `send_image` | 发送图片 |
|
||||
| `platform` | `send_chain` | 发送消息链 |
|
||||
| `platform` | `get_members` | 获取群成员 |
|
||||
|
||||
---
|
||||
|
||||
## 运行时架构
|
||||
|
||||
### 组件关系图
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ AstrBot │
|
||||
│ Core │
|
||||
└──────┬─────┘
|
||||
│
|
||||
┌──────▼─────┐
|
||||
│ Supervisor │
|
||||
│ Runtime │
|
||||
└──────┬─────┘
|
||||
│
|
||||
┌──────────────────┼──────────────────┐
|
||||
│ │ │
|
||||
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
|
||||
│ Peer │ │ Peer │ │ Peer │
|
||||
│ (stdio) │ │ (stdio) │ │ (stdio) │
|
||||
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
|
||||
│ │ │
|
||||
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
|
||||
│ Worker │ │ Worker │ │ Worker │
|
||||
│ Runtime │ │ Runtime │ │ Runtime │
|
||||
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
|
||||
│ │ │
|
||||
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
|
||||
│ Plugin A │ │ Plugin B │ │ Plugin C │
|
||||
│ (v4/old) │ │ (v4/old) │ │ (v4/old) │
|
||||
└───────────┘ └───────────┘ └───────────┘
|
||||
```
|
||||
|
||||
### SupervisorRuntime
|
||||
|
||||
职责:管理多个 Worker 进程,聚合所有 handler
|
||||
|
||||
```python
|
||||
class SupervisorRuntime:
|
||||
def __init__(self, *, transport, plugins_dir, env_manager):
|
||||
self.transport = transport # 与 Core 的传输层
|
||||
self.plugins_dir = plugins_dir # 插件目录
|
||||
self.capability_router = CapabilityRouter() # 能力路由器
|
||||
self.peer = Peer(...) # 与 Core 的对等端
|
||||
self.worker_sessions = {} # Worker 会话映射
|
||||
self.handler_to_worker = {} # Handler → Worker 映射
|
||||
|
||||
async def start(self):
|
||||
# 1. 发现所有插件
|
||||
discovery = discover_plugins(self.plugins_dir)
|
||||
|
||||
# 2. 规划环境分组
|
||||
plan_result = self.env_manager.plan(discovery.plugins)
|
||||
|
||||
# 3. 为每个分组启动 Worker
|
||||
for group in plan_result.groups:
|
||||
session = WorkerSession(group=group, ...)
|
||||
await session.start()
|
||||
self.worker_sessions[group.id] = session
|
||||
|
||||
# 4. 聚合所有 handler 和 capability
|
||||
await self.peer.initialize(
|
||||
handlers=[...],
|
||||
provided_capabilities=self.capability_router.descriptors()
|
||||
)
|
||||
```
|
||||
|
||||
### WorkerSession
|
||||
|
||||
职责:管理单个 Worker 进程的生命周期
|
||||
|
||||
```python
|
||||
class WorkerSession:
|
||||
def __init__(self, *, group, env_manager, capability_router):
|
||||
self.group = group # 环境分组
|
||||
self.peer = Peer(...) # 与 Worker 的对等端
|
||||
self.capability_router = capability_router
|
||||
self.handlers = [] # Worker 注册的 handlers
|
||||
self.provided_capabilities = [] # Worker 提供的 capabilities
|
||||
|
||||
async def start(self):
|
||||
# 启动 Worker 子进程
|
||||
python_path = self.env_manager.prepare_group_environment(self.group)
|
||||
transport = StdioTransport(
|
||||
command=[python_path, "-m", "astrbot_sdk", "worker", "--group-metadata", ...]
|
||||
)
|
||||
self.peer = Peer(transport=transport, ...)
|
||||
|
||||
# 等待 Worker 初始化完成
|
||||
await self.peer.start()
|
||||
await self.peer.wait_until_remote_initialized()
|
||||
|
||||
# 获取 Worker 的注册信息
|
||||
self.handlers = list(self.peer.remote_handlers)
|
||||
self.provided_capabilities = list(self.peer.remote_provided_capabilities)
|
||||
|
||||
async def invoke_capability(self, capability_name, payload, *, request_id):
|
||||
# 转发能力调用到 Worker
|
||||
return await self.peer.invoke(capability_name, payload, request_id=request_id)
|
||||
```
|
||||
|
||||
### PluginWorkerRuntime
|
||||
|
||||
职责:Worker 进程内的插件加载与执行
|
||||
|
||||
```python
|
||||
class PluginWorkerRuntime:
|
||||
def __init__(self, *, plugin_dir, transport):
|
||||
self.plugin = load_plugin_spec(plugin_dir)
|
||||
self.loaded_plugin = load_plugin(self.plugin)
|
||||
self.peer = Peer(transport=transport, ...)
|
||||
self.dispatcher = HandlerDispatcher(...)
|
||||
self.capability_dispatcher = CapabilityDispatcher(...)
|
||||
|
||||
async def start(self):
|
||||
# 1. 向 Supervisor 注册 handlers 和 capabilities
|
||||
await self.peer.initialize(
|
||||
handlers=[h.descriptor for h in self.loaded_plugin.handlers],
|
||||
provided_capabilities=[c.descriptor for c in self.loaded_plugin.capabilities]
|
||||
)
|
||||
|
||||
# 2. 执行 on_start 生命周期
|
||||
await self._run_lifecycle("on_start")
|
||||
|
||||
# 3. 设置消息处理器
|
||||
self.peer.set_invoke_handler(self._handle_invoke)
|
||||
self.peer.set_cancel_handler(self._handle_cancel)
|
||||
|
||||
async def _handle_invoke(self, message, cancel_token):
|
||||
if message.capability == "handler.invoke":
|
||||
return await self.dispatcher.invoke(message, cancel_token)
|
||||
return await self.capability_dispatcher.invoke(message, cancel_token)
|
||||
```
|
||||
|
||||
### HandlerDispatcher
|
||||
|
||||
职责:将 handler.invoke 请求转成真实 Python 调用
|
||||
|
||||
```python
|
||||
class HandlerDispatcher:
|
||||
def __init__(self, *, plugin_id, peer, handlers):
|
||||
self._handlers = {item.descriptor.id: item for item in handlers}
|
||||
self._peer = peer
|
||||
self._active = {} # request_id → (task, cancel_token)
|
||||
|
||||
async def invoke(self, message, cancel_token):
|
||||
# 1. 查找 handler
|
||||
loaded = self._handlers[message.input["handler_id"]]
|
||||
|
||||
# 2. 创建上下文
|
||||
ctx = Context(peer=self._peer, plugin_id=plugin_id, cancel_token=cancel_token)
|
||||
event = MessageEvent.from_payload(message.input["event"], context=ctx)
|
||||
|
||||
# 3. 构建参数 (支持类型注解注入)
|
||||
args = self._build_args(loaded.callable, event, ctx)
|
||||
|
||||
# 4. 执行 handler
|
||||
result = loaded.callable(*args)
|
||||
|
||||
# 5. 处理返回值
|
||||
await self._consume_result(result, event, ctx)
|
||||
```
|
||||
|
||||
**参数注入优先级**:
|
||||
1. 按类型注解注入(`MessageEvent`, `Context`)
|
||||
2. 按参数名注入(`event`, `ctx`, `context`)
|
||||
3. 从 legacy_args 注入(命令参数等)
|
||||
|
||||
### CapabilityRouter
|
||||
|
||||
职责:能力注册、发现和执行路由
|
||||
|
||||
```python
|
||||
class CapabilityRouter:
|
||||
def __init__(self):
|
||||
self._registrations = {} # capability_name → registration
|
||||
self.db_store = {} # 内置 KV 存储
|
||||
self.memory_store = {} # 内置记忆存储
|
||||
self._register_builtin_capabilities()
|
||||
|
||||
def register(self, descriptor, *, call_handler, stream_handler, finalize):
|
||||
"""注册能力"""
|
||||
self._registrations[descriptor.name] = _CapabilityRegistration(
|
||||
descriptor=descriptor,
|
||||
call_handler=call_handler,
|
||||
stream_handler=stream_handler,
|
||||
finalize=finalize
|
||||
)
|
||||
|
||||
async def execute(self, capability, payload, *, stream, cancel_token, request_id):
|
||||
"""执行能力调用"""
|
||||
registration = self._registrations[capability]
|
||||
|
||||
if stream:
|
||||
# 流式调用
|
||||
raw_execution = registration.stream_handler(request_id, payload, cancel_token)
|
||||
return StreamExecution(iterator=raw_execution, finalize=finalize)
|
||||
else:
|
||||
# 同步调用
|
||||
output = await registration.call_handler(request_id, payload, cancel_token)
|
||||
return output
|
||||
```
|
||||
|
||||
### 环境分组管理
|
||||
|
||||
```python
|
||||
class EnvironmentPlanner:
|
||||
def plan(self, plugins):
|
||||
"""根据 Python 版本和依赖兼容性分组"""
|
||||
# 1. 按版本分组
|
||||
# 2. 按依赖兼容性合并
|
||||
# 3. 生成分组元数据
|
||||
return EnvironmentPlanResult(groups=[...])
|
||||
|
||||
class GroupEnvironmentManager:
|
||||
def prepare(self, group):
|
||||
"""准备分组虚拟环境"""
|
||||
# 1. 生成 lock/source/metadata 工件
|
||||
# 2. 必要时重建虚拟环境
|
||||
# 3. 返回 Python 解释器路径
|
||||
return venv_python_path
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 客户端层设计
|
||||
|
||||
### 客户端架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ User Plugin │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ctx.llm.chat() │
|
||||
│ ctx.memory.save() │
|
||||
│ ctx.db.set() │
|
||||
│ ctx.platform.send() │
|
||||
└────────────┬──────────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────▼──────────────────────────────────────────────┐
|
||||
│ CapabilityProxy │
|
||||
│ - call(name, payload) │
|
||||
│ - stream(name, payload) │
|
||||
└────────────┬──────────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────▼──────────────────────────────────────────────┐
|
||||
│ Peer │
|
||||
│ - invoke(capability, payload, stream=False) │
|
||||
│ - invoke_stream(capability, payload) │
|
||||
└────────────┬──────────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────▼──────────────────────────────────────────────┐
|
||||
│ Transport │
|
||||
│ - send(json_string) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### CapabilityProxy
|
||||
|
||||
职责:封装 Peer 的能力调用接口
|
||||
|
||||
```python
|
||||
class CapabilityProxy:
|
||||
def __init__(self, peer):
|
||||
self._peer = peer
|
||||
|
||||
async def call(self, name, payload):
|
||||
"""普通能力调用"""
|
||||
# 1. 检查能力是否可用
|
||||
descriptor = self._peer.remote_capability_map.get(name)
|
||||
if descriptor is None:
|
||||
raise AstrBotError.capability_not_found(name)
|
||||
|
||||
# 2. 调用 Peer.invoke
|
||||
return await self._peer.invoke(name, payload, stream=False)
|
||||
|
||||
async def stream(self, name, payload):
|
||||
"""流式能力调用"""
|
||||
# 1. 检查流式支持
|
||||
descriptor = self._peer.remote_capability_map.get(name)
|
||||
if not descriptor.supports_stream:
|
||||
raise AstrBotError.invalid_input(f"{name} 不支持 stream")
|
||||
|
||||
# 2. 调用 Peer.invoke_stream
|
||||
event_stream = await self._peer.invoke_stream(name, payload)
|
||||
async for event in event_stream:
|
||||
if event.phase == "delta":
|
||||
yield event.data
|
||||
```
|
||||
|
||||
### LLMClient
|
||||
|
||||
```python
|
||||
class LLMClient:
|
||||
def __init__(self, proxy: CapabilityProxy):
|
||||
self._proxy = proxy
|
||||
|
||||
async def chat(self, prompt, *, system=None, history=None, **kwargs) -> str:
|
||||
"""发送聊天请求,返回文本"""
|
||||
output = await self._proxy.call("llm.chat", {
|
||||
"prompt": prompt,
|
||||
"system": system,
|
||||
"history": self._serialize_history(history),
|
||||
**kwargs
|
||||
})
|
||||
return output["text"]
|
||||
|
||||
async def chat_raw(self, prompt, **kwargs) -> LLMResponse:
|
||||
"""发送聊天请求,返回完整响应"""
|
||||
output = await self._proxy.call("llm.chat_raw", {"prompt": prompt, **kwargs})
|
||||
return LLMResponse.model_validate(output)
|
||||
|
||||
async def stream_chat(self, prompt, **kwargs) -> AsyncGenerator[str]:
|
||||
"""流式聊天"""
|
||||
async for delta in self._proxy.stream("llm.stream_chat", {"prompt": prompt, **kwargs}):
|
||||
yield delta["text"]
|
||||
```
|
||||
|
||||
### 其他客户端
|
||||
|
||||
| 客户端 | 主要方法 | 对应 Capability |
|
||||
|--------|---------|-----------------|
|
||||
| `MemoryClient` | `search()`, `save()`, `get()`, `delete()` | `memory.*` |
|
||||
| `DBClient` | `get()`, `set()`, `delete()`, `list()`, `get_many()`, `set_many()`, `watch()` | `db.*` |
|
||||
| `PlatformClient` | `send()`, `send_image()`, `send_chain()`, `get_members()` | `platform.*` |
|
||||
| `HTTPClient` | `register_webhook()` | - |
|
||||
| `MetadataClient` | `get_plugin_info()` | - |
|
||||
|
||||
---
|
||||
|
||||
## 新旧架构对比
|
||||
|
||||
### 协议对比
|
||||
|
||||
| 特性 | 旧版 JSON-RPC | 新版 v4 协议 |
|
||||
|------|---------------|--------------|
|
||||
| 消息格式 | `{"jsonrpc": "2.0", ...}` | `{"type": "invoke", ...}` |
|
||||
| 方法区分 | `method` 字段 | `type` 字段 |
|
||||
| 错误码 | 整数 (`-32000`) | 字符串 (`"internal_error"`) |
|
||||
| 流式支持 | 独立 notification 方法 | 统一 `EventMessage` phase |
|
||||
| 握手 | `handshake` method | `InitializeMessage` type |
|
||||
| 能力声明 | 隐式(method 名称) | 显式 `CapabilityDescriptor` |
|
||||
|
||||
### 运行时对比
|
||||
|
||||
| 特性 | 旧版 | 新版 |
|
||||
|------|------|------|
|
||||
| Peer 抽象 | 分离 `JSONRPCClient/Server` | 统一 `Peer` |
|
||||
| Handler 分发 | 直接调用 `handler(event)` | `HandlerDispatcher` 参数注入 |
|
||||
| 能力路由 | 无显式路由 | `CapabilityRouter` |
|
||||
| 环境管理 | 无 | `PluginEnvironmentManager` 分组 |
|
||||
| 传输层 | 每个实现处理 JSON-RPC | 传输层只处理字符串 |
|
||||
|
||||
### 代码对比
|
||||
|
||||
#### 旧版 Handler
|
||||
|
||||
```python
|
||||
from astrbot.api.star import Star
|
||||
from astrbot.api.event import AstrMessageEvent
|
||||
|
||||
class MyPlugin(Star):
|
||||
@command_handler("hello", aliases=["hi"])
|
||||
def hello_handler(self, event: AstrMessageEvent):
|
||||
reply = self.call_context_function("llm_generate", prompt=event.message_plain)
|
||||
event.reply(reply)
|
||||
```
|
||||
|
||||
#### 新版 Handler
|
||||
|
||||
```python
|
||||
from astrbot_sdk import Star, Context, MessageEvent
|
||||
from astrbot_sdk.decorators import on_command
|
||||
|
||||
class MyPlugin(Star):
|
||||
@on_command("hello", aliases=["hi"])
|
||||
async def hello(self, event: MessageEvent, ctx: Context) -> None:
|
||||
reply = await ctx.llm.chat(event.text)
|
||||
await event.reply(reply)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 兼容层设计
|
||||
|
||||
### 兼容架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Legacy Plugin │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ import astrbot.api.star as star │
|
||||
│ import astrbot.api.event as event │
|
||||
│ from astrbot.api.star import Star, Context │
|
||||
└────────────┬───────────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────▼──────────────────────────────────────────────┐
|
||||
│ astrbot_sdk.compat │
|
||||
│ - 顶层兼容入口,统一旧导入路径 │
|
||||
└────────────┬───────────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────▼──────────────────────────────────────────────┐
|
||||
│ astrbot_sdk.api.* (Facade) │
|
||||
│ - 提供 astrbot.api.* 导入路径 │
|
||||
│ - 调用 _legacy_api.py 中的实现 │
|
||||
└────────────┬───────────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────▼──────────────────────────────────────────────┐
|
||||
│ astrbot_sdk._legacy_api.py │
|
||||
│ - LegacyContext, LegacyStar 等 │
|
||||
│ - 兼容的 API 实现 │
|
||||
└────────────┬───────────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────▼──────────────────────────────────────────────┐
|
||||
│ astrbot_sdk._legacy_runtime.py │
|
||||
│ - LegacyRuntimeAdapter, LegacyWorkerRuntimeBridge │
|
||||
│ - 处理 legacy hook 和 result │
|
||||
└────────────┬───────────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────▼──────────────────────────────────────────────┐
|
||||
│ runtime.HandlerDispatcher │
|
||||
│ - 调用 legacy_runtime.dispatch_result() │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 兼容模块职责
|
||||
|
||||
| 模块 | 职责 |
|
||||
|------|------|
|
||||
| `compat.py` | 顶层兼容入口,统一旧导入路径 |
|
||||
| `api/*` | `astrbot.api.*` 导入路径 facade |
|
||||
| `_legacy_api.py` | LegacyContext, LegacyStar, 兼容 API 实现 |
|
||||
| `_legacy_runtime.py` | LegacyRuntimeAdapter, 处理 legacy hook/result |
|
||||
| `_legacy_loader.py` | Legacy 插件发现与 main.py 包装 |
|
||||
| `_legacy_llm.py` | Legacy LLM/tool 兼容 |
|
||||
| `_session_waiter.py` | session_waiter 兼容执行 |
|
||||
| `_shared_preferences.py` | 共享偏好兼容 |
|
||||
|
||||
### LegacyContext
|
||||
|
||||
```python
|
||||
class LegacyContext:
|
||||
"""旧版 Context 的兼容实现"""
|
||||
|
||||
def __init__(self, *, peer, plugin_id, logger, ...):
|
||||
self.peer = peer
|
||||
self.plugin_id = plugin_id
|
||||
|
||||
# 旧版方法
|
||||
def call_context_function(self, function_name, **kwargs):
|
||||
"""调用上下文函数(兼容旧版)"""
|
||||
# 通过 capability 调用实现
|
||||
|
||||
def send_message(self, target, message_chain):
|
||||
"""发送消息(兼容旧版)"""
|
||||
|
||||
# ... 其他旧版方法
|
||||
```
|
||||
|
||||
### 旧包名兼容
|
||||
|
||||
```python
|
||||
# src-new/astrbot/__init__.py
|
||||
# 提供 astrbot.api.* 和 astrbot.core.* 导入路径
|
||||
|
||||
# 用户代码
|
||||
from astrbot.api.star import Star
|
||||
from astrbot.api.event import AstrMessageEvent
|
||||
from astrbot.api.event.filter import filter
|
||||
|
||||
# 实际实现
|
||||
from astrbot_sdk.api.star import Star as _Star
|
||||
from astrbot_sdk.api.event import AstrMessageEvent as _AstrMessageEvent
|
||||
# ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 插件开发指南
|
||||
|
||||
### v4 原生插件
|
||||
|
||||
#### plugin.yaml
|
||||
|
||||
```yaml
|
||||
_schema_version: 2
|
||||
name: my_plugin
|
||||
author: your_name
|
||||
version: 1.0.0
|
||||
runtime:
|
||||
python: "3.12"
|
||||
components:
|
||||
- class: main:MyPlugin
|
||||
```
|
||||
|
||||
#### main.py
|
||||
|
||||
```python
|
||||
from astrbot_sdk import Star, Context, MessageEvent
|
||||
from astrbot_sdk.decorators import on_command, on_message, provide_capability
|
||||
|
||||
class MyPlugin(Star):
|
||||
# 命令处理器
|
||||
@on_command("hello", aliases=["hi"])
|
||||
async def hello(self, event: MessageEvent, ctx: Context) -> None:
|
||||
await event.reply(f"你好,{event.user_id}!")
|
||||
|
||||
# 消息处理器
|
||||
@on_message(keywords=["帮助"])
|
||||
async def help(self, event: MessageEvent, ctx: Context) -> None:
|
||||
await event.reply("可用命令:hello, help")
|
||||
|
||||
# 提供能力
|
||||
@provide_capability(
|
||||
"my_plugin.calculate",
|
||||
description="执行计算",
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {"x": {"type": "number"}},
|
||||
"required": ["x"]
|
||||
},
|
||||
output_schema={
|
||||
"type": "object",
|
||||
"properties": {"result": {"type": "number"}},
|
||||
"required": ["result"]
|
||||
}
|
||||
)
|
||||
async def calculate_capability(
|
||||
self,
|
||||
payload: dict,
|
||||
ctx: Context
|
||||
) -> dict:
|
||||
x = payload.get("x", 0)
|
||||
return {"result": x * 2}
|
||||
```
|
||||
|
||||
### 旧版兼容插件
|
||||
|
||||
#### plugin.yaml
|
||||
|
||||
```yaml
|
||||
name: my_old_plugin
|
||||
version: 1.0.0
|
||||
components:
|
||||
- class: main:MyOldPlugin
|
||||
```
|
||||
|
||||
#### main.py
|
||||
|
||||
```python
|
||||
from astrbot.api.star import Star
|
||||
from astrbot.api.event import AstrMessageEvent
|
||||
|
||||
class MyOldPlugin(Star):
|
||||
# 旧版装饰器仍然支持
|
||||
@command_handler("old_hello")
|
||||
def old_hello_handler(self, event: AstrMessageEvent):
|
||||
# 旧版 API 调用
|
||||
reply = self.call_context_function("llm_generate", prompt="你好")
|
||||
event.reply(reply)
|
||||
|
||||
# 生命周期钩子
|
||||
async def on_start(self):
|
||||
self.put_kv_data("started", True)
|
||||
|
||||
async def on_stop(self):
|
||||
self.put_kv_data("started", False)
|
||||
```
|
||||
|
||||
### 生命周期钩子
|
||||
|
||||
| 钩子 | 说明 |
|
||||
|------|------|
|
||||
| `on_start()` | 插件启动时调用 |
|
||||
| `on_stop()` | 插件停止时调用 |
|
||||
| `on_error(exc, event, ctx)` | Handler 执行出错时调用 |
|
||||
|
||||
---
|
||||
|
||||
## 关键设计模式
|
||||
|
||||
### 1. 协议优先模式
|
||||
|
||||
- 所有跨进程通信都通过 v4 协议
|
||||
- 传输层只处理字符串,协议由 Peer 层处理
|
||||
- 支持多种传输方式(Stdio, WebSocket)
|
||||
|
||||
### 2. 能力路由模式
|
||||
|
||||
- 显式声明 Capability 和输入/输出 Schema
|
||||
- 通过 CapabilityRouter 统一路由
|
||||
- 支持同步和流式两种调用模式
|
||||
|
||||
### 3. 环境分组模式
|
||||
|
||||
- 多插件可共享同一 Python 虚拟环境
|
||||
- 按版本和依赖兼容性自动分组
|
||||
- 节省资源,加快启动速度
|
||||
|
||||
### 4. 参数注入模式
|
||||
|
||||
- HandlerDispatcher 支持类型注解注入
|
||||
- 优先级:类型注解 > 参数名 > legacy_args
|
||||
- 支持可选类型 `Optional[Type]`
|
||||
|
||||
### 5. 取消传播模式
|
||||
|
||||
- CancelToken 统一取消机制
|
||||
- 跨进程取消通过 CancelMessage
|
||||
- 早到取消避免竞态条件
|
||||
|
||||
### 6. 兼容桥接模式
|
||||
|
||||
- 多层兼容桥接:`compat.py` → `api/*` → `_legacy_api.py` → `_legacy_runtime.py`
|
||||
- 每层只负责一个兼容维度的转换
|
||||
- 新旧代码可以共存
|
||||
|
||||
### 7. 插件隔离模式
|
||||
|
||||
- 每个插件运行在独立 Worker 进程
|
||||
- 崩溃不影响其他插件
|
||||
- 支持 GroupWorkerRuntime 共享环境
|
||||
|
||||
---
|
||||
|
||||
## 附录:关键文件速查
|
||||
|
||||
| 文件 | 核心类/函数 | 说明 |
|
||||
|------|------------|------|
|
||||
| `src-new/astrbot_sdk/__init__.py` | `Star`, `Context`, `MessageEvent` | 顶层入口 |
|
||||
| `src-new/astrbot_sdk/star.py` | `Star` | v4 原生插件基类 |
|
||||
| `src-new/astrbot_sdk/context.py` | `Context` | 运行时上下文 |
|
||||
| `src-new/astrbot_sdk/decorators.py` | `on_command`, `on_message` | v4 装饰器 |
|
||||
| `src-new/astrbot_sdk/runtime/peer.py` | `Peer` | 协议对等端 |
|
||||
| `src-new/astrbot_sdk/runtime/bootstrap.py` | `SupervisorRuntime` | 启动引导 |
|
||||
| `src-new/astrbot_sdk/runtime/loader.py` | `load_plugin()` | 插件加载 |
|
||||
| `src-new/astrbot_sdk/runtime/handler_dispatcher.py` | `HandlerDispatcher` | Handler 执行分发 |
|
||||
| `src-new/astrbot_sdk/runtime/capability_router.py` | `CapabilityRouter` | Capability 路由 |
|
||||
| `src-new/astrbot_sdk/protocol/messages.py` | `InitializeMessage`, `InvokeMessage` | 协议消息 |
|
||||
| `src-new/astrbot_sdk/protocol/descriptors.py` | `HandlerDescriptor`, `CapabilityDescriptor` | 描述符 |
|
||||
| `src-new/astrbot_sdk/clients/_proxy.py` | `CapabilityProxy` | 能力代理 |
|
||||
| `src-new/astrbot_sdk/_legacy_runtime.py` | `LegacyRuntimeAdapter` | Legacy 执行适配 |
|
||||
|
||||
---
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 2026-03-14
|
||||
- 添加环境分组详细说明
|
||||
- 完善 CapabilityRouter 内置能力列表
|
||||
- 添加客户端层架构图
|
||||
- 补充新旧代码对比示例
|
||||
|
||||
### 2026-03-13
|
||||
- 初始版本
|
||||
- 完成整体架构分析
|
||||
- 新旧对比整理
|
||||
|
||||
---
|
||||
|
||||
> 本文档基于 AstrBot SDK 当前版本 (`refact1/refactsome`) 整理
|
||||
> 如有疑问请查阅源代码或提交 Issue
|
||||
@@ -1,9 +1,18 @@
|
||||
"""过渡期 ``astrbot_sdk.api`` 兼容 facade。
|
||||
"""过渡期兼容实现层 ``astrbot_sdk.api``。
|
||||
|
||||
这个包仅用于承接旧插件的历史导入路径,方便外部插件逐步迁移到 v4 顶层 API。
|
||||
它不是新的推荐入口,也不应继续承载新的运行时实现逻辑。
|
||||
这个包承载旧插件所需的兼容模型与逻辑实现,供 legacy 执行路径直接使用。
|
||||
它不是简单的重导出层,而是维护旧语义所必需的实现模块:
|
||||
|
||||
迁移目标:
|
||||
- ``astrbot_sdk.api.event``:``AstrMessageEvent``、``filter``、``MessageEventResult``
|
||||
- ``astrbot_sdk.api.message``:``MessageChain``、消息组件模型
|
||||
- ``astrbot_sdk.api.provider``:``LLMResponse`` 兼容实体
|
||||
- ``astrbot_sdk.api.basic``、``platform``、``components``:配置、平台、组件兼容面
|
||||
|
||||
与 ``src-new/astrbot/`` 对比:
|
||||
- 此包(``astrbot_sdk.api``)是真正的兼容实现,包含运行时逻辑
|
||||
- ``src-new/astrbot/`` 是薄 facade,只做导入路径转发
|
||||
|
||||
迁移目标(仅在有明确迁移计划时移除):
|
||||
|
||||
- ``astrbot_sdk.context.Context``
|
||||
- ``astrbot_sdk.events.MessageEvent``
|
||||
@@ -12,10 +21,9 @@
|
||||
|
||||
维护约束:
|
||||
|
||||
- ``astrbot_sdk.api.*`` 保持为受控兼容面,后续会随迁移推进逐步移除
|
||||
- 包内模块优先作为 facade / 重导出层存在,但允许少量必须保留行为的 compat 模块
|
||||
- compat 真实行为应尽量收口到顶层 private compat 模块
|
||||
- 新增运行时逻辑应优先放在 v4 主路径,由 compat 层按需转发或包装
|
||||
- 新增运行时逻辑优先放在 v4 主路径,由此层按需包装
|
||||
- compat 真实执行边界收口在顶层私有模块(``_legacy_runtime.py`` 等)
|
||||
- 新插件应直接使用 ``astrbot_sdk`` 顶层 v4 API
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -137,7 +137,18 @@ class AstrMessageEvent(MessageEvent):
|
||||
session_id=self.session_id,
|
||||
)
|
||||
self.unified_msg_origin = str(self.session)
|
||||
self.platform = self.platform_meta or self.platform
|
||||
|
||||
def to_payload(self) -> dict[str, Any]:
|
||||
"""Override to guarantee ``platform`` in the wire payload is always a string id.
|
||||
|
||||
``MessageEvent.to_payload()`` serialises ``self.platform`` verbatim.
|
||||
Since ``AstrMessageEvent`` may receive a ``PlatformMetadata`` object
|
||||
via ``platform_meta``, we normalise it through ``get_platform_id()``
|
||||
so the wire format stays a clean ``str | None``.
|
||||
"""
|
||||
payload = super().to_payload()
|
||||
payload["platform"] = self.get_platform_id() or None
|
||||
return payload
|
||||
|
||||
@classmethod
|
||||
def from_payload(
|
||||
|
||||
Reference in New Issue
Block a user