ZeroClaw 作为自主智能代理核心,Hook 是它的重要扩展点。本文带你拆透 Hook 框架设计、调用流程、配置开关、实战样例,并附 Mermeid 图方便理解。
1. Hook 设计目的
通过 Hook 能在“渠道消息 -> LLM -> 渠道回复”路径中插入策略/审计/安全逻辑,避免在主流程里硬编码。
用途示例:
- 过滤垃圾/违禁词
- 工具调用审计日志
- 模型路由和限流策略
- 业务和安全策略
- 发布/订阅事件监控
2. 模式:事件驱动 + 责任链
- 事件驱动(观察):
on_llm_input、on_llm_output、on_after_tool_call等 - 责任链(中间件):
on_message_received、before_llm_call、on_message_sending等 HookResult定义:Continue或Cancel,可截断或变更
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_logger、webhook_audit) - mod.rs:
ChannelRuntimeContext&process_channel_message
6. Hook 接口结构与默认行为
HookHandler 关键接口(可选方法均有默认实现):
- 事件型(不会改变流程)
on_gateway_start,on_gateway_stop,on_session_start,on_session_endon_llm_input,on_llm_output,on_after_tool_callon_message_sent,on_heartbeat_tick
- 修改型(顺序执行,支持取消)
before_model_resolve,before_prompt_build,before_llm_callbefore_tool_callon_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
- 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
}