跳转到内容

插件

本指南展示如何打包一个 Bub 插件 —— 一个注册到 bub 入口点组、并向框架运行时贡献钩子实现的 Python 发行包。

  • 已安装 uvbub 的 Python 3.12+ 环境(见安装)。
  • 已经走过一次完整的 Bub 轮次(见首次轮次)。
  • 一个基于 pyproject.toml 的构建后端;下面示例使用 hatchling

最小插件 = 一个 Python 包加一个 pyproject.toml

bub-myplugin/
├─ pyproject.toml
└─ src/
   └─ bub_myplugin/
      ├─ __init__.py
      ├─ plugin.py
      └─ tools.py        # 可选,见步骤 4

发行名建议以 bub- 开头(例如 bub-myplugin)。导入名通常把短横线换成下划线(bub_myplugin)。

Bub 通过 bub 组的 Python 入口点发现插件。在 pyproject.toml 里加入:

[project]
name = "bub-myplugin"
version = "0.1.0"
dependencies = ["bub>=0.3"]

[project.entry-points."bub"]
myplugin = "bub_myplugin.plugin"

[tool.hatch.build.targets.wheel]
packages = ["src/bub_myplugin"]

左侧入口点键(myplugin)是 Bub 用于诊断的名字,右侧是 Bub 启动时导入的目标。

3. 在模块、实例、可调用三种形态中选择

Section titled “3. 在模块、实例、可调用三种形态中选择”

BubFramework.load_hooks() 接受三种入口点目标。if callable(plugin) 分支是判别点:

# 来自 src/bub/framework.py
if callable(plugin):  # Support entry points that are classes
    plugin = plugin(self)
self._plugin_manager.register(plugin, name=plugin_name)

由此得到三种合法选择。

模块目标。 指向一个在模块层定义了 @hookimpl 函数的模块。Bub 直接注册这个模块对象:

[project.entry-points."bub"]
myplugin = "bub_myplugin.plugin"
# bub_myplugin/plugin.py
from bub import hookimpl


@hookimpl
def build_prompt(message, session_id, state):
    return "custom prompt"

实例目标。 指向一个事先构造好的实例。Bub 原样注册:

[project.entry-points."bub"]
myplugin = "bub_myplugin.plugin:my_plugin"
# bub_myplugin/plugin.py
from bub import hookimpl


class MyPlugin:
    @hookimpl
    def build_prompt(self, message, session_id, state):
        return "custom prompt"


my_plugin = MyPlugin()

可调用目标(类或工厂)。 指向一个把 BubFramework 作为唯一参数的类或工厂。Bub 用框架调用它,再把返回值注册进去。当插件需要持有框架的活引用时使用——例如要在 CLI 已经处理过 --workspace 之后再读 framework.workspace

[project.entry-points."bub"]
myplugin = "bub_myplugin.plugin:MyPlugin"
# bub_myplugin/plugin.py
from bub import BubFramework, hookimpl


class MyPlugin:
    def __init__(self, framework: BubFramework) -> None:
        self.framework = framework

    @hookimpl
    def build_prompt(self, message, session_id, state):
        workspace = self.framework.workspace
        return f"custom prompt from {workspace}"

visual-base 的 EyeImpl 正是因为这个原因采用可调用形态:它捕获框架,从而 provide_channels 时能读到 CLI 处理后的工作区。

把插件安装到运行 bub 的同一个 Python 环境中。如果你已经激活了该环境,在插件目录下运行:

uv pip install -e .

Bub 从当前 Python 环境加载入口点,因此可编辑安装能在不重新安装的情况下吃到代码变更。如果你不在一个已激活的 Bub 虚拟环境中,请在插件项目中运行 uv sync,并从该项目里使用 uv run bub ...。同样的模式见首个插件

运行钩子诊断命令:

uv run bub hooks

每个已注册钩子会列出贡献它的插件。该命令展示发现到的实现顺序,不等价于精确的 firstresult 胜出者;运行时优先级见钩子参考。一个覆盖了 build_promptrun_modelEchoPlugin,预期输出形如:

build_prompt: builtin, echo
run_model: builtin, echo
...

如果每一行都没有你的插件名,说明入口点没有加载。常见原因:

  • 入口点组写错了 —— 必须是 bub(不是 bub.plugins)。
  • 包没安装到当前 Python 环境。
  • 模块导入时抛错。Bub 会以 Failed to load plugin '<name>' 记录错误。

上述模式在已发布的插件中都能看到:

  • 钩子 —— 各钩子签名的配方手册
  • 工具 —— 注册可被模型调用的工具
  • 钩子参考 —— 完整签名表
  • 发行版 —— 把插件打包为产品