docs: remove SDK integration plan document

The SDK integration was reverted, remove the now-stale planning document.
This commit is contained in:
LIghtJUNction
2026-03-23 01:51:48 +08:00
parent 6a4177cae4
commit 095ac35221

View File

@@ -1,638 +0,0 @@
# AstrBot SDK 接入实施计划
> 本文档基于用户 `whatevertogo` 的分析文档,补充具体实施细节。
## 一、架构验证结果
经代码审查确认:
| 组件 | 路径 | 状态 |
|------|------|------|
| Core Context | `astrbot/core/star/context.py` | ✅ 包含所有核心能力方法 |
| CapabilityRouter | `src-new/astrbot_sdk/runtime/capability_router.py` | ✅ 已实现内置能力注册 |
| StarHandlerMetadata | `astrbot/core/star/star_handler.py` | ✅ 定义清晰,可扩展 |
| HandlerDescriptor | `src-new/astrbot_sdk/protocol/descriptors.py` | ✅ Pydantic 模型,类型安全 |
| SupervisorRuntime | `src-new/astrbot_sdk/runtime/bootstrap.py` | ✅ 已实现完整生命周期 |
| CoreLifecycle | `astrbot/core/core_lifecycle.py` | ✅ 接入点明确 |
---
## 二、实施阶段
### Phase 1: 基础桥接层 (预计 2-3 天)
#### 1.1 创建目录结构
```
astrbot/core/sdk_bridge/
├── __init__.py
├── capability_bridge.py # CoreCapabilityBridge
├── supervisor_bridge.py # SdkPluginBridge
├── event_bridge.py # 事件转换
├── handler_bridge.py # Handler 注册桥接
└── transport_adapter.py # Transport 适配
```
#### 1.2 实现 `capability_bridge.py`
```python
"""CoreCapabilityBridge: 把 astrbot.core.Context 能力映射到 CapabilityRouter。"""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from astrbot.core.star.context import Context as CoreContext
from astrbot_sdk.runtime.capability_router import CapabilityRouter
logger = logging.getLogger("astrbot.sdk_bridge")
class CoreCapabilityBridge:
"""把 astrbot.core.Context 的真实实现注入进 SDK CapabilityRouter。
SupervisorRuntime 初始化后Core 侧的 Peer 调用此 Bridge 来响应插件发来的能力请求。
"""
def __init__(self, core_context: CoreContext, router: CapabilityRouter) -> None:
self.ctx = core_context
self.router = router
self._wire()
def _wire(self) -> None:
"""注册所有能力处理器到 router。"""
# 替换 router 的内置 echo 实现为真实实现
self._wire_llm()
self._wire_db()
self._wire_platform()
self._wire_memory()
self._wire_metadata()
logger.info("SDK CapabilityBridge 已连接")
def _wire_llm(self) -> None:
"""连接 LLM 能力。"""
async def llm_chat(request_id: str, payload: dict[str, Any], _token) -> dict[str, Any]:
# 从 payload 提取参数
prompt = payload.get("prompt", "")
provider_id = payload.get("provider_id")
# 调用 core 的 llm_generate
if provider_id:
resp = await self.ctx.llm_generate(
chat_provider_id=provider_id,
prompt=prompt,
# ... 其他参数
)
else:
# 使用默认 provider
prov = self.ctx.get_using_provider()
if prov:
resp = await self.ctx.llm_generate(
chat_provider_id=prov.meta().id,
prompt=prompt,
)
else:
return {"text": f"Echo (no provider): {prompt}"}
return {"text": resp.completion_text or ""}
# 注册到 router (覆盖默认 echo 实现)
self.router.unregister("llm.chat")
self.router.register(
self.router._registrations["llm.chat"].descriptor, # 复用 descriptor
call_handler=llm_chat,
)
def _wire_db(self) -> None:
"""连接数据库能力。"""
async def db_get(request_id: str, payload: dict[str, Any], _token) -> dict[str, Any]:
key = str(payload.get("key", ""))
# 使用 core 的数据库
db = self.ctx.get_db()
value = await db.get(key)
return {"value": value}
async def db_set(request_id: str, payload: dict[str, Any], _token) -> dict[str, Any]:
key = str(payload.get("key", ""))
value = payload.get("value")
db = self.ctx.get_db()
await db.set(key, value)
return {}
# 覆盖注册
self.router.unregister("db.get")
self.router.unregister("db.set")
# ... 类似注册其他 db 方法
def _wire_platform(self) -> None:
"""连接平台消息发送能力。"""
async def platform_send(request_id: str, payload: dict[str, Any], _token) -> dict[str, Any]:
from astrbot.core.platform.astr_message_event import MessageSesion
from astrbot.core.message.message_event_result import MessageChain
session_str = payload.get("session", "")
text = payload.get("text", "")
# 构建 MessageChain
chain = MessageChain().message(text)
# 调用 core 的 send_message
success = await self.ctx.send_message(session_str, chain)
return {"message_id": f"msg_{request_id}", "success": success}
self.router.unregister("platform.send")
self.router.register(
self.router._registrations["platform.send"].descriptor,
call_handler=platform_send,
)
def _wire_memory(self) -> None:
"""连接记忆/会话管理能力。"""
async def memory_save(request_id: str, payload: dict[str, Any], _token) -> dict[str, Any]:
# 使用 conversation_manager
key = str(payload.get("key", ""))
value = payload.get("value")
# 实现具体逻辑
return {}
# 注册...
def _wire_metadata(self) -> None:
"""连接插件元数据能力。"""
# metadata.get_config -> context.get_config()
# metadata.get_star -> star_registry 查询
pass
```
#### 1.3 实现 `event_bridge.py`
```python
"""事件双向转换模块。"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from astrbot.core.platform.astr_message_event import AstrMessageEvent
def astr_event_to_sdk_payload(event: AstrMessageEvent) -> dict[str, Any]:
"""把旧版 AstrMessageEvent 转成 SDK MessageEvent 的 to_payload() 格式。"""
return {
"text": event.message_str,
"user_id": event.get_sender_id(),
"group_id": event.get_group_id() if event.is_group_message() else None,
"platform": event.get_platform_name(),
"session_id": event.unified_msg_origin,
"target": {
"conversation_id": event.unified_msg_origin,
"platform": event.get_platform_name(),
},
# 保留原始消息对象引用
"_raw_message_obj": event.message_obj,
"_raw_platform_meta": event.platform_meta,
# 保留原始 event 引用,供后续处理
"_core_event": event,
}
def sdk_payload_to_event_context(payload: dict[str, Any]) -> dict[str, Any]:
"""从 SDK payload 提取事件上下文信息。"""
return {
"session": payload.get("session_id", ""),
"user_id": payload.get("user_id"),
"group_id": payload.get("group_id"),
"platform": payload.get("platform"),
"text": payload.get("text"),
}
```
#### 1.4 实现 `handler_bridge.py`
```python
"""Handler 注册桥接:把 SDK HandlerDescriptor 转换为 StarHandlerMetadata。"""
from __future__ import annotations
import logging
from collections.abc import Awaitable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Callable
from astrbot.core.star.filter import HandlerFilter
from astrbot.core.star.star_handler import EventType, StarHandlerMetadata
if TYPE_CHECKING:
from astrbot_sdk.protocol.descriptors import HandlerDescriptor
from astrbot_sdk.runtime.bootstrap import WorkerSession
logger = logging.getLogger("astrbot.sdk_bridge")
# SDK 事件类型到 Core 事件类型的映射
TRIGGER_TO_EVENT_TYPE = {
"message": EventType.AdapterMessageEvent,
"command": EventType.AdapterMessageEvent,
"event": EventType.AdapterMessageEvent, # 需要根据具体 event_type 细分
"schedule": EventType.OnAstrBotLoadedEvent, # 定时任务特殊处理
}
@dataclass
class SdkHandlerWrapper:
"""包装 SDK handler 的调用闭包。"""
session: WorkerSession
handler_id: str
async def __call__(self, *args, **kwargs) -> Any:
"""调用 SDK handler。"""
# 从 args/kwargs 提取事件信息
event = kwargs.get("event") or (args[0] if args else None)
if event is None:
raise ValueError("SDK handler 需要 event 参数")
# 转换事件格式
from .event_bridge import astr_event_to_sdk_payload
payload = astr_event_to_sdk_payload(event)
# 通过 session 调用远程 handler
return await self.session.invoke_handler(self.handler_id, payload)
def handler_descriptor_to_metadata(
descriptor: HandlerDescriptor,
session: WorkerSession,
) -> StarHandlerMetadata:
"""把 SDK HandlerDescriptor 转换为 Core 的 StarHandlerMetadata。
Args:
descriptor: SDK handler 描述符
session: Worker 会话,用于调用远程 handler
Returns:
StarHandlerMetadata: Core 兼容的 handler 元数据
"""
# 创建包装器
wrapper = SdkHandlerWrapper(session=session, handler_id=descriptor.id)
# 确定 Core 事件类型
trigger = descriptor.trigger
event_type = TRIGGER_TO_EVENT_TYPE.get(trigger.type, EventType.AdapterMessageEvent)
# 构建 event_filters
event_filters = _build_event_filters(trigger)
# 构建 extras_configs
extras_configs = {
"priority": descriptor.priority,
"require_admin": descriptor.permissions.require_admin,
"level": descriptor.permissions.level,
"sdk_handler": True, # 标记为 SDK handler
}
# 解析 handler_full_name
parts = descriptor.id.rsplit(".", 1)
if len(parts) == 2:
module_path, handler_name = parts
else:
module_path = descriptor.id
handler_name = descriptor.id
return StarHandlerMetadata(
event_type=event_type,
handler_full_name=descriptor.id,
handler_name=handler_name,
handler_module_path=module_path,
handler=wrapper, # 使用包装器
event_filters=event_filters,
desc=getattr(trigger, "description", "") or "",
extras_configs=extras_configs,
)
def _build_event_filters(trigger: Any) -> list[HandlerFilter]:
"""根据 trigger 类型构建事件过滤器。"""
from astrbot.core.star.filter.command import CommandFilter
from astrbot.core.star.filter.regex import RegexFilter
filters: list[HandlerFilter] = []
if trigger.type == "command":
# 命令触发器 -> CommandFilter
filters.append(CommandFilter(
command_name=trigger.command,
# handler_md 需要稍后设置
))
elif trigger.type == "message":
# 消息触发器 -> RegexFilter
if trigger.regex:
filters.append(RegexFilter(regex=trigger.regex))
# keywords 过滤需要在运行时检查
return filters
```
#### 1.5 实现 `supervisor_bridge.py`
```python
"""SdkPluginBridge: 在 Core 侧管理 SupervisorRuntime。"""
from __future__ import annotations
import asyncio
import logging
from pathlib import Path
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from astrbot.core.star.context import Context as CoreContext
logger = logging.getLogger("astrbot.sdk_bridge")
class SdkPluginBridge:
"""在 Core 侧管理 SupervisorRuntime并把 SDK handler 注册进 pipeline。"""
def __init__(
self,
core_context: CoreContext,
sdk_plugins_dir: Path,
) -> None:
from astrbot_sdk.runtime.capability_router import CapabilityRouter
from astrbot_sdk.runtime.bootstrap import SupervisorRuntime
from astrbot_sdk.transport.stdio import StdioTransport
self.ctx = core_context
self.plugins_dir = sdk_plugins_dir
# 创建 CapabilityRouter 和 Bridge
self.capability_router = CapabilityRouter()
self.capability_bridge = None # 延迟初始化
# 创建 Transport (使用 stdio 进行进程间通信)
self.transport = StdioTransport()
# 创建 Supervisor
self.supervisor = SupervisorRuntime(
transport=self.transport,
plugins_dir=sdk_plugins_dir,
)
# 已注册的 handler 映射
self._registered_handlers: dict[str, str] = {} # handler_id -> plugin_name
async def start(self) -> None:
"""启动 SDK 插件桥接。"""
# 初始化 CapabilityBridge (替换默认 echo 实现)
from .capability_bridge import CoreCapabilityBridge
self.capability_bridge = CoreCapabilityBridge(self.ctx, self.capability_router)
# 启动 Supervisor
await self.supervisor.start()
# 注册所有 handler 到 pipeline
self._register_handlers_into_pipeline()
logger.info(f"SDK 插件桥接已启动,共加载 {len(self._registered_handlers)} 个 handler")
async def stop(self) -> None:
"""停止 SDK 插件桥接。"""
await self.supervisor.stop()
self._registered_handlers.clear()
logger.info("SDK 插件桥接已停止")
def _register_handlers_into_pipeline(self) -> None:
"""把 supervisor 聚合的所有 HandlerDescriptor 注册进 star_handlers_registry。"""
from astrbot.core.star.star_handler import star_handlers_registry
from .handler_bridge import handler_descriptor_to_metadata
for handler_id, session in self.supervisor.handler_to_worker.items():
# 获取 handler 的 descriptor
descriptor = self._get_handler_descriptor(handler_id)
if descriptor is None:
logger.warning(f"无法获取 handler descriptor: {handler_id}")
continue
# 转换为 StarHandlerMetadata
metadata = handler_descriptor_to_metadata(descriptor, session)
# 注册到全局 registry
star_handlers_registry.append(metadata)
self._registered_handlers[handler_id] = descriptor.id.split(".")[0]
logger.debug(f"注册 SDK handler: {handler_id}")
def _get_handler_descriptor(self, handler_id: str):
"""从 supervisor 获取 handler descriptor。"""
# 遍历所有 session 的 loaded_plugin 找到对应的 handler
for plugin_id, session in self.supervisor._plugin_sessions.items():
if session.loaded_plugin:
for handler in session.loaded_plugin.handlers:
if handler.id == handler_id:
return handler
return None
```
---
### Phase 2: 生命周期集成 (预计 1 天)
#### 2.1 修改 `core_lifecycle.py`
```python
# 在 CoreLifecycle 类中添加
def __init__(self, ...):
# ... 现有代码 ...
self.sdk_bridge: SdkPluginBridge | None = None
async def initialize(self) -> None:
# ... 现有初始化代码 ...
# 在 PluginManager 初始化后,初始化 SDK Bridge
if self.astrbot_config.get("enable_sdk_plugins", False):
from pathlib import Path
from astrbot.core.sdk_bridge.supervisor_bridge import SdkPluginBridge
sdk_plugins_dir = Path("data/sdk_plugins")
sdk_plugins_dir.mkdir(parents=True, exist_ok=True)
self.sdk_bridge = SdkPluginBridge(
core_context=self.star_context,
sdk_plugins_dir=sdk_plugins_dir,
)
async def start(self) -> None:
self._load()
# 启动 SDK Bridge
if self.sdk_bridge:
await self.sdk_bridge.start()
logger.info("SDK 插件桥接启动完成")
# ... 现有代码 ...
async def stop(self) -> None:
# 停止 SDK Bridge
if self.sdk_bridge:
await self.sdk_bridge.stop()
# ... 现有代码 ...
```
---
### Phase 3: 测试与验证 (预计 2 天)
#### 3.1 单元测试
```python
# tests/test_sdk_bridge.py
import pytest
from astrbot.core.sdk_bridge.capability_bridge import CoreCapabilityBridge
from astrbot.core.sdk_bridge.event_bridge import astr_event_to_sdk_payload
from astrbot.core.sdk_bridge.handler_bridge import handler_descriptor_to_metadata
class TestEventBridge:
def test_astr_event_to_sdk_payload(self, mock_event):
payload = astr_event_to_sdk_payload(mock_event)
assert payload["text"] == mock_event.message_str
assert payload["user_id"] == mock_event.get_sender_id()
assert "_core_event" in payload
class TestCapabilityBridge:
async def test_llm_chat_capability(self, mock_core_context, mock_router):
bridge = CoreCapabilityBridge(mock_core_context, mock_router)
# 验证 llm.chat 已被覆盖
assert "llm.chat" in mock_router._registrations
class TestHandlerBridge:
def test_command_handler_conversion(self, mock_descriptor, mock_session):
metadata = handler_descriptor_to_metadata(mock_descriptor, mock_session)
assert metadata.handler_full_name == mock_descriptor.id
assert len(metadata.event_filters) > 0
```
#### 3.2 集成测试
```python
# tests/integration/test_sdk_integration.py
import pytest
from pathlib import Path
@pytest.fixture
async def sdk_bridge(test_context, tmp_path):
from astrbot.core.sdk_bridge.supervisor_bridge import SdkPluginBridge
bridge = SdkPluginBridge(
core_context=test_context,
sdk_plugins_dir=tmp_path / "sdk_plugins",
)
await bridge.start()
yield bridge
await bridge.stop()
async def test_sdk_plugin_loading(sdk_bridge):
"""测试 SDK 插件能被正确加载。"""
assert len(sdk_bridge._registered_handlers) > 0
async def test_sdk_handler_invocation(sdk_bridge, mock_event):
"""测试 SDK handler 能被正确调用。"""
from astrbot.core.star.star_handler import star_handlers_registry
# 查找 SDK 注册的 handler
handlers = [h for h in star_handlers_registry if h.extras_configs.get("sdk_handler")]
assert len(handlers) > 0
# 调用 handler
handler = handlers[0]
result = await handler.handler(event=mock_event)
# 验证结果
```
---
### Phase 4: 配置与文档 (预计 1 天)
#### 4.1 配置项
```yaml
# config/astrbot_config.yaml
sdk_plugins:
enabled: true
plugins_dir: "data/sdk_plugins"
worker_timeout: 30 # Worker 启动超时(秒)
max_restarts: 3 # Worker 崩溃后最大重启次数
```
#### 4.2 用户文档
创建 `docs/sdk_plugin_development.md`,说明:
- SDK 插件目录结构
- `plugin.yaml` 配置格式
- Handler 注册方式
- Capability 调用示例
---
## 三、能力映射详细表
| SDK Capability | Core Context 方法 | 实现状态 |
|----------------|------------------|----------|
| `llm.chat` | `context.llm_generate()` | 待实现 |
| `llm.chat_raw` | `context.llm_generate()` | 待实现 |
| `llm.stream_chat` | `context.llm_generate()` + stream | 待实现 |
| `db.get` | `context.get_db().get()` | 待实现 |
| `db.set` | `context.get_db().set()` | 待实现 |
| `db.delete` | `context.get_db().delete()` | 待实现 |
| `db.list` | `context.get_db().list()` | 待实现 |
| `platform.send` | `context.send_message()` | 待实现 |
| `platform.send_image` | `context.send_message()` + image | 待实现 |
| `platform.send_chain` | `context.send_message()` | 待实现 |
| `memory.search` | `context.conversation_manager.*` | 待实现 |
| `memory.save` | `context.conversation_manager.*` | 待实现 |
| `metadata.get_config` | `context.get_config()` | 待实现 |
| `metadata.get_star` | `star_registry` 查询 | 待实现 |
---
## 四、风险与缓解措施
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| Worker 进程崩溃 | 插件不可用 | 实现自动重启机制 |
| 事件格式不兼容 | 功能异常 | 完善事件转换层 |
| 性能下降 | 用户体验变差 | 优化序列化,考虑共享内存 |
| 旧插件迁移成本 | 用户流失 | 保持 legacy 兼容层 |
---
## 五、后续优化
1. **Worker 崩溃重启**:在 `SupervisorRuntime` 中添加指数退避重启机制
2. **性能优化**:考虑使用共享内存传输大消息
3. **调试支持**:添加 SDK 插件调试模式
4. **热重载**:支持 SDK 插件热更新
---
## 六、实施检查清单
- [ ] 创建 `astrbot/core/sdk_bridge/` 目录
- [ ] 实现 `capability_bridge.py`
- [ ] 实现 `event_bridge.py`
- [ ] 实现 `handler_bridge.py`
- [ ] 实现 `supervisor_bridge.py`
- [ ] 修改 `core_lifecycle.py` 集成
- [ ] 添加配置项
- [ ] 编写单元测试
- [ ] 编写集成测试
- [ ] 编写用户文档
- [ ] 处理 `legacy_adapter.py` 死代码
- [ ]`CommandComponent` 添加 DeprecationWarning