什么是Hook机制?
Hook(钩子)是一种在特定时刻插入自定义代码的机制。就像装修房子时的"预留点位"——你可以在这些点位安装任何设备。OpenClaw的Hook机制让你能够在Agent执行的关键节点插入自己的逻辑。
💡 类比理解:Hook就像是Agent生命周期中的"插件接口"——你可以在请求发送前、响应返回后、工具执行时、错误发生时等节点,插入自己的代码。
Hook类型全景图
生命周期钩子
| Hook名称 | 触发时机 | 用途 |
|---|---|---|
| onInit | Agent初始化时 | 加载配置、注册工具 |
| onRequest | 发送请求前 | 修改prompt、添加上下文 |
| onResponse | 收到响应后 | 后处理、格式化输出 |
| onToolCall | 工具调用前 | 参数校验、权限检查 |
| onToolResult | 工具执行后 | 结果处理、错误转换 |
| onError | 发生错误时 | 错误处理、降级策略 |
| onComplete | 任务完成时 | 清理资源、记录日志 |
Hook实战
请求拦截
在发送请求前修改prompt或添加上下文:
agent.hook('onRequest', async (context) => {
// 添加时间上下文
const now = new Date().toLocaleString('zh-CN');
context.prompt = `[当前时间: ${now}]\n\n${context.prompt}`;
// 检查敏感词
if (containsSensitiveWords(context.prompt)) {
throw new Error('Prompt包含敏感内容');
}
// 添加用户偏好
if (context.userPrefs) {
context.systemPrompt += `\n用户偏好: ${JSON.stringify(context.userPrefs)}`;
}
return context;
});
响应处理
在收到响应后进行后处理:
agent.hook('onResponse', async (response, context) => {
// 格式化输出
response.content = formatMarkdown(response.content);
// 添加来源水印
response.content += '\n\n---\n*由妙趣AI生成*';
// 内容安全检查
const safetyCheck = await checkContentSafety(response.content);
if (!safetyCheck.safe) {
response.content = '内容安全检查未通过,已隐藏';
response.flagged = true;
}
// 记录token使用
metrics.track('token_usage', {
input: response.usage.inputTokens,
output: response.usage.outputTokens,
model: context.model
});
return response;
});
工具调用拦截
最常用的Hook场景——控制工具执行:
agent.hook('onToolCall', async (toolCall) => {
// 参数校验
validateParams(toolCall.name, toolCall.arguments);
// 权限检查
if (!hasPermission(toolCall.name, context.user)) {
return {
error: '权限不足',
suggestion: '请联系管理员授权'
};
}
// 限流检查
if (isRateLimited(toolCall.name)) {
return {
error: '操作频率过高',
suggestion: '请稍后再试'
};
}
// 记录审计日志
audit.log({
user: context.user.id,
tool: toolCall.name,
params: toolCall.arguments,
timestamp: Date.now()
});
return toolCall; // 继续执行
});
工具结果处理
agent.hook('onToolResult', async (result, toolCall) => {
// 结果缓存
if (result.cacheable) {
cache.set(toolCall.cacheKey, result);
}
// 错误转换
if (result.error) {
result.error = translateError(result.error, 'zh-CN');
result.suggestion = suggestFix(result.error);
}
// 大数据截断
if (result.data && result.data.length > 10000) {
result.data = result.data.slice(0, 10000);
result.truncated = true;
result.note = '数据已截断,完整数据请另存';
}
return result;
});
中间件模式
链式中间件
OpenClaw支持Express风格的中间件链:
// 日志中间件
agent.use(async (context, next) => {
console.log(`[${new Date().toISOString()}] 请求开始`);
const result = await next();
console.log(`[${new Date().toISOString()}] 请求完成`);
return result;
});
// 限流中间件
agent.use(async (context, next) => {
if (await rateLimiter.isLimited(context.user.id)) {
throw new Error('请求频率过高');
}
await next();
await rateLimiter.increment(context.user.id);
});
// 计费中间件
agent.use(async (context, next) => {
const result = await next();
await billing.record({
user: context.user.id,
tokens: result.usage.totalTokens,
cost: calculateCost(result.usage)
});
return result;
});
中间件顺序
⚠️ 注意顺序:中间件按注册顺序执行。日志中间件应该在最前面(最先执行),计费中间件应该在最后面(最后执行)。
高级用法
条件Hook
// 只对特定工具触发
agent.hook('onToolCall', {
filter: (toolCall) => toolCall.name === 'send_email',
handler: async (toolCall) => {
// 只对邮件工具执行特殊处理
await checkSpamContent(toolCall.arguments.body);
return toolCall;
}
});
异步Hook
// 异步执行,不阻塞主流程
agent.hook('onComplete', async (context) => {
// 不等待异步任务完成,直接返回
backgroundTasks.add(async () => {
await sendAnalytics(context);
await updateUserStats(context.user.id);
});
return context;
});
Hook组合
// 组合多个Hook为中间件
const auditMiddleware = AuditMiddleware.create({
hooks: ['onRequest', 'onToolCall', 'onComplete'],
logLevel: 'info'
});
agent.use(auditMiddleware);
最佳实践
- ✅ Hook处理要快,避免阻塞主流程
- ✅ 使用条件Hook减少不必要的执行
- ✅ 错误处理Hook要返回清晰的错误信息
- ✅ 日志Hook记录关键信息,不过度记录
- ✅ 工具Hook校验参数后再执行
- ✅ 中间件按逻辑顺序注册
- ✅ 异步任务用后台执行,不阻塞
常见问题
Q: Hook可以修改请求内容吗?
可以。onRequest Hook可以修改prompt、添加system message、调整参数等。
Q: 多个Hook的执行顺序是什么?
按注册顺序执行,先注册先执行。可以使用priority参数控制优先级。
Q: Hook抛出错误会怎样?
会中断当前请求,触发onError Hook。可以在onError中降级处理或返回备用结果。