一文看懂ZeroClaw 的 Hook 机制解析

0 阅读2分钟

ZeroClaw 作为自主智能代理核心,Hook 是它的重要扩展点。本文带你拆透 Hook 框架设计、调用流程、配置开关、实战样例,并附 Mermeid 图方便理解。


1. Hook 设计目的

通过 Hook 能在“渠道消息 -> LLM -> 渠道回复”路径中插入策略/审计/安全逻辑,避免在主流程里硬编码。

用途示例:

  • 过滤垃圾/违禁词
  • 工具调用审计日志
  • 模型路由和限流策略
  • 业务和安全策略
  • 发布/订阅事件监控

2. 模式:事件驱动 + 责任链

  • 事件驱动(观察):on_llm_inputon_llm_outputon_after_tool_call
  • 责任链(中间件):on_message_receivedbefore_llm_callon_message_sending
  • HookResult 定义:ContinueCancel,可截断或变更

3. 框架组件图(Mermaid)

flowchart TB
  A[ChannelRuntimeContext] --> B[HookRunner]
  B --> C[HookHandler]
  C --> C1[CommandLoggerHook]
  C --> C2[WebhookAuditHook]
  B --> D[process_channel_message]
  D --> E[run_tool_call_loop / Agent]
  E --> F[Provider]
  E --> G[Tools]
  D --> H[Channel.send]
  • ChannelRuntimeContext 生成时创建 HookRunner(config中配置 hooks.enabled=true)。
  • HookRunner 管理所有 hook,按 priority() 排序。
  • 运行流程中多个节点会调用 hook。

4. 事件调用流程(Mermaid 顺序图)

sequenceDiagram
  participant Channel as Channel
  participant Dispatcher as run_message_dispatch_loop
  participant Processor as process_channel_message
  participant Hooks as HookRunner
  participant Agent as run_tool_call_loop
  participant ChannelOut as Channel.send
  Channel ->> Dispatcher: tx.send(ChannelMessage)
  Dispatcher ->> Processor: 执行 worker(msg)
  Processor ->> Hooks: on_message_received(msg)
  alt Cancel
    Hooks -->> Processor: Cancel
    Processor -->> ChannelOut: 结束(不发送)
  else Continue
    Hooks -->> Processor: Continue(msg')
    Processor ->> Processor: 额外预处理(media/link/live)
    Processor ->> Hooks: before_llm_call(history, model)
    Processor ->> Agent: run_tool_call_loop
    Agent -->> Processor: LLM / tool 结果
    Processor ->> Hooks: on_after_tool_call/on_llm_output
    Processor ->> Hooks: on_message_sending(channel, recipient, content)
    Hooks -->> Processor: Continue(final_content)
    Processor ->> ChannelOut: send(final_content)
  end

5. 核心源码位置

  • traits.rs:HookHandler + 方法默认实现 + HookResult
  • runner.rs:HookRunner 注册与触发
  • builtin:内置实现(command_loggerwebhook_audit
  • mod.rs:ChannelRuntimeContext & process_channel_message

6. Hook 接口结构与默认行为

HookHandler 关键接口(可选方法均有默认实现):

  • 事件型(不会改变流程)
    • on_gateway_start, on_gateway_stop, on_session_start, on_session_end
    • on_llm_input, on_llm_output, on_after_tool_call
    • on_message_sent, on_heartbeat_tick
  • 修改型(顺序执行,支持取消)
    • before_model_resolve, before_prompt_build, before_llm_call
    • before_tool_call
    • on_message_received, on_message_sending

HookResult

  • Continue(T):继续执行,带可能修改值
  • Cancel(reason):取消后续处理(在某些阶段可中断)

7. 重要流程节点(源码中)

  • process_channel_message 中:

    • 入站:hooks.run_on_message_received(msg)
    • LLM 前:before_llm_call
    • tool 后:on_after_tool_call
    • LLM 输出:on_llm_output
    • 出站:on_message_sending
  • 组件交互

    • Channel 收消息后 => run_message_dispatch_loop
    • 并发/并行管理 (Semaphore, /stop)
    • 结果回复到 channel

8. 配置开关

~/.zeroclaw/config.toml 主要字段:

[hooks]
enabled = true

[hooks.builtin]
command_logger = false

[hooks.builtin.webhook_audit]
enabled = false
url = ""
tool_patterns = []
include_args = false
max_args_bytes = 4096

ZeroClaw 初始化时:

  • if config.hooks.enabled → 创建 HookRunner
  • 注册内置 hook(command_logger / webhook_audit
  • 可继续注册自定义 hook

9. 自定义 Hook 例子

9.1 新文件:src/hooks/builtin/my_echo_hook.rs

use async_trait::async_trait;
use crate::hooks::traits::{HookHandler, HookResult};
use crate::channels::traits::ChannelMessage;
use crate::providers::traits::{ChatMessage, ChatResponse};
use crate::tools::traits::ToolResult;
use std::time::Duration;

pub struct MyEchoHook;

impl MyEchoHook {
    pub fn new() -> Self { Self }
}

#[async_trait]
impl HookHandler for MyEchoHook {
    fn name(&self) -> &str { "my_echo_hook" }
    fn priority(&self) -> i32 { 10 }

    async fn on_message_received(&self, mut message: ChannelMessage) -> HookResult<ChannelMessage> {
        if message.content.to_lowercase().contains("hello") {
            message.content = format!("{} [hooked]", message.content);
        }
        HookResult::Continue(message)
    }

    async fn on_message_sending(
        &self, channel: String, recipient: String, content: String
    ) -> HookResult<(String, String, String)> {
        HookResult::Continue((channel, recipient, format!("[EchoHook] {}", content)))
    }

    async fn on_llm_output(&self, response: &ChatResponse) {
        tracing::info!("MyEchoHook saw LLM output, len={}", response.text().len());
    }
}

9.2 注入 ChannelRuntimeContext

mod.rs 有段:

if config.hooks.enabled {
    let mut runner = crate::hooks::HookRunner::new();
    if config.hooks.builtin.command_logger {
        runner.register(Box::new(crate::hooks::builtin::CommandLoggerHook::new()));
    }
    if config.hooks.builtin.webhook_audit.enabled {
        runner.register(Box::new(crate::hooks::builtin::WebhookAuditHook::new(
            config.hooks.builtin.webhook_audit.clone(),
        )));
    }
    runner.register(Box::new(crate::hooks::builtin::my_echo_hook::MyEchoHook::new()));  // 新增
    Some(Arc::new(runner))
} else {
    None
}