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:
whatevertogo
2026-03-14 17:17:25 +08:00
parent 1672bc3227
commit 89d3e2f014
3 changed files with 1009 additions and 9 deletions

981
PROJECT_ARCHITECTURE.md Normal file
View 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

View File

@@ -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

View File

@@ -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(