Skip to main content

Add your description here

Project description

NeoBot Modloader

NeoBot Modloader 用于从文件系统目录加载 Python 插件。

当前版本是一次破坏性插件 API 更新。插件入口只保留一种写法:显式导出 plugin = Plugin(...),不再支持旧的 setup(ctx)ctx.on.*Matcheron_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(...) 支持:

  • str
  • list[dict]
  • Message
  • MessageChain
  • MessageSegment
  • ImageSegment
  • list[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(...) 冲突的 nameversion,插件加载会失败。

注册子 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_chatWorkflowStateGraphCompiledGraph,使用 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

neobot_modloader 顶层只导出新的插件开发 API 和运行时基础设施:

from neobot_modloader import (
    AgentRequest,
    Bot,
    DefaultPluginManager,
    DiscoveredPlugin,
    FilesystemPluginLoader,
    ImageSegment,
    Message,
    MessageChain,
    MessageSegment,
    Plugin,
    PluginHookBus,
    PluginHostFacade,
    PluginRuntime,
    PythonDependencyInstaller,
    Reply,
    image,
    text,
)

破坏性迁移

已移除的旧 API:

  • setup(ctx)
  • 对象式旧 plugin
  • create_plugin()
  • ctx.on.*
  • on_command() / Matcher
  • PluginMetadata
  • get_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)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

neobot_modloader-1.0.0a10.tar.gz (28.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

neobot_modloader-1.0.0a10-py3-none-any.whl (39.3 kB view details)

Uploaded Python 3

File details

Details for the file neobot_modloader-1.0.0a10.tar.gz.

File metadata

  • Download URL: neobot_modloader-1.0.0a10.tar.gz
  • Upload date:
  • Size: 28.4 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for neobot_modloader-1.0.0a10.tar.gz
Algorithm Hash digest
SHA256 43c710b547a922afda7deac19f37f446bc77d751c357ace4b0e2dd7bccdab2d5
MD5 4d71b638ae1d18354422600a3f31e485
BLAKE2b-256 e6649c08cc8d717e3512351d7eaa2bffd7eebc882c447799df531dfa9967e937

See more details on using hashes here.

File details

Details for the file neobot_modloader-1.0.0a10-py3-none-any.whl.

File metadata

  • Download URL: neobot_modloader-1.0.0a10-py3-none-any.whl
  • Upload date:
  • Size: 39.3 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for neobot_modloader-1.0.0a10-py3-none-any.whl
Algorithm Hash digest
SHA256 3049cb6013f671d9809937d900a3254daa7462637cb6674b43a73ed04296b2ad
MD5 745e518e7950974d01682387a7ba7efd
BLAKE2b-256 9639676a2ca7da7738f19f5d2023148f7bf07ee07be553f12a16291087ae313f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page