From 095ac35221b8c8adeb90cae31b48bc0607de9713 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Mon, 23 Mar 2026 01:51:48 +0800 Subject: [PATCH] docs: remove SDK integration plan document The SDK integration was reverted, remove the now-stale planning document. --- docs/SDK_INTEGRATION_PLAN.md | 638 ----------------------------------- 1 file changed, 638 deletions(-) delete mode 100644 docs/SDK_INTEGRATION_PLAN.md diff --git a/docs/SDK_INTEGRATION_PLAN.md b/docs/SDK_INTEGRATION_PLAN.md deleted file mode 100644 index 87114a6d5..000000000 --- a/docs/SDK_INTEGRATION_PLAN.md +++ /dev/null @@ -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