Skip to content

插件开发(给 LLM) ​

最小模板(照抄) ​

python
# plugins/<plugin_id>/main.py,plugin_id 全小写下划线
__plugin_meta__ = {"name": "中文名", "version": "1.0.0", "author": "…",
                   "desc": "一句话", "priority": 50}   # 越小越先,守卫类 2-10

def register(ctx):
    ctx.command("/签到", handle, alias="/sign", description="…",
                require_admin=False, require_superuser=False,
                require_perm="myplugin.sign")          # 权限节点,可选
    ctx.on_raw_message(on_raw)   # (raw: dict, bot_name) -> True=接管全部后续
    ctx.task("*/10 * * * *", job)
    ctx.on("message", listener)  # 返回 True=已处理,路由终止
    ctx.hook("action.after", fn)

async def handle(event, match):      # match.group(1)=命令后参数
    await ctx.asend_msg(user_id=event.user_id, group_id=event.group_id, message="文本")

def unregister(): ...                # 可选

约定:sync 用同步方法,async 用 a 前缀(asend_msg/aapi);数据只写 ctx.get_data_dir()(= data/plugins_dat/<插件>/),不写代码目录;重复注册自动去重。

ctx 常用面 ​

类别成员
发送send_msg/asend_msg(user_id, group_id, message)、api/aapi(action, bot=None, **params)、actions
会话await wait_for(event, prompt, timeout)、create_session(event)(session 插件)
数据db_query/db_execute(sql, params)(及 db_query_one/db_insert/create_table 与异步 *_async 版;SQLite/MySQL 双方言)、get_config(key, default)
权限has_perm(user_id, node)、audit_log(action)
其它on/emit/aemit、hook/unhook、register_api(path, view)、webui(...)、log(msg)

middleware / transform 不在主框架 PluginContext 里。 这两个 surface 只属于 --iipc 模式下的 framework/iipc/py_sdk(Go 内核 + Python SDK)。用默认 python main.py 启动时调用 ctx.middleware(...) / ctx.transform(...) 会直接 AttributeError。下面两节的示例同样只在 --iipc 模式可用。

event 字段:message(提取后文本)、raw_message、user_id/group_id/is_group、sender、 role、is_admin、segments、reply_id(被回复消息 ID,属性)、stop_event()(停止传播)。

LLM 子系统(可选) ​

core_plugins.llm_load(装载器)+ plugins/llm_core(对话核心)。后者释放自 zip 载荷, 运行时目录内的改动会被 manifest 抹平,改代码请改 core_plugins/llm_load/src/llm_core/ 并 python tools/build_llm_payload.py --write。

  • 取服务:fw.services.get('llm_core')
  • 注册函数:@svc.tool(description=...) 或 svc.register_tool(FunctionTool(...))
  • 注册模型提供商:svc.register_provider(Provider 子类实例)(provider id 要各不相同)
  • 函数契约:描述写给模型看;参数是 JSON Schema(也可按类型注解推导);返回 str 回灌、 返回 None 不回灌;参数校验失败会回灌错误信息让模型自纠
  • 详见 接入大模型

IIPC 链路下写插件(python main.py --iipc) ​

插件写法基本照抄本页即可(register(ctx)、装饰器、ctx.*、Event 字段都 1:1 对齐), 但有几条只对 IIPC 成立的差异:

  1. handler 里裸用 ctx 可以——IIPC 已对齐 module.ctx = ctx 注入(原框架在 loader/base.py)。
  2. 内建服务型插件跑不了:依赖 ctx._framework.services 的(如 core_plugins/scheduler、 session)是 host 进程内 API,IIPC 插件在独立进程没有 _framework。能对口的只有 plugins/* 这类用户态插件。
  3. call_async 只给同步 handler 用:在 async handler 里 fut.result() 会阻塞插件 自己的事件循环,必然超时。
  4. 插件进程的 stdout 不进内核日志:插件内异常要自己落文件才看得到(排查时最容易踩)。
  5. 端口 / DB / 管理员等走 config.yaml 的 iipc: 段,不写死在插件里。

未覆盖:Event.perms/perm_groups/primary_group、WebUI 等价物、真 OneBot 对接。 详见 IIPC 内核链路。

跨插件访问 ​

sys.modules.get("plugin_<id>")(官方插件 core_plugin_<name>;html_assembler 兼容别名 plugin_html_assembler)。拿不到返回 None,不要 [] 直取;依赖顺序时订阅 system.plugin.loaded。

多阶段中间件与全局变换节点 ​

这两类是"自由度"的核心扩展点:把"命令 + 消息"两阶段模型升级为可编排的管线, 任意插件都能在事件生命周期的节点插入逻辑,不必改写框架或整体接管消息。

进程内中间件 ctx.middleware(stage, priority, handler) ​

在本插件处理链路的节点拦截 / 变换 / 短路。四个阶段:

  • pre:事件入站后、命令匹配前。handler(event, ctx),可就地改写 event (脱敏 / 翻译 / 补全字段),返回 False 中止后续中间件与分发。
  • post:本事件分发完成后。handler(event, ctx),事后统计 / 审计 / 落库。
  • send:每条出站消息 flush 前。handler(params, ctx),params 为 {"message","user_id","group_id",...};返回 None/False 拦截该条,返回 dict 改写后发出。
  • error:任意 handler 抛异常时。handler(event, exc, ctx),中央异常处理 / 告警。
python
def register(ctx):
    @ctx.middleware("pre", priority=10)
    def normalize(event, ctx):
        event.message = event.message.strip()   # 下游命令看到的已是去空格文本

    @ctx.middleware("send", priority=10)
    def sign(params, ctx):
        p = dict(params)
        p["message"] = (p.get("message") or "") + " — by 我的插件"
        return p

零成本:未注册任何中间件时,分发路径与以前完全一致。

全局变换节点 ctx.transform(priority, handler) ​

内核在把任意事件扇出给所有订阅者之前,按 priority 升序调用各变换节点,把插件返回的 event.data(dict)作为新的事件数据往下走;返回 None 则全局丢弃该事件。等价于在内核级 插入一个"变换节点"——所有订阅者(命令、on 处理器)都看到变换后的事件,而无需各自实现一遍。

python
def register(ctx):
    @ctx.transform(priority=10)
    def normalize(params):
        params = dict(params or {})
        params["message"] = (params.get("message") or "").strip()
        return params        # 返回 None 则丢弃该事件

适用:协议归一、字段补全、全局脱敏、消息改写。注意:变换节点本身不必订阅该事件类型 (它由内核经 RPC 回调,不靠扇出),且不要在变换里又把同类型事件重新 emit,避免回环。

坑 ​

  1. 别绕过框架直跑 main.py(模块名/ctx/DB 全靠加载器)。
  2. raw handler 返回 True 会吞掉该消息的命令匹配,谨慎。
  3. @机器人 前缀已在 Event 构造时剥离,event.message 是干净文本。
  4. SQLite 只适合小环境,多群上 MySQL(config.yaml → database.type)。
  5. SQL 用参数占位符;批量写参考 stats_writer 的队列模式,别在热路径直查库。

基于 MIT + Apache 2.0 双协议发布