mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-16 01:40:15 +08:00
`abort_signal` (asyncio.Event) is passed via **kwargs into payloads during tool_call streaming, causing "Object of type Event is not JSON serializable" when the OpenAI client tries to serialize the request body. Regression test added: test_prepare_chat_payload_strips_non_json_serializable_kwargs
4.0 KiB
4.0 KiB
Context
ABP 协议第一阶段完成了核心握手、传输层和事件系统。当前 OutOfProcessPluginLoader 已实现,但:
PluginLoadertrait 未定义,InProcess 模式缺失tools/list端点未实现,工具发现依赖它- Python FFI 绑定未完成,Python 层无法调用 Rust 核心
- 配置集成未落地,无 PluginRegistry
Goals / Non-Goals
Goals:
- 实现
PluginLoadertrait,统一进程内/外加载逻辑 - 实现
InProcessPluginLoader,Python 模块直接加载 - 实现
tools/list端点和工具 Schema 验证 - 完成 Python FFI 绑定链路
- 落地 config.yaml 插件配置和 PluginRegistry
Non-Goals:
- 不实现 Stars → ABP 插件迁移(下一阶段)
- 不实现 WebUI 插件配置界面(config.yaml 落地即可)
- 不改变 Rust 核心源码(仅调用已有接口)
Decisions
1. PluginLoader Trait 设计
决定:定义抽象 trait,包含 load()、unload()、reload()、get_plugin_info() 方法。
class PluginLoader(ABC):
@abstractmethod
async def load(self, config: PluginConfig) -> PluginInstance: ...
@abstractmethod
async def unload(self, plugin_id: str) -> None: ...
@abstractmethod
async def reload(self, plugin_id: str) -> PluginInstance: ...
替代方案:
- 工厂模式(被否定):增加抽象层级,当前场景不需要
- 统一接口参数(被否定):进程内/外行为差异大,分离更清晰
2. InProcessPluginLoader 实现
⚠️ 架构约束:核心加载逻辑在 Rust FFI,Python 仅做胶水层
决定:PluginLoader trait 定义在 Python,load/unload/reload 实现调用 Rust FFI(load_plugin()/unload_plugin())。
class InProcessPluginLoader(PluginLoader):
async def load(self, plugin_id, config, data_dirs):
# 调用 Rust FFI: _core.load_plugin(config)
result = await rust_ffi.load_plugin(plugin_id, config, data_dirs)
return PluginInstance(plugin_id, result.instance, ...)
理由:
- 核心加载逻辑在 Rust(线程安全、错误隔离)
- Python 胶水层仅做类型转换和聚合
- 符合 config.yaml
rust_core规范
3. 工具发现架构
决定:ToolRegistry 统一管理,tools/list 聚合所有插件工具。
ToolRegistry
├── register(plugin_id, tools)
├── unregister(plugin_id)
├── list_tools() -> List[ToolDef]
└── call_tool(name, args) -> ToolResult
理由:
- 中心化管理避免冲突
- Schema 验证在注册时执行
- 跨插件工具调用统一入口
4. FFI 绑定链路
⚠️ 禁止 ctypes:所有 FFI 必须通过 PyO3(rust-ffi.md 规范)
决定:Python → PyO3 _core.so → ABP PluginLoader。
Python PluginRegistry (_internal/)
→ PyO3 调用 _core.so
→ Rust abp_plugin_loader_* 函数
→ 返回 Python 对象(通过 .pyi 类型提示)
理由:
- PyO3 是 Rust 官方 Python 绑定方案
rust-ffi.md明确禁止 ctypes_core.pyi提供类型检查- anyio 异步调用通过
run_in_executor封装
Risks / Trade-offs
| 风险 | 影响 | 缓解措施 |
|---|---|---|
| Rust 源码未提交,接口可能变 | FFI 调用失败 | 接口版本化,核心保持向后兼容 |
| InProcess 插件崩溃影响主进程 | 稳定性下降 | 进程内插件加超时保护 + 错误隔离 |
| 工具 Schema 验证复杂度 | 开发体验下降 | JSON Schema 仅校验格式,不校验业务逻辑 |
Migration Plan
- Phase 1:PluginLoader trait + Python 聚合层(调用 Rust FFI)
- Phase 2:tools/list + ToolRegistry(Rust FFI + Python 聚合)
- Phase 3:PyO3 FFI 绑定 + config.yaml 集成
- Phase 4:测试覆盖 + 文档
Open Questions
- Q1: InProcess 插件是否需要独立的沙箱隔离?(当前:无)
- Q2: 工具注册时 Schema 校验严格程度?(当前:格式校验 + 必需字段)
- Q3: PluginRegistry 是否需要持久化?(当前:内存,进程重启丢失)