第 8 章 · Hooks 与生命周期扩展

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 的典型应用——自动格式化。

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page