01 · Hook 的四个时机
在 agent 执行的关键节点插入回调——before_tool、after_tool、after_edit、before_finish。
为什么需要 Hook
前 7 章中,agent 的执行流程是固定的:调用模型 → 执行工具 → 返回结果。如果要加一个"每次工具执行后记录日志"的功能,唯一的办法是修改 agent.ts 的核心代码。
这种"改核心代码"的方式有几个问题:
- 侵入性强。 加日志、加格式化、加验证——每个功能都要改 agent.ts。
- 难以组合。 日志功能和格式化功能可能互相影响,都在同一个函数里改,容易冲突。
- 不可配置。 有些用户需要日志,有些不需要。硬编码在 agent 里没法按需开关。
Hook 的思路是:不改 agent 核心代码,在关键节点插入外部回调。
agent 核心流程(不变)
├── before_tool hook → 工具执行前
├── 工具执行
├── after_tool hook → 工具执行后
├── after_edit hook → 文件编辑后
└── before_finish hook → 返回结果前agent 只管在正确的时机调用 hook,不关心 hook 做什么。hook 也不关心 agent 的内部逻辑,只处理自己负责的事。
四个 Hook 时机
before_tool
时机: 工具执行前。
用途: 拦截不需要的工具调用、修改工具参数。
interface BeforeToolContext {
tool: string; // 工具名称
args: Record<string, unknown>; // 工具参数
}
interface BeforeToolResult {
skip?: boolean; // 是否跳过执行
skipResult?: string; // 跳过时的替代结果
modifiedArgs?: Record<string, unknown>; // 修改后的参数
}返回 skip: true 时,工具不会执行。skipResult 作为替代结果返回给模型。
after_tool
时机: 工具执行后。
用途: 记录日志、统计信息、结果后处理。
interface AfterToolContext {
tool: string;
args: Record<string, unknown>;
result: string;
}after_edit
时机: 文件编辑(write_file / patch_file)后。
用途: 自动格式化、运行测试、验证修改。
interface AfterEditContext {
path: string; // 被编辑的文件路径
method: "write" | "patch";
result: string;
}这是 after_tool 的特化版本——只在文件编辑操作时触发,专门用于编辑后的自动化处理。
before_finish
时机: agent 返回结果前。
用途: 最终检查、汇总统计、清理资源。
interface BeforeFinishContext {
answer: string;
stats: { modelCalls: number; toolCallCount: number; hitLimit: boolean };
}HookManager 实现
在 src/hooks/manager.ts 中实现 hook 管理器:
export class HookManager {
private hooks = new Map<HookTiming, Hook[]>();
register(hook: Hook): void {
const list = this.hooks.get(hook.timing) ?? [];
list.push(hook);
this.hooks.set(hook.timing, list);
}
async executeBeforeTool(ctx: BeforeToolContext): Promise<BeforeToolResult> {
const hooks = this.getHooks("before_tool");
let lastResult: BeforeToolResult = {};
for (const hook of hooks) {
try {
const result = await hook.handler(ctx);
if (result?.skip) return result;
if (result?.modifiedArgs) ctx.args = result.modifiedArgs;
} catch (error) {
// hook 报错不中断主流程
}
}
return lastResult;
}
}设计要点:
按注册顺序执行。 先注册的 hook 先执行。多个 hook 形成一条"处理链"。
before_tool 可以中断链。 如果某个 hook 返回 skip: true,后续 hook 不再执行,直接返回。这类似于权限系统的"deny"——一票否决。
hook 报错不中断主流程。 单个 hook 的异常被捕获并记录,不会影响其他 hook 或 agent 的正常执行。这是关键的安全设计——hook 是"锦上添花"的功能,不应该因为 hook 出错导致 agent 崩溃。
和权限系统的区别
Hook 看起来和第 4 章的权限系统很像——都是在工具执行前后做检查。区别在于:
| 权限系统 | Hook | |
|---|---|---|
| 目的 | 安全控制 | 生命周期扩展 |
| 触发条件 | 所有工具调用 | 按需注册 |
| 配置方式 | PermissionRule 配置 | 注册 Hook 对象 |
| 拦截能力 | 拒绝执行 | 拒绝、修改参数、后处理 |
| 知识面 | 只关心"能不能执行" | 可以看到工具参数和结果 |
权限系统是"门卫"——决定能不能进。Hook 是"流水线"——在执行前后做各种处理。两者互补,不互相替代。
下一节讲解 after_edit 的典型应用——自动格式化。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。