MaiBot 插件开发的唯一依赖。提供插件基类、配置模型、组件装饰器、能力代理和类型定义。
完整文档:插件开发指南 — 覆盖 16 种能力代理、日志接口、7 种正式声明装饰器、1 种兼容装饰器、配置模型、消息模型、生命周期、调试与发布。
Breaking change(2.0.0):
WorkflowStep已移除并重命名为HookHandler。组件协议值统一为大写(如ACTION、EVENT_HANDLER)。顶层仍保留WorkflowStep名称,但只会在运行时抛出明确错误,不再提供兼容映射。
pip install maibot-plugin-sdkfrom maibot_sdk import CONFIG_RELOAD_SCOPE_SELF, Command, MaiBotPlugin, Tool
from maibot_sdk.types import ToolParameterInfo, ToolParamType
class MyPlugin(MaiBotPlugin):
async def on_load(self) -> None:
return None
async def on_unload(self) -> None:
return None
async def on_config_update(self, scope: str, config_data: dict[str, object], version: str) -> None:
if scope == CONFIG_RELOAD_SCOPE_SELF:
self.ctx.logger.info("插件配置已更新: version=%s", version)
del config_data
@Tool(
"greet",
brief_description="在合适的时候向用户打招呼",
detailed_description="参数说明:\n- stream_id:string,必填。当前聊天流 ID。",
parameters=[
ToolParameterInfo(
name="stream_id",
param_type=ToolParamType.STRING,
description="当前聊天流 ID",
required=True,
),
],
)
async def handle_greet(self, stream_id: str, **kwargs):
del kwargs
await self.ctx.send.text("你好!", stream_id)
return {"success": True, "message": "已回复"}
@Command("hello", pattern=r"^/hello")
async def handle_hello(self, **kwargs):
await self.ctx.send.text("Hello!", kwargs["stream_id"])
return True, "Hello!", 2
def create_plugin():
return MyPlugin()将上述代码保存为 plugin.py,放入 MaiBot 的 plugins/ 目录即可自动加载。
如果你是在迁移旧插件,Action 装饰器仍然可以继续使用;但它现在只是兼容入口,SDK 内部会把它转换成 Tool 声明,新的插件建议直接使用 @Tool。
如果你在编写平台接入插件,请使用 @MessageGateway 声明消息网关组件,并通过 self.ctx.gateway.route_message() 将外部平台消息注入 Host。详细示例见 插件开发指南 中的 MessageGateway 章节。
如果你在编写新的 LLM Provider 插件,请在 _manifest.json 顶层 llm_providers 中静态声明 client_type,并在插件方法上使用 @LLMProvider("同一个 client_type")。Runner 会校验 manifest 与装饰器声明完全一致;不同插件声明同一个 client_type 时,冲突双方都会被阻止加载。完整请求/返回字段见 插件开发指南 中的 LLMProvider 章节。
通过 self.ctx 访问所有能力,调用自动转发为 RPC 请求:
| 属性 | 说明 |
|---|---|
ctx.api |
插件 API 查询、调用与动态同步 |
ctx.gateway |
消息网关路由与运行时状态上报 |
ctx.send |
发送文本、图片、表情、转发、混合消息 |
ctx.db |
数据库增删改查计数 |
ctx.llm |
LLM 文本生成与工具调用 |
ctx.config |
插件配置读取 |
ctx.emoji |
表情包管理 |
ctx.message |
历史消息查询 |
ctx.frequency |
发言频率控制 |
ctx.component |
插件与组件管理 |
ctx.chat |
聊天流查询、打开或创建聊天流 |
ctx.person |
用户信息查询 |
ctx.render |
将 HTML 渲染为 PNG 图片 |
ctx.knowledge |
LPMM 知识库搜索 |
ctx.tool |
LLM 工具定义查询 |
ctx.maisaka |
Maisaka 上下文追加与主动任务 |
ctx.logger |
插件日志(标准 logging.Logger) |
如果插件负责把外部平台接入 MaiBot,可使用 @MessageGateway 声明消息网关组件:
from typing import Any
from maibot_sdk import CONFIG_RELOAD_SCOPE_SELF, MaiBotPlugin, MessageGateway
class NapCatGatewayPlugin(MaiBotPlugin):
async def on_load(self) -> None:
await self.ctx.gateway.update_state(
gateway_name="napcat_gateway",
ready=True,
platform="qq",
account_id="10001",
scope="primary",
metadata={"protocol": "napcat"},
)
async def on_unload(self) -> None:
await self.ctx.gateway.update_state(
gateway_name="napcat_gateway",
ready=False,
)
async def on_config_update(self, scope: str, config_data: dict[str, object], version: str) -> None:
if scope == CONFIG_RELOAD_SCOPE_SELF:
self.ctx.logger.info("配置已更新: %s", version)
del config_data
@MessageGateway(
route_type="duplex",
name="napcat_gateway",
platform="qq",
protocol="napcat",
account_id="10001",
scope="primary",
)
async def send_to_platform(
self,
message: dict[str, Any],
route: dict[str, Any] | None = None,
metadata: dict[str, Any] | None = None,
**kwargs: Any,
) -> dict[str, Any]:
# 将 Host MessageDict 转成平台动作并发送
return {"success": True, "external_message_id": "platform-msg-1"}
async def handle_inbound(self, payload: dict[str, Any]) -> None:
accepted = await self.ctx.gateway.route_message(
gateway_name="napcat_gateway",
{
"message_id": payload["message_id"],
"platform": "qq",
"message_info": {
"user_info": {
"user_id": payload["user_id"],
"user_nickname": payload["nickname"],
},
"additional_config": {},
},
"raw_message": payload["message"],
},
route_metadata={"self_id": "10001", "connection_id": "primary"},
external_message_id=payload["message_id"],
dedupe_key=payload["message_id"],
)
if not accepted:
self.ctx.logger.warning("Host 未接收入站消息: %s", payload["message_id"])
def create_plugin():
return NapCatGatewayPlugin()主程序会根据 route_type 和运行时状态选择可用网关:
route_type="send"或"duplex"的网关可被 Platform IO 选中处理出站消息route_type="receive"或"duplex"的网关可通过ctx.gateway.route_message()注入入站消息- 插件应在链路可用时调用
ctx.gateway.update_state(..., ready=True),在断开或卸载时上报ready=False
如果你希望 Runner 自动补齐默认配置、向 WebUI 暴露结构化 Schema,并在插件内以强类型对象读取配置,可以声明 config_model:
from maibot_sdk import Field, MaiBotPlugin, PluginConfigBase
class PluginSection(PluginConfigBase):
"""插件基础配置。"""
__ui_label__ = "插件设置"
enabled: bool = Field(default=True, description="是否启用插件")
greeting: str = Field(default="你好!", description="默认问候语")
class MyPluginConfig(PluginConfigBase):
"""插件完整配置。"""
plugin: PluginSection = Field(default_factory=PluginSection)
class MyPlugin(MaiBotPlugin):
config_model = MyPluginConfig
async def on_load(self) -> None:
self.ctx.logger.info("当前问候语: %s", self.config.plugin.greeting)配置来源仍然是插件目录下的 config.toml。当插件声明了 config_model 后,Runner / Host 可以基于模型生成默认配置和 WebUI Schema,插件代码则可以通过 self.config 访问校验后的强类型配置对象;需要临时读取原始配置值时,也仍可继续使用 await self.ctx.config.get(...)。
- Runner 会在调用
on_load()之前先注入PluginContext并完成 capability bootstrap,因此插件可以在on_load()中直接调用self.ctx.send.*、self.ctx.db.*等能力,无需自行等待“注册完成”信号。 - SDK 插件必须实现
on_load()、on_unload()和on_config_update(scope, config_data, version)三个生命周期方法;未实现时 Runner 会拒绝加载。 HookHandler现在基于命名 Hook 点注册,不再依赖固定的 workflow stage;插件通过hook、mode、order描述自己的订阅位置。PluginContext当前暴露 16 个能力代理:api、gateway、send、db、llm、config、emoji、message、frequency、component、chat、person、render、knowledge、tool、maisaka。ctx.gateway.route_message()/ctx.gateway.update_state()分别对应主程序的入站路由和网关状态上报接口;只有处于ready=True的消息网关才会被主程序接收入站消息或纳入出站路由。ctx.api支持查询、调用其他插件公开的 API,也支持用register_dynamic_api()/sync_dynamic_apis()动态更新当前插件的 API 集合。- 如果插件声明了
config_model,Runner 会在注入配置时按模型补齐默认值并构造self.config强类型对象;Host / WebUI 也可复用get_default_config()与get_webui_config_schema()导出的配置元数据。 ctx.send.custom(custom_type, data, stream_id)现在会同时发送新旧两套字段别名,便于与不同版本 Host 兼容。ctx.db.count(model_name, filters)直接返回int,SDK 会自动解包 Host 返回的 RPC 结果。- 对于
config.get()、chat.*、message.*、person.*、frequency.get_*()、tool.get_definitions()等接口,SDK 会自动把 Host 返回的单字段包装结果解包为插件更直观的值、列表或字典;兼容层异步 API 也保持相同语义。 ctx.render.html2png()可将 HTML 模板渲染为 PNG 图片,适合卡片、榜单或分享图等需要图片化输出的场景。- 兼容层
emoji_api.get_random()/emoji_api.get_by_description()会返回归一化后的字典结果,而不是旧版 tuple 结构;迁移旧插件时请按字段读取。 ctx.chat.*查询接口支持显式传入platform,不再被固定到默认平台;ctx.chat.open_session()可按平台目标打开或创建聊天流。ctx.llm.generate*()会同时兼容model和model_name字段;插件侧优先读取model即可。- 旧版同步
component_manage_api/plugin_manage_api查询函数会返回最近一次运行时同步到本地的插件快照;如果需要实时状态,优先使用新的异步ctx.component.*能力。 - 插件热重载采用“验证通过后切换”的安全策略。正常插件开发无需感知 generation 细节,但在 reload 失败时,旧插件实例会继续提供服务。
ctx.component.load_plugin()/ctx.component.reload_plugin()在新运行时里只会在切换成功后返回成功;如果新 Runner 预热失败并回滚,SDK 会收到失败结果,而不是“已回滚但仍返回成功”的假阳性。@LLMProvider可声明新的模型 Providerclient_type;Provider 插件必须同时在 manifest 的llm_providers中静态声明,遗漏或冲突会导致整个插件不加载。
my_plugin/
plugin.py # 插件入口,包含 create_plugin()
config.toml # 可选配置
- Python >= 3.10
- pydantic >= 2.0
- msgpack >= 1.0
git clone https://github.com/Mai-with-u/maibot-plugin-sdk.git
cd maibot-plugin-sdk
uv sync --extra dev
uv run ruff check . # lint
uv run ruff format --check . # 格式检查
uv run pyright # 类型检查
uv run mypy . # 类型检查
uv run pytest -v # 测试