diff --git a/website/src/content/docs/docs/tutorials/index.mdx b/website/src/content/docs/docs/tutorials/index.mdx index e7877fdd..b2b0b28e 100644 --- a/website/src/content/docs/docs/tutorials/index.mdx +++ b/website/src/content/docs/docs/tutorials/index.mdx @@ -13,6 +13,7 @@ Tutorials are hands-on lessons. Use this section when you want to learn a workfl 2. [Connect MCP Servers with bub-mcp](/docs/tutorials/mcp/) — install the MCP plugin, wire up a time server, and call it from a Bub turn. 3. [Persist tapes in SQLAlchemy with SQLite](/docs/tutorials/tapestore-sqlalchemy/) — replace the file-based tape store with a local SQLite database. 4. [Run Bub with a local llama.cpp model](/docs/tutorials/local-llama-cpp/) — expose a GGUF Gemma model as a local OpenAI-compatible endpoint. +5. [Experimental: Extend Bub with Other Languages](/docs/tutorials/language-extensions/) — keep Bub in Python, move selected hooks into Go, Rust, or other languages, and wire them back through pluggy, WebAssembly, and Extism. ## Next steps diff --git a/website/src/content/docs/docs/tutorials/language-extensions.mdx b/website/src/content/docs/docs/tutorials/language-extensions.mdx new file mode 100644 index 00000000..e4385044 --- /dev/null +++ b/website/src/content/docs/docs/tutorials/language-extensions.mdx @@ -0,0 +1,156 @@ +--- +title: "Experimental: Extending Bub with Other Languages" +description: Keep Bub as a Python host while implementing select hooks in Go, Rust, or other languages—using pluggy, WebAssembly, and Extism. +sidebar: + order: 5 +--- + +This tutorial is experimental. It demonstrates **how to extend Bub from other languages**—not how to rewrite Bub itself as a multi-language project. + +Many teams want to write business logic in languages they already know well, or they want the runtime boundary to sit closer to their existing stack. Splitting the Bub core across multiple languages would make maintenance significantly harder. Bub takes a different approach: **keep the Python host**, and use [pluggy](https://pluggy.readthedocs.io/en/stable/), [WebAssembly](https://webassembly.org/), and [Extism](https://extism.org/) to let other languages implement select hooks. + +The goal here is not to replace the Bub kernel. It's to provide a pragmatic extension boundary—one that's useful whenever a hook makes more sense to write in Go, Rust, or something else entirely. + +## Before you start + +Make sure you have: + +- Bub installed into an active virtual environment (`bub install` requires running from inside a venv), with `bub --help` working. +- The `bub-extism` plugin installed from [`bub-contrib`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism). +- A Go toolchain if you plan to build the Go example. +- A Rust toolchain with the `wasm32-unknown-unknown` target if you plan to build the Rust example. + +You'll probably want to keep these references handy: + +- [`bub-extism` README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) +- [`bub-extism` examples](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism/examples) +- [Extism manifest reference](https://extism.org/docs/concepts/manifest/) +- [Bub hooks reference](/docs/reference/hooks/) + +## 1. Install the experimental plugin + +Install `bub-extism` into the same environment where Bub runs: + +```bash +bub install bub-extism@main +``` + +Verify that Bub picks it up: + +```bash +uv run bub hooks +``` + +You should see `extism` listed alongside `builtin`. + +## 2. Implement one hook in Go and one in Rust + +We'll use the two verified example modules that ship with `bub-extism`: + +- `go-build-prompt` implements the `build_prompt` hook +- `rust-run-model` implements the `run_model` hook + +Build the Go example: + +```bash +cd bub-contrib/packages/bub-extism/examples/go-build-prompt +GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o go-build-prompt.wasm . +``` + +Build the Rust example: + +```bash +cd bub-contrib/packages/bub-extism/examples/rust-run-model +cargo build --release --target wasm32-unknown-unknown +``` + +If you need the full build commands for either example, check the [examples guide](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism/examples). + +## 3. Wire both modules into `extism.json` + +Create an `extism.json` in your project root that binds the prompt hook to the Go module and the model hook to the Rust module: + +```json +{ + "plugins": { + "prompt": { + "manifest": { + "wasm": [ + { + "path": "bub-contrib/packages/bub-extism/examples/go-build-prompt/go-build-prompt.wasm" + } + ] + }, + "wasi": true, + "hooks": { + "build_prompt": "build_prompt" + } + }, + "model": { + "manifest": { + "wasm": [ + { + "path": "bub-contrib/packages/bub-extism/examples/rust-run-model/target/wasm32-unknown-unknown/release/bub_extism_rust_run_model.wasm" + } + ] + }, + "hooks": { + "run_model": "run_model" + } + } + } +} +``` + +A few things to note about this structure: + +- `manifest` follows the standard Extism manifest format. +- `hooks` maps Bub hook names to wasm exports. +- Bub still owns dispatch and precedence. + +Point Bub at the config file before running a turn: + +```bash +export BUB_EXTISM_CONFIG_PATH=./extism.json +``` + +## 4. Run a turn that spans both languages + +Now let Bub build a prompt first, then execute the model hook: + +```bash +uv run bub run "Say hello from a prompt built outside Python." +``` + +When everything is wired up correctly, Bub will: + +1. Call `build_prompt` inside the Go module +2. Pass the returned prompt to `run_model` inside the Rust module +3. Hand the final result back for this turn + +The CLI prints outbound messages in `[channel:chat_id]` format, so a default `bub run` typically shows `[cli:local]` first, followed by the rendered output. + +You can expect output similar to: + +```text +[cli:local] +[rust-run-model:cli:local] [go-build-prompt:cli:local] Say hello from a prompt built outside Python. +``` + +The Go module wraps the inbound prompt with a `[go-build-prompt:]` prefix; the Rust module then wraps the Go output with `[rust-run-model:]`. The leading `[cli:local]` line is the channel header emitted by the CLI adapter. + +## 5. Know the boundary + +This path works well when: + +- A particular hook genuinely fits better in another language +- A team wants more direct ownership of the implementation +- You still want Bub to act as the orchestration host + +**Don't** treat this as a reason to fragment the Bub core into separate rewrites in multiple languages. That would reintroduce exactly the kind of maintenance burden this architecture was designed to avoid. This is an extension path—not a rewrite plan. + +## Next steps + +- [Building plugins](/docs/build/plugins/) — learn the structure of first-party Bub plugins. +- [`bub-extism` README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) — read the package conventions and supported hooks. +- [Extism overview](https://extism.org/docs/overview) — understand how Extism runs wasm modules across languages. diff --git a/website/src/content/docs/zh-cn/docs/tutorials/index.mdx b/website/src/content/docs/zh-cn/docs/tutorials/index.mdx index 020e1c68..8d7729e7 100644 --- a/website/src/content/docs/zh-cn/docs/tutorials/index.mdx +++ b/website/src/content/docs/zh-cn/docs/tutorials/index.mdx @@ -13,6 +13,7 @@ sidebar: 2. [使用 bub-mcp 连接 MCP 服务器](/zh-cn/docs/tutorials/mcp/) — 安装 MCP 插件,接入时间服务器,并在 Bub turn 中调用。 3. [用 SQLAlchemy 与 SQLite 持久化 tape](/zh-cn/docs/tutorials/tapestore-sqlalchemy/) — 把基于文件的 tape store 换成本地 SQLite 数据库。 4. [使用本地 llama.cpp 模型运行 Bub](/zh-cn/docs/tutorials/local-llama-cpp/) — 把 GGUF Gemma 模型暴露成本地 OpenAI-compatible endpoint。 +5. [实验性:使用其他语言扩展 Bub](/zh-cn/docs/tutorials/language-extensions/) — 保持 Bub 作为 Python 宿主,把部分 hooks 放到 Go、Rust 或其他语言中实现,并通过 pluggy、WebAssembly 和 Extism 接回 Bub。 ## 下一步 diff --git a/website/src/content/docs/zh-cn/docs/tutorials/language-extensions.mdx b/website/src/content/docs/zh-cn/docs/tutorials/language-extensions.mdx new file mode 100644 index 00000000..7712aa69 --- /dev/null +++ b/website/src/content/docs/zh-cn/docs/tutorials/language-extensions.mdx @@ -0,0 +1,156 @@ +--- +title: 实验性:使用其他语言扩展 Bub +description: 通过 pluggy、WebAssembly 和 Extism,让 Bub 继续作为 Python 宿主,同时把部分 hooks 放到 Go、Rust 或其他语言中实现。 +sidebar: + order: 5 +--- + +本教程是实验性的。它展示的是**如何从其他语言扩展 Bub**,而不是把 Bub 本体改写成多语言项目。 + +很多团队希望把业务逻辑写在自己熟悉的语言里,或者让运行时边界更贴近现有的技术栈。如果把 Bub 核心拆成多个语言版本,维护成本会显著上升。Bub 选择的是另一条路:**保留 Python 宿主**,借助 [pluggy](https://pluggy.readthedocs.io/en/stable/)、[WebAssembly](https://webassembly.org/) 和 [Extism](https://extism.org/),让其他语言来实现部分 hook。 + +这里的目标不是替换 Bub 内核,而是提供一个务实的扩展边界——当某个 hook 用 Go、Rust 或其他语言实现更合适时,这条路径会非常有用。 + +## 准备工作 + +开始前,请确保: + +- 已经把 Bub 安装到一个激活的虚拟环境中(`bub install` 必须在 venv 内运行),并且 `bub --help` 可以正常工作。 +- 已安装来自 [`bub-contrib`](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) 的 `bub-extism` 插件。 +- 如果要构建 Go 示例,需要安装 Go 工具链。 +- 如果要构建 Rust 示例,需要安装 Rust 工具链,并添加 `wasm32-unknown-unknown` target。 + +在动手之前,建议先浏览这些参考链接: + +- [`bub-extism` README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) +- [`bub-extism` 示例](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism/examples) +- [Extism manifest 参考](https://extism.org/docs/concepts/manifest/) +- [Bub hooks 参考](/zh-cn/docs/reference/hooks/) + +## 第一步:安装实验性插件 + +将 `bub-extism` 安装到 Bub 所在的同一环境: + +```bash +bub install bub-extism@main +``` + +安装后,确认 Bub 已经识别到它: + +```bash +uv run bub hooks +``` + +你应该能在输出中看到 `extism` 与 `builtin` 并列出现。 + +## 第二步:用 Go 和 Rust 各实现一个 hook + +我们使用 `bub-extism` 自带的两个经过验证的示例模块: + +- `go-build-prompt` 实现了 `build_prompt` hook +- `rust-run-model` 实现了 `run_model` hook + +构建 Go 示例: + +```bash +cd bub-contrib/packages/bub-extism/examples/go-build-prompt +GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o go-build-prompt.wasm . +``` + +构建 Rust 示例: + +```bash +cd bub-contrib/packages/bub-extism/examples/rust-run-model +cargo build --release --target wasm32-unknown-unknown +``` + +如果需要完整的构建命令,请参考 [examples 指南](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism/examples)。 + +## 第三步:用 extism.json 连接两个模块 + +在项目根目录创建 `extism.json`,将 prompt hook 绑定到 Go 模块,model hook 绑定到 Rust 模块: + +```json +{ + "plugins": { + "prompt": { + "manifest": { + "wasm": [ + { + "path": "bub-contrib/packages/bub-extism/examples/go-build-prompt/go-build-prompt.wasm" + } + ] + }, + "wasi": true, + "hooks": { + "build_prompt": "build_prompt" + } + }, + "model": { + "manifest": { + "wasm": [ + { + "path": "bub-contrib/packages/bub-extism/examples/rust-run-model/target/wasm32-unknown-unknown/release/bub_extism_rust_run_model.wasm" + } + ] + }, + "hooks": { + "run_model": "run_model" + } + } + } +} +``` + +这个配置文件的关键点: + +- `manifest` 遵循标准 Extism manifest 格式。 +- `hooks` 把 Bub 的 hook 名称映射到 wasm 模块的导出函数。 +- Bub 仍然负责整体调度和优先级管理。 + +配置完成后,设置环境变量让 Bub 找到它: + +```bash +export BUB_EXTISM_CONFIG_PATH=./extism.json +``` + +## 第四步:跑一次跨语言的对话轮次 + +现在让 Bub 先用 Go 构建 prompt,再交给 Rust 执行 model hook: + +```bash +uv run bub run "Say hello from a prompt built outside Python." +``` + +如果一切配置正确,整个流程是: + +1. Bub 调用 Go 模块中的 `build_prompt` +2. 将返回的 prompt 传递给 Rust 模块中的 `run_model` +3. Rust 模块返回最终结果,完成本次对话轮次 + +CLI 会按 `[channel:chat_id]` 的格式输出消息,默认的 `bub run` 通常会先显示 `[cli:local]`,然后是实际内容。 + +示例输出大致如下: + +```text +[cli:local] +[rust-run-model:cli:local] [go-build-prompt:cli:local] Say hello from a prompt built outside Python. +``` + +Go 模块会在入站 prompt 前加上 `[go-build-prompt:]` 前缀;Rust 模块再把 Go 的输出包一层 `[rust-run-model:]`。开头的 `[cli:local]` 是 CLI 通道发出的频道头。 + +## 第五步:理解这条路径的边界 + +这条扩展路径适合以下场景: + +- 某个 hook 用其他语言实现确实更自然 +- 团队希望更直接地掌控和共同维护具体实现 +- 仍然想让 Bub 作为核心编排宿主 + +**不要**把它当作把 Bub 核心拆成多个独立语言版本的理由——那样会把这套架构原本要避免的维护负担重新引回来。这是一个扩展路径,不是重写计划。 + +## 下一步 + +- [构建插件](/zh-cn/docs/build/plugins/) — 了解第一方 Bub 插件的结构。 +- [`bub-extism` README](https://github.com/bubbuild/bub-contrib/tree/main/packages/bub-extism) — 阅读 package 约定及支持的 hooks。 +- [Extism 概览](https://extism.org/docs/overview) — 了解 Extism 如何在不同语言中运行 wasm 模块。