Hedera Hook Creation
用途
Hooks 是围绕 BaseTool 生命周期运行的非阻塞扩展。它们观察并可丰富执行过程:记录调用、写入审计轨迹、跟踪指标、在 Context 上持久化状态。它们不会阻止工具执行——那是 policies 的职责(见 agent-kit-policy 技能)。
Hooks 仅对扩展 BaseTool 的工具触发。直接实现 Tool 接口而不扩展 BaseTool 的工具会完全绕过 hook 系统。
何时编写 hook 与 policy
- 记录/审计/跟踪工具调用:Hook
- 丰富 Context(计数器、会话状态):Hook
- 当条件成立时阻止调用发生:Policy(见 agent-kit-policy)
- 拒绝特定工具方法:Policy
内置 hooks 包括 HcsAuditTrailHook 和 HolAuditTrailHook(基于 HCS 的审计轨迹),两者都位于 @hashgraph/hedera-agent-kit/hooks。
7 阶段生命周期
BaseTool.execute() 运行流程:
[1] preToolExecutionHook ← hook fires
[2] normalizeParams (tool internal)
[3] postParamsNormalizationHook ← hook fires
[4] coreAction (tool internal)
[5] postCoreActionHook ← hook fires
[6] secondaryAction (tool internal)
[7] postToolExecutionHook ← hook fires
Hooks 接入阶段 1、3、5、7。它们在阶段 2、4、6 从不触发。各阶段完整参数形态见 references/lifecycle.md。
AbstractHook 契约
import { AbstractHook } from '@hashgraph/hedera-agent-kit';
export abstract class AbstractHook {
abstract name: string; // human-readable identifier
abstract description?: string; // what the hook does
abstract relevantTools: string[]; // tool methods this hook cares about
// Override only the stages you need; defaults are no-ops.
preToolExecutionHook(params, method): Promise<any>;
postParamsNormalizationHook(params, method): Promise<any>;
postCoreActionHook(params, method): Promise<any>;
postToolExecutionHook(params, method): Promise<any>;
}
关键:方法签名是 (params, method)——params 是四种类型化对象之一,context 位于其中(params.context)。它不是单独的第一个参数。
| 阶段 | Params 类型 | 值得注意的字段 |
|---|---|---|
| 1 | PreToolExecutionParams | context, rawParams, client |
| 3 | PostParamsNormalizationParams | + normalisedParams |
| 5 | PostCoreActionParams | + coreActionResult |
| 7 | PostSecondaryActionParams | + toolResult |
骨架
import {
AbstractHook,
Context,
PreToolExecutionParams,
PostSecondaryActionParams,
} from '@hashgraph/hedera-agent-kit';
export class MyHook extends AbstractHook {
name = 'My Hook';
description = 'What this hook does, in one sentence';
relevantTools = ['transfer_hbar', 'create_token'];
async preToolExecutionHook(params: PreToolExecutionParams, method: string) {
if (!this.relevantTools.includes(method)) return;
// ... your pre-execution logic ...
// params.context, params.rawParams, params.client
}
async postToolExecutionHook(params: PostSecondaryActionParams, method: string) {
if (!this.relevantTools.includes(method)) return;
// params.toolResult is the final tool output
}
}
每个 hook 方法的第一行应按 relevantTools 进行过滤。工具包会为所有工具调用分派所有 hooks——除非你据此行动,否则 relevantTools 只是信息性的。
注册 hooks
Hooks 放在 Context.hooks 上。工具包将该 context 贯穿传递到 BaseTool.execute(),生命周期在那里分派它们。
import { AgentMode } from '@hashgraph/hedera-agent-kit';
import { allCorePlugins } from '@hashgraph/hedera-agent-kit/plugins';
import { HederaLangchainToolkit } from '@hashgraph/hedera-agent-kit-langchain';
import { MyHook } from './my-hook';
const toolkit = new HederaLangchainToolkit({
client,
configuration: {
plugins: allCorePlugins,
context: {
mode: AgentMode.AUTONOMOUS,
hooks: [new MyHook()],
},
},
});
多个 hooks 可以共存——它们在每个阶段按数组顺序运行。Policies(见 agent-kit-policy)也是 AbstractHook 实例,放在同一个数组中。
多工具类型模式
Hook 参数是泛型的(any)——工具包无法推断每个工具的类型,因为一个 hook 可以针对多个具有不同 schema 的工具。三种模式:
1. 通用逻辑
当 hook 与参数无关时(计数调用、将整个负载记录为 JSON):
async postToolExecutionHook(params: PostSecondaryActionParams, method: string) {
console.log(`[${method}]`, JSON.stringify(params.rawParams));
}
2. 类型守卫(少量已知集合)
对 method 进行 switch,用转换或守卫缩小范围:
async postParamsNormalizationHook(
params: PostParamsNormalizationParams,
method: string,
) {
switch (method) {
case 'transfer_hbar':
case 'transfer_hbar_with_allowance': {
const p = params.normalisedParams as { transfers: Array<{ amount: number }> };
const total = p.transfers.reduce((s, t) => s + t.amount, 0);
console.log(`[${method}] total HBAR: ${total}`);
break;
}
}
}
3. 策略映射(可插拔、第三方工具)
在构造函数中接受 Record<string, (params: any) => SomeResult>。验证每个 additionalTool 都有策略,缺失时在构造时抛出。这是 MaxRecipientsPolicy 使用的模式。
Context 上的状态
params.context 是整个 agent 运行期间相同的 Context 实例。将其用作按会话的暂存区:
async preToolExecutionHook(params: PreToolExecutionParams, method: string) {
if (!this.relevantTools.includes(method)) return;
const counts = ((params.context as any).callCounts ??= {} as Record<string, number>);
counts[method] = (counts[method] ?? 0) + 1;
}
(Context 是结构化类型的;转换以附加临时状态。如果你发布该 hook,请记录你写入的键。)
最佳实践
- 在每个方法顶部按 relevantTools 过滤——便宜且惯用
- 如果 hook 执行 I/O(HCS 写入、HTTP),用 try/catch 包裹——抛出的 hook 会中止整个工具调用
- 保持 hooks 轻量——它们位于热路径上
- 不要阻塞——如果需要停止执行,请编写 policy
- 除非确实有意为之,不要修改 rawParams 或 normalisedParams;后续 hooks 会看到你的更改
- 使用 description 记录副作用(HCS topic 写入、外部 API 调用),以便 policy 作者/agent 操作者知道该 hook 做什么
- 用
[<hook-name>]标记日志行以便于 grep
资源
- references/hook-interface.md——完整参数类型定义、全部四个 hook 阶段
- references/lifecycle.md——在 BaseTool 的 normalizeParams/coreAction/secondaryAction 周围何时触发什么
- examples/tx-timing-hook/——最小 hook,用于计时工具执行并记录持续时间
常见导入
import {
AbstractHook,
Context,
PreToolExecutionParams,
PostParamsNormalizationParams,
PostCoreActionParams,
PostSecondaryActionParams,
} from '@hashgraph/hedera-agent-kit';
// Built-in implementations live in the /hooks subpath
import { HcsAuditTrailHook, HolAuditTrailHook } from '@hashgraph/hedera-agent-kit/hooks';
限制
- Hooks 仅对扩展 BaseTool 的工具触发;直接实现 Tool 接口的工具会绕过 hook 系统。
- Hooks 为非阻塞扩展,不能阻止工具执行;如需阻止执行需使用 policies。
- Hook 参数为泛型,工具包无法推断每个工具的类型。