跳转到内容

应用 Hooks

Hooks 把插件接到 Bub 应用上。本页列出 BubHookSpecs 声明的 26 个 hooks。用法示例见改变应用行为,应用阶段顺序见应用的一轮处理。

下文的“首个”指按 hook 优先级取得第一个非 None 的结果,“全部”指调用每个实现。普通 hook 抛出异常时,本次分发停止,异常向上传播;agent 拦截 hooks 和 on_error 有各自的异常处理规则。

Hook 签名 行为
resolve_session (message: Envelope) -> str 取首个结果;falsy 结果回退到 framework 按渠道和聊天标识构造的会话 ID。
load_state (message: Envelope, session_id: str) -> TurnState 收集全部状态片段;先合并低优先级片段,让高优先级值覆盖。
build_prompt (message: Envelope, session_id: str, state: TurnState) -> list[UserContent] | LegacyPrompt 取首个结果;选中 falsy 值时回退到入站内容,不再尝试其他实现。
run_model (prompt, session_id, state) -> str 提供模型阶段的文本结果;与 run_model_stream 二选一实现。
run_model_stream (prompt, session_id, state) -> AsyncStreamEvents 提供模型阶段的流;与 run_model 二选一实现。
continue_prompt (prompt: LegacyPrompt | None, tape: Tape, state: StreamState) -> str | None 取首个结果;由 AgentPlugins 在 status 为 continue 的 StepFinished 上调用,返回字符串时添加用户消息。
save_state (session_id, state, message, model_output) -> None 在模型阶段的 finally 中调用全部实现;此前会话、状态或提示词准备失败时不会调用。
render_outbound (message, session_id, state, model_output) -> list[Envelope] 收集并拼接消息批次;合并结果为空时使用 framework 的默认出站消息。
dispatch_outbound (message: Envelope) -> bool 针对每条出站消息调用全部实现。

这些 hooks 可以同步或异步实现。continue_prompt 接收 StreamState;表中其他 state 参数是可变的 TurnState 字典。

模型适配器先按插件注册顺序的逆序选择文本或流式 hook,再按 hook 优先级分发选中的名称。它可以把流收集为文本,也可以把文本回复包装成流。每个插件选择一种模型 hook 实现。

Hook 签名 行为
register_cli_commands (app: typer.Typer) -> None 创建 CLI 时同步调用全部实现。
onboard_config (current_config: dict) -> dict | None 同步调用,将每个返回片段合入累计配置;非 dict 返回值抛出 TypeError。
provide_model_options (session_id: str, workspace: Path | None) -> ModelOptions | None 按优先级收集模型选项,可异步实现;调用方负责选择和持久化。
provide_environment (session_id: str, workspace: Path) -> Environment | None 取首个结果,可异步实现;每个会话缓存一个环境,在退出 running() 时关闭。
system_prompt (prompt, state: TurnState) -> str 同步调用,按 hook 优先级的逆序用空行拼接 truthy 片段。
provide_tape_store () -> TapeStore | AsyncTapeStore | None 进入 running() 时同步取得首个结果;可以用生成器 yield 当前 store。
provide_lifespan () -> Iterator[None] | AsyncIterator[None] | None 收集资源生命周期,在每个 running() 作用域中分别进入一次。
provide_tape_sidecar () -> TapeSidecar 同步收集 sidecars,同名时保留优先级顺序中的首个结果。
provide_channels (message_handler: MessageHandler) -> list[Channel] 同步收集渠道,按 Channel.name 去重,保留优先级顺序中的首个结果。
build_tape_context () -> TapeContext 取首个同步结果,选择应用 agent 的上下文策略。
admit_message (session_id, message, turn: TurnSnapshot) -> AdmitDecision | None 渠道调度前取首个结果,可异步实现;None 保留渠道默认的并发调度。
provide_tools () -> list[Tool] 同步收集工具,同名时保留优先级顺序中的首个结果。
provide_steering_inbox () -> SteeringInbox | None 进入 running() 时同步取得首个结果;可以用生成器 yield inbox。
on_error (stage: str, error: Exception, message: Envelope | None) -> None 通知全部观察者;观察者异常被记录并跳过。异步实现只在异步通知路径上生效。

同步分发跳过 awaitable 返回值,并记录 hook.async_not_supported 告警。资源 provider 可以返回同步或异步生成器,Bub 会把它们作为上下文管理器进入。

Hook 签名 行为
before_llm_call (request: LLMCallRequest, state: TurnState) -> LLMCallRequest | LLMCallDecision | None 链式更新请求;finish decision 跳过供应商调用并终止调用链。
after_llm_call (request: LLMCallRequest, result: LLMCallResult, state: TurnState) -> None 观察完成的供应商调用或普通异常失败。
before_tool_call (call: ToolCall, state: TurnState) -> ToolCallDecision | None 链式更新参数;replace 或 deny 终止调用链。
after_tool_call (call: ToolCall, result: ToolCallResult, state: TurnState) -> None 处理工具成功、失败、替换或拒绝;给 result.result 赋值可以改变结果。

Bub 先注册内置 hooks,再注册已安装插件的入口点。普通实现优先级相同时,后注册的先运行。@hookimpl(tryfirst=True) 和 @hookimpl(trylast=True) 会改变优先级。这些标记与注册顺序共同决定哪个实现先运行。

HookRuntime 按 pluggy 的 get_hookimpls() 列表逆序,直接调用普通 hook 实现。这条分发路径不支持 pluggy 的 wrapper 标记(wrapper=True 和 hookwrapper=True)。

call_first 遇到第一个非 None 值就停止,call_many 收集所有实现的值。它们的同步版本会跳过 awaitables。每个实现只收到自己声明的参数。

bub hooks 按 pluggy 内部保存的顺序显示绑定,用于确认注册结果。执行时遵循上面的优先级规则。

build_prompt 可以返回包含字符串和 Republic 媒体的列表,媒体类型包括 republic.Image、Audio 和 Video。使用 republic.image(...)、republic.audio(...)、republic.video(...) 构造媒体。

旧格式的字符串和内容块字典仍可使用。接收提示词的 hooks(run_model、run_model_stream、continue_prompt、system_prompt)收到的是旧格式:没有媒体时是字符串,否则是带媒体 URL 的内容块。内联媒体使用 data URL。bub.prompt.to_content(prompt) 可以转换为用户内容。

framework.running() 先进入 lifespan providers,再获取会话存储和 steering inbox。退出时按相反顺序关闭已经进入的资源,发生错误时也是如此。执行环境缓存也会在作用域退出时关闭。

需要清理的 provider 可以 yield 一次,在 finally 中释放资源。资源生命周期应由这种方式管理;save_state 只覆盖准备完成后的模型阶段。

bub run 为一条消息打开作用域,bub chat 和 bub gateway 在应用运行期间保持作用域开启。嵌入应用的宿主也应这样做。作用域外的 get_tape_store() 返回 None。

内置 lifespan 管理在作用域中启动的后台 shell 进程。POSIX 上关闭进程组,Windows 当前关闭直接 shell 进程。脱离进程组的进程,以及应用被强制终止的情况,需要宿主层面的进程管理。

Tape sidecar providers 提供具名的关联 tape。Bub 把它们挂载到应用的根 Tape 上,fork、merge、archive 和 reset 都会处理已挂载的 sidecars。移除 provider 后,已保存的数据仍然保留。公共存储语义见 Tape 与上下文。

AgentPlugins.attach() 把 PluginAgentHooks 订阅到核心 agent 的 LLM 和工具开始、完成事件上;BubAgent 将它接到自身。应用插件通过这个适配器介入调用。

Before hooks 按 hook 优先级运行,每个实现都能看到前序实现的更新。LLMCallDecision.finish(text)、ToolCallDecision.replace(result) 和 ToolCallDecision.deny(message) 会停止后续 before 实现。LLM finish decision 不调用供应商,也没有对应的 after_llm_call。

适配器捕获每个拦截 hook 的普通异常,记录 hook.agent_hook_failed 后继续。需要拒绝调用时,返回 decision。直接注册到核心 EventBus 的 handler 会向上传播异常。

After hooks 观察成功调用和普通异常失败,取消与消费方关闭不会触发。after_tool_call 也会处理拒绝或替换的调用,各个实现共享同一个结果对象。修改 result.result 会改变向后传递的值,result.error 则保留原始失败信息。

载荷类型定义在 bub.core.events 中,并由 bub.hooks.interception 重新导出。run_id 将调用与记录条目关联起来。