This release is a pre-release and may not be stable for production use.
NeoBot Modloader
NeoBot Modloader 用于从文件系统目录加载 Python 插件。
当前版本是一次破坏性插件 API 更新。插件入口只保留一种写法:显式导出 plugin = Plugin(...),不再支持旧的 setup(ctx)、ctx.on.*、Matcher、on_command() 等兼容 API。
插件结构
插件可以是单个 Python 文件:
plugins/
ping.py
也可以是一个包:
plugins/
weather/
plugin.toml
__init__.py
插件模块必须导出一个 Plugin 实例:
from neobot_modloader import Plugin
plugin = Plugin("ping")
快速开始
from neobot_modloader import Plugin, Reply
plugin = Plugin("ping")
@plugin.command("ping [args:rest]")
async def ping(args: str | None, reply: Reply):
await reply.send(f"pong: {args or ''}")
用户发送 /ping hello 时,args 会得到 "hello"。
消息链
处理器可以注入标准化后的 Message 对象。它会优先从 event["message"] 读取 OneBot 消息链;如果没有消息链,才使用 raw_message 作为纯文本兜底。
from neobot_modloader import Message, Plugin, Reply
plugin = Plugin("echo")
@plugin.message()
async def echo(message: Message, reply: Reply):
await reply.send(message)
常用属性:
message.raw_event
message.raw_message
message.segments
message.text
message.images
message.first_image
message.has_image
message.of_type("at")
reply.send(message) 会直接发送原始消息链,适合做复读、转发式响应或调试。
消息链 DSL
@plugin.command(...) 和 @plugin.message(...) 的模式匹配基于消息链 token stream,而不是只看 raw_message。
from neobot_modloader import ImageSegment, Plugin, Reply
plugin = Plugin("vision")
@plugin.command("识图 <img:image>")
async def vision(img: ImageSegment, reply: Reply):
await reply.send(img)
这段代码可以匹配类似 /识图 + 图片 的消息链,并把图片段注入到 img 参数。
支持的捕获写法:
| 模式 | 注入结果 |
|---|---|
<name> |
必填字符串 token |
[name] |
可选字符串 token |
<name:int> |
int |
<name:float> |
float |
<name:bool> |
bool |
<name:rest> |
剩余文本 token |
<img:image> |
一个图片段 |
[img:image] |
可选图片段 |
<imgs:list[image]> |
一个或多个图片段 |
如果不想要求 slash 命令,可以用 message 模式:
@plugin.message("识图 <img:image>")
async def vision_message(img: ImageSegment, reply: Reply):
await reply.send(img)
Reply
Reply.send() 是主要发送入口。
await reply.send("text")
await reply.send(message)
await reply.send(message.segments)
await reply.send(img)
await reply.send([
{"type": "text", "data": {"text": "result:"}},
{"type": "image", "data": {"url": image_url}},
])
也提供一些糖方法:
await reply.text("hello")
await reply.image(url=image_url)
await reply.private(user_id, "hello")
await reply.group(group_id, "hello")
reply.send(...) 支持:
strlist[dict]MessageMessageChainMessageSegmentImageSegmentlist[MessageSegment]
组合消息链
可以用 MessageChain 组合多个消息段:
from neobot_modloader import MessageChain
await reply.send(
MessageChain()
.text("收到图片: ")
.image(url=img.url, file=img.file)
)
配置
包插件可以使用 plugin.toml 配置启用状态、优先级、依赖和默认配置:
enabled = true
priority = 10
dependencies = []
python_dependencies = ["httpx"]
[config]
api_key = "secret"
default_city = "Shanghai"
插件名、版本、描述、作者以 Plugin(...) 为准:
from pydantic import BaseModel
from neobot_modloader import Plugin, Reply
class Config(BaseModel):
api_key: str
default_city: str = "Shanghai"
plugin = Plugin(
"weather",
version="1.0.0",
description="Weather query plugin",
config=Config,
)
@plugin.command("weather [city]")
async def weather(city: str | None, config: Config, reply: Reply):
await reply.send(city or config.default_city)
如果 plugin.toml 声明了和 Plugin(...) 冲突的 name 或 version,插件加载会失败。
注册子 Agent
插件可以声明可被主 Agent 委托的子 Agent。默认写法是 handler:接收自然语言 task,返回文本结果。
from neobot_modloader import AgentRequest, Plugin
plugin = Plugin("weather")
@plugin.agent("forecast", description="查询天气、解释天气状况")
async def forecast(task: str, request: AgentRequest, config: Config) -> str:
city = task.strip() or config.default_city
return f"{city} 今天晴,22-28 度。"
子 Agent 会以 <plugin>.<agent> 的名字暴露给主 Agent,例如 weather.forecast。
如果要使用 neobot_chat 的 Workflow、StateGraph、CompiledGraph,使用 factory=True 返回一个有 invoke(state) 的对象:
from neobot_chat import State, Workflow
from neobot_modloader import Plugin
plugin = Plugin("planner")
async def parse(state: State) -> State:
messages = list(state.get("messages", []))
task = str(messages[-1].get("content", "")) if messages else ""
return {**state, "_task": task}
async def answer(state: State) -> State:
messages = list(state.get("messages", []))
messages.append({"role": "assistant", "content": f"完成: {state.get('_task', '')}"})
return {**state, "messages": messages}
@plugin.agent("worker", description="使用 Workflow 处理委托任务", factory=True)
def build_worker() -> Workflow:
return Workflow().add_step(parse).add_step(answer)
子 Agent 默认不直接发消息,而是把结果返回给主 Agent。需要直接回复用户时,仍使用 @plugin.command 或 @plugin.message。
生命周期
from neobot_modloader import Plugin
plugin = Plugin("lifecycle")
@plugin.on_load
async def loaded(logger):
logger.info("loaded")
@plugin.on_startup
async def started(logger):
logger.info("started")
@plugin.on_shutdown
async def stopped(logger):
logger.info("stopped")
运行时
应用侧仍然使用运行时设施加载和管理插件:
runtime.load_all()
await runtime.load_registered()
await runtime.start_all()
await runtime.stop_all()
await runtime.reload_plugin("ping")
重载插件时,运行时会停止旧插件、清理已跟踪的订阅、Host 注册和 Agent 注册,清除模块缓存,然后重新导入插件并按需启动。
插件管理 API
Dashboard 或其他管理型插件可以通过生命周期注入拿到受控管理门面:
from neobot_modloader import Plugin, RuntimePluginContext
plugin = Plugin("manager")
@plugin.on_load
async def load(ctx: RuntimePluginContext):
snapshots = ctx.plugin_control.snapshot()
PluginControlFacade 只暴露运行期管理能力,不暴露完整 PluginRuntime:
await ctx.plugin_control.load_path(path, start=True)
await ctx.plugin_control.unload("ping")
await ctx.plugin_control.reload("ping")
await ctx.plugin_control.start("ping")
await ctx.plugin_control.stop("ping")
ctx.plugin_control.snapshot()
这些方法返回 PluginOperationResult 或 PluginSnapshot,用于展示结构化状态和错误,不需要访问 runtime/loader/manager 的私有成员。
公开 API
neobot_modloader 顶层只导出新的插件开发 API 和运行时基础设施:
from neobot_modloader import (
AgentRequest,
Bot,
DefaultPluginManager,
DiscoveredPlugin,
FilesystemPluginLoader,
ImageSegment,
Message,
MessageChain,
MessageSegment,
Plugin,
PluginControlFacade,
PluginHookBus,
PluginHostFacade,
PluginOperationResult,
PluginRuntime,
PluginSnapshot,
PythonDependencyInstaller,
Reply,
RuntimePluginContext,
image,
text,
)
破坏性迁移
已移除的旧 API:
setup(ctx)- 对象式旧
plugin create_plugin()ctx.on.*on_command()/MatcherPluginMetadataget_plugin_config()CommandArg等旧 DI sentinel- 旧兼容层模块
旧写法:
def setup(ctx):
@ctx.on.message(contains="ping")
async def ping(event):
await ctx.reply(event, "pong")
新写法:
from neobot_modloader import Plugin, Reply
plugin = Plugin("ping")
@plugin.message(contains="ping")
async def ping(reply: Reply):
await reply.send("pong")
旧写法:
ping = on_command("ping")
@ping.handle()
async def ping_handler(args: CommandArg):
await ping.finish(f"pong: {args}")
新写法:
from neobot_modloader import Plugin, Reply
plugin = Plugin("ping")
@plugin.command("ping [args:rest]")
async def ping(args: str | None, reply: Reply):
await reply.send(f"pong: {args or ''}")
旧写法:
segments = event.get("message", [])
await ctx.reply(event, segments)
新写法:
from neobot_modloader import Message, Plugin, Reply
plugin = Plugin("echo")
@plugin.message()
async def echo(message: Message, reply: Reply):
await reply.send(message)
Metadata
Release files for neobot-modloader 1.0.0a14
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| neobot_modloader-1.0.0a14.tar.gz | 31.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| neobot_modloader-1.0.0a14-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 74.1 kB
Release files / neobot_modloader-1.0.0a14.tar.gz
| Download URL | neobot_modloader-1.0.0a14.tar.gz |
|---|---|
| Size | 31.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5cc5cae3246ede605c391efb8098644cb293810998ef0cc7e0f3cff6af01c72b
|
|
BLAKE2b-256 checksum How to use checksums |
ef7fcc70ece491488ffe4855284d8342347e0e22756fb2592993089e3ca7a28c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / neobot_modloader-1.0.0a14-py3-none-any.whl
| Download URL | neobot_modloader-1.0.0a14-py3-none-any.whl |
|---|---|
| Size | 42.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ff8bd0e530965e1e8b3ee79c59b164086608d050de2661d01be2c1878ac3c2ff
|
|
BLAKE2b-256 checksum How to use checksums |
d8b04915e528f4d85d0c5322176b7974325bf5154cba41b75fcc2c0baec329b9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|