正文
“挂在循环上, 不写进循环里” — 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() 不再关心“有哪些额外动作”,它只负责在关键节点喊一声:“现在到付款前了,谁要处理自己来。”