文件名 Hook(钩子).md

Hook(钩子)

本文目录

正文

“挂在循环上, 不写进循环里” — hook 在工具执行前后注入扩展逻辑。

Hook是Harness中的重要一环,不写进循环,以扩展的形式注入循环中。

名词解释

Hook 这个词可以理解成: 钩子 / 挂钩 / 插入点 / 扩展点

最直观的翻译是:钩子。但不要把它想成“钓鱼钩”,而要把它想成:

程序运行到某个关键位置时,提前留好的一个“可以挂东西的点”。

hook 在agnet_loop里是什么意思?

Agent 的主循环大概是:

:::color1 用户输入 → 调用 LLM → LLM 决定是否调用工具 → 工具执行前 → 执行工具

															       ↓

                               把结果交回 LLM   ←  工具执行后

:::

** 这里面有几个关键时刻,这些地方就可以留 hook:**

:::color1 用户输入之后

工具执行之前

工具执行之后

Agent 准备停止之前

:::


比如:

  trigger_hooks("PreToolUse", block)
#   意思是:
# 现在马上要执行工具了,所有注册在 PreToolUse 这个位置的 hook,都可以出来干活了。

在项目里,hook 的本质是什么?

** 本质上就是:某个事件发生时,自动调用一批提前注册好的函数。**

也就是:

  HOOKS = {
      "PreToolUse": [],
      "PostToolUse": [],
  }

注册一个 hook:

  def log_hook(block):
      print("即将执行工具:", block.name)
  register_hook("PreToolUse", log_hook)

触发 hook:

  trigger_hooks("PreToolUse", block)

完整意思是:

把 log_hook 这个函数挂到 PreToolUse 这个事件上。

以后每次执行工具前,就自动调用 log_hook。

动机

如果我们想给agent_loop额外添加几项功能,如 记录每次bash调用、记录操作后自动 git add,那么,每次新增功能都要修改 agent_loop 函数。

循环很快就变成了这样:

def agent_loop(messages):
    while True:
        # ... LLM call ...
        for block in response.content:
            if block.type != "tool_use":
                continue
            log_to_file(block)          # 加一行
            check_permission(block)     # 加一行
            notify_slack(block)         # 又加一行
            output = execute(block)
            auto_git_add(block)         # 再加一行
            # ... 很快循环就认不出来了

hook构建

为了解决上述问题,故引入hook。

“hook 不写进循环里”不是说循环里完全没有 hook 相关代码,而是说具体扩展逻辑不写进循环里。

循环只保留一个很薄的“挂点”:

  blocked = trigger_hooks("PreToolUse", block)

真正的逻辑,比如权限检查、日志记录、大输出提醒,都写在外面的 hook 函数里,再注册进去:

  register_hook("PreToolUse", permission_hook)
  register_hook("PreToolUse", log_hook)
  register_hook("PostToolUse", large_output_hook)

让循环只认识事件,不认识具体扩展:

  def agent_loop(messages):
      while True:
          response = call_llm(messages)
          for block in response.content:
              if block.type != "tool_use":
                  continue
              blocked = trigger_hooks("PreToolUse", block)
              if blocked:
                  continue
              output = execute_tool(block)
              trigger_hooks("PostToolUse", block, output)

这时要加日志,不改循环:

  def log_hook(block):
      print("准备执行工具:", block.name)
  register_hook("PreToolUse", log_hook)

要加权限检查,也不改循环:

  def permission_hook(block):
      if block.name == "bash" and "rm -rf" in block.input["command"]:
          return "禁止危险命令"
      return None
  register_hook("PreToolUse", permission_hook)

要加执行后的检查,还是不改循环:

  def large_output_hook(block, output):
  register_hook("PostToolUse", large_output_hook)
      if len(output) > 100000:
          print("输出太大了")

Hook示例-点外卖

一个更生活化的例子:假设你有一个“点外卖流程”。

侵入式写法:

  def order_food():
      choose_restaurant()
      check_coupon()
      check_balance()
      log_order()
      notify_friend()
      pay()
      send_receipt()

后来每多一个需求,都要改 order_food()。

hook 写法:

  def order_food():
      choose_restaurant()
      trigger_hooks("BeforePay")
      pay()
      trigger_hooks("AfterPay")

外面想加什么就注册什么:

  register_hook("BeforePay", check_coupon)
  register_hook("BeforePay", check_balance)
  register_hook("BeforePay", log_order)

  register_hook("AfterPay", send_receipt)
  register_hook("AfterPay", notify_friend)

order_food() 不再关心“有哪些额外动作”,它只负责在关键节点喊一声:“现在到付款前了,谁要处理自己来。”