嗨,我是 Lena。上周我看着一个 Agent 处理工具调用时,隐约觉得哪里有点不对。
那次调用失败了。Agent 只是继续往下走,没有重试,没有日志,什么都没有。它像是什么都没发生一样继续执行。
我盯着这个问题想了一会儿。不是因为它崩了,而是因为我找不到一个明显的位置,去放那段本该把它拦下来的逻辑。
也是从那时起,我开始更仔细地看 hooks。
什么是 Agent Hook?
agent hook 是 Agent 执行生命周期中的一个预定义拦截点,可以在这里插入自定义逻辑。它不会改写 Agent 的核心推理循环,而是挂在一些明确的时点上,比如工具调用前、模型响应后,或错误浮现时,让你在不改动 Agent 本体的前提下,在那里做点什么。
如果你已经在用 Agent 框架开发,这大概是你熟悉的一种模式,只是你可能还没给它起这个名字。你也许写过在模型调用前注入上下文的逻辑,或者在输出向下游传播前先拦截处理的逻辑。那就是 hook。
Pre-hooks、Post-hooks 和 Error Hooks
不同框架的叫法会有差异,但整体结构是稳定的。
Pre-hooks 在某一步执行前运行。它们可以检查或修改输入,比如注入认证头、附加上下文、验证必填字段是否存在。比如,before_model hook 可以先检查对话是否已经过长,并在模型看到它之前先行截断。
Post-hooks 在某一步完成后运行。它们会拿到输出,并可以对其做转换、记录日志,或者触发下游动作。比如,after_tool_call hook 可以在 Agent 继续之前,把工具返回值先记录下来。
Error hooks 是我觉得最值得观察的一类。它们会在执行链中的某个环节失败时触发。可能是工具超时,API 返回 429,或者模型产出的结果没有通过校验。只要系统里真的有这层设计,重试逻辑、回退路由和优雅降级通常就住在这里。
Hooks 和 Middleware 有什么不同
这个区别很微妙,而很多框架对这两个术语的使用也并不总是一致,这点不止一次把我搞糊涂过。
Middleware 包裹的是整段执行,或者其中某个大的层级。请求进入后,会穿过一层层 middleware,再从另一侧出来。Hooks 则更细粒度。它们挂在执行周期里的特定命名事件上,而不是把整个流程包起来。
在 LangChain 的 Agent 架构里,wrap_model_call 的行为更像 middleware,因为它包裹的是模型调用本身。而 before_tool_call 和 after_tool_call 则是更狭义上的 hook:它们在特定时点触发,但不做包裹。LangChain agent documentation 把这两种模式放在一起介绍,这很符合它们在实践中的用法,即便概念上并不完全相同。
Hooks 常见的使用位置
拦截工具调用
这大概是我最常见到 hooks 落地的地方。工具调用是 Agent 与外部系统交互的入口,比如 API、数据库、文件系统。而外部系统的失败方式,往往不是 Agent 自己就能优雅处理好的。
before_tool_call hook 可以在请求发出前先验证输入。wrap_tool_call hook 可以捕获失败并用退避策略重试。after_tool_call hook 可以在 Agent 处理结果之前,先把不一致的返回结构统一掉。
如果没有这些,工具失败通常会悄无声息地一路往下传。
Memory 读写 Hooks
Memory 操作也是 hooks 很能放大价值的一个位置。在读取 memory 之前,hook 可以根据当前任务上下文过滤出该暴露什么内容。在写入 memory 之后,hook 可以把那些看起来与现有记录不一致的条目标记出来。
Microsoft agent pipeline architecture 通过 AIContextProviders 采用了类似思路。这些可注入组件会在每次 LLM 调用前运行,用来丰富消息历史。它没有明确把这个机制叫作 hook,但模式是一模一样的:在一个明确的执行点插入可组合、可替换的自定义逻辑。
输出校验 Hooks
就我目前的观察,模型输出后的校验 hooks 其实被低估了。Agent 生成了一段响应,可它真的符合预期 schema 吗?里面是否有超出可接受范围的值?把它继续传给下游是否安全?
如果能有一个校验 hook 在这些问题向下传播前先把它们拦住,成本会比事后排查低得多。
设计不打断 Agent 流程的 Hooks
Stateless Hooks vs. Stateful Hooks
Stateless hook 只读取输入并产出输出,不依赖也不修改自己作用域之外的内容。它是可预测的。Stateful hook 则会读写共享状态,这会引入执行顺序依赖和潜在的竞态条件,尤其是在多 Agent 或并行执行场景里。
Stateless hooks 更容易测试、更容易推理,也更安全地组合。 Stateful hooks 有时确实不可避免,但它们往往也是我在 hook 系统里遇到那些微妙 bug 的来源。LangGraph's extensibility patterns 建议把状态变更显式化,并限制在清晰的作用域内,以避免这一类问题。
控制副作用
如果一个 hook 会调用外部 API、写数据库,或者触发通知,那它就是带副作用的 hook。副作用本身并不是错,但它需要被收束起来:边界要清楚,尽可能做到幂等,并且最好与核心执行路径隔离开,这样 hook 自己失败时,问题才不会级联到 Agent 上。
这也是我在实践里至今仍觉得有些模糊的部分。“Agent 自己的逻辑”与“hook 可以做的事”之间的边界,其实并不总是那么好定义。
值得复用的 Hook 模式
处理工具失败的重试 Hooks
重试 hook 会包裹一次工具调用,并在特定失败条件下重新执行,比如超时、速率限制错误,或临时网络问题。这里真正关键的设计点是:哪些错误值得重试,最多重试多少次,以及采用什么样的退避策略。
LangChain middleware 协议里的 wrap_tool_call 做的正是这件事。它通过 handler callback 模式,让每一层 middleware 都能拿到内层执行的引用,因此可以在不修改工具本身的前提下实现重试逻辑。
用于执行日志的审计 Hooks
审计 hook 会在每个关键步骤之后触发,并记录发生了什么:调用了哪个工具、返回了什么、耗时多久、是否成功。时间久了,这份日志会变成你在生产环境中排查 Agent 行为的核心工件。
Microsoft Agent Governance Toolkit 把这件事做成了一整道治理闸门:每一次工具调用、每一份输出、每一次 Agent 之间的交互,都会在执行前经过策略引擎,并留下审计痕迹。这是同一底层模式的一种更强约束版本。
面向 API 调用的速率限制 Hooks
速率限制 hook 会跟踪对外 API 调用,并在接近阈值时引入延迟或排队。这个想法本身不复杂,但往往要等 Agent 工作流真的在规模上碰到限制时,大家才意识到前期投入不够。
放置位置很关键。hook 挂得太早,可能会过度限流;挂得太晚,调用已经发出去了。对工具调用使用 pre-hook,通常才是放置速率限制逻辑的正确插入点:既足够接近调用本身,判断更准确,也足够早,还来得及干预。
局限与权衡
Hooks 的确解决了一个真实问题:在不重写核心逻辑的前提下,把额外逻辑注入执行链。但与此同时,它们也带来了新的复杂度表面。
执行顺序会变得重要。 Hooks 往往按固定顺序运行,比如所有 before hooks 依次执行,然后是调用本身,再由 after hooks 逆序执行。当多个 hook 修改同一份状态时,执行先后会直接影响结果,而这点并不总是显而易见。
Hooks 可能悄悄吞掉错误。 一个 error hook 如果捕获并处理了失败,可能会让 Agent 自己根本不知道刚才发生过失败。这到底是不是正确行为,完全取决于具体上下文,而且非常容易做错。
调试一整条 hook 链,比调试线性代码难得多。 当行为不符合预期时,原因可能藏在组合 hook 栈的任意一层里。可观测性不是生产环境里的可选项,而是必需品:trace、结构化日志、显式的 hook ID,一个都不能少。OpenTelemetry specification 在这里很值得理解;如今大多数生产级 Agent 框架都会按这个格式发出 trace,而 hooks 往往正是提供这些观测信息的那一层。
我还在继续摸索:hooks 到底在哪些地方是合适的抽象,在哪些地方又会把事情变得太重。那条线并不是固定的。
FAQ
- AI 里的 Agent Hook 是什么?
所谓 agent hook,是在 Agent 执行周期某个特定阶段插入自定义逻辑的点,比如工具调用前、模型响应后,或者错误发生时。Hooks 让开发者可以在不改动 Agent 核心循环的前提下,拦截、检查、修改或扩展 Agent 的行为。LangChain、LangGraph 和 Microsoft Agent Framework 等主流框架都支持这一模式。
- Hook 和 Agent 里的 Middleware 有什么区别?
Middleware 通常包裹的是整层执行,就像一个每个请求都要经过的 pipeline decorator。Hooks 则更细,它们附着在执行周期里的具体命名事件上。实践里,很多框架两种都会用:模型调用用 middleware 风格的 wrapper,before_tool_call 或 after_model 这样的离散事件则用 hook 风格的 listener。两者的边界在实际工程里往往会变模糊。
- Pre-hooks 和 Post-hooks 在 AI Agent 执行中是如何工作的?
Pre-hooks 在某一步执行前运行,可以修改输入、注入上下文,或根据条件阻止执行。Post-hooks 在某一步完成后运行,它们会拿到输出,并可以记录日志、做校验,或者触发下游动作。执行顺序是固定的:所有 pre-hooks 依次触发,然后执行步骤本身,再由 post-hooks 逆序触发。这和 Web 服务器 middleware 的 request/response 周期非常相似。
- 不修改 Agent 核心,怎样给它加入自定义逻辑?
标准做法是把 hooks 或 middleware 注册到 Agent 的执行 pipeline 里。在 LangChain 里,这通常意味着把 middleware 对象或被装饰的函数传给 create_agent。在 Microsoft Agent Framework 里,则是用 .Use() decorators 或 AIContextProviders。不同框架写法不同,但原则是一致的:定义拦截点,写入逻辑,然后注册它。Agent 的核心循环不需要动。
我会继续观察 hook 模式在生产系统里怎么演化。一个本来就被设计成“尽量不可见”的机制,其实很有意思:你通常只有在它失效时,或者在它本来应该存在却缺席、结果别的东西先出问题时,才会真正注意到它。




