理解什么是Hook、为什么需要Hook、本项目的9种Hook类型与实战实现
Hook就像生活中的"插队点"——在某个流程执行到特定位置时,允许外部代码"插进来"做点事情。
Hook(钩子)是一种观察者模式的应用:系统在关键位置发布事件,注册的监听器(Hook)可以响应这些事件,执行自定义逻辑。
Hook模式的优势: ┌──────────────────────────────────────────────────┐ │ · 解耦: 核心流程不需要知道Hook的存在 │ │ · 可扩展: 新增功能只需添加Hook,不修改核心代码 │ │ · 可插拔: Hook可以随时注册/注销 │ │ · 顺序控制: 通过Order字段控制执行顺序 │ └──────────────────────────────────────────────────┘ Hook模式的代价: ┌──────────────────────────────────────────────────┐ │ · 调试困难: 逻辑分散在多个Hook中,不易追踪 │ │ · 执行顺序: 多个Hook的顺序可能影响结果 │ │ · 性能开销: 每个Hook点都有方法调用开销 │ │ · 错误传播: 一个Hook出错可能影响整个流程 │ └──────────────────────────────────────────────────┘ 常见Hook框架对比: ┌──────────────────────────────────────────────────┐ │ 框架 Hook类型 特点 │ │─────────────────────────────────────────────────│ │ Git Hooks pre-commit等 代码提交流程控制 │ │ React Hooks useState等 组件状态管理 │ │ Webpack tapable 构建流程插件 │ │ Spring @PostConstruct Bean生命周期 │ │ 本项目 AgentHook Agent执行生命周期 │ └──────────────────────────────────────────────────┘
Hook系统核心类:
┌────────────────────────────────────────────────────────┐
│ AgentHook 接口 │
│ · supportedTypes() → 监听哪些Hook类型 │
│ · getOrder() → 执行顺序 (越小越先执行) │
│ · isAsync() → 是否异步执行 │
│ · onHook(event) → Hook的实际逻辑 │
│ 返回 ALLOW / WARN / BLOCK │
└───────────────────────┬────────────────────────────────┘
│ @Component 自动注册
▼
┌────────────────────────────────────────────────────────┐
│ AgentHookPublisher (发布器) │
│ │
│ 构造函数注入 List<AgentHook> → 按 AgentHookType 分组 │
│ 每组按 order 排序 │
│ │
│ publish(type, event): │
│ for each hook in hooks[type]: ← 按order排序 │
│ if async → 提交到线程池,不阻塞 │
│ else → 同步调用 onHook() │
│ if result == BLOCK && isBeforeType: │
│ → 立即返回,后续Hook和主流程都不执行 │
└────────────────────────────────────────────────────────┘
支持类:
├── AgentHookType ← 9种Hook类型枚举
├── AgentHookEvent ← 事件数据(agentId, taskId, request, response...)
├── AgentHookResult ← 返回结果(ALLOW/WARN/BLOCK)
└── HookContext ← ConcurrentHashMap, Hook间传递数据
| # | Hook类型 | 触发时机 | 能阻断? | 触发位置 |
|---|---|---|---|---|
| 1 | ON_USER_MESSAGE 可阻断 | 用户消息预处理后,传给Agent前 | 是 | ConversationController |
| 2 | BEFORE_AGENT_EXECUTION 可阻断 | Agent执行前(熔断/限流之后) | 是 | AgentTaskOrchestrator |
| 3 | ON_SYSTEM_PROMPT | 系统提示词构建后,LLM调用前 | 否 | AbstractLlmAgent |
| 4 | BEFORE_LLM_CALL 可阻断 | 消息列表组装完成,LLM API调用前 | 是 | AbstractLlmAgent |
| 5 | AFTER_LLM_CALL | LLM调用返回后(仅非ReAct模式) | 否 | AbstractLlmAgent |
| 6 | ON_OUTPUT 未使用 | 输出发送给客户端前(预留) | 否 | 暂未实现 |
| 7 | AFTER_AGENT_EXECUTION | Agent执行成功完成后 | 否 | AgentTaskOrchestrator |
| 8 | ON_AGENT_ERROR | Agent执行失败时 | 否 | AgentTaskOrchestrator |
| 9 | ON_AGENT_TIMEOUT | Agent执行超时时 | 否 | AgentTaskOrchestrator |
Agent执行的完整流程 + Hook触发点:
用户发送消息
│
▼
┌─────────────────────────────────────────────┐
│ ① ON_USER_MESSAGE ← Hook点 1 │
│ 可以: 修改用户消息内容 / 阻断请求 │
│ 位置: ConversationController.chat() │
└──────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ AgentTaskOrchestrator.execute() │
│ ├── 熔断检查 │
│ ├── 限流检查 │
│ ├── ② BEFORE_AGENT_EXECUTION ← Hook点 2 │
│ │ 可以: 阻止Agent执行 / 修改请求 │
│ ├── 创建AgentTask │
│ └── 提交到线程池 │
└──────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ AbstractLlmAgent.chat() │
│ ├── 构建系统提示词 │
│ ├── 安全包装(PromptIsolation) │
│ ├── ③ ON_SYSTEM_PROMPT ← Hook点 3 │
│ │ 可以: 动态修改系统提示词 │
│ ├── 注入工具描述 │
│ ├── 组装消息列表 │
│ ├── ④ BEFORE_LLM_CALL ← Hook点 4 │
│ │ 可以: 修改消息列表 / 阻止LLM调用 │
│ ├── [LLM API调用] │
│ ├── ⑤ AFTER_LLM_CALL ← Hook点 5 │
│ │ 可以: 处理LLM响应 │
│ └── [ReAct循环 / 流式输出] │
└──────────────────┬──────────────────────────┘
▼
┌─────────────────────────────────────────────┐
│ 完成后: │
│ ├── 成功 → ⑦ AFTER_AGENT_EXECUTION (Hook点7)│
│ ├── 失败 → ⑧ ON_AGENT_ERROR (Hook点 8) │
│ └── 超时 → ⑨ ON_AGENT_TIMEOUT (Hook点 9) │
└─────────────────────────────────────────────┘
项目当前只有一个具体的Hook实现,用于安全审计:
SecurityEventHook
文件: hub-api/src/main/java/com/example/agenthub/api/security/SecurityEventHook.java
┌──────────────────────────────────────────────────┐
│ @Component │
│ public class SecurityEventHook implements AgentHook {│
│ │
│ 支持的Hook类型: │
│ ├── ON_USER_MESSAGE ← 记录用户消息 │
│ ├── ON_AGENT_ERROR ← 记录Agent错误 │
│ └── ON_AGENT_TIMEOUT ← 记录Agent超时 │
│ │
│ 执行顺序: 10 (很早执行) │
│ 是否异步: true (不阻塞主流程) │
│ │
│ 依赖: AuditLogService (审计日志服务) │
│ │
│ 实现逻辑: │
│ ├── ON_USER_MESSAGE: │
│ │ auditLogService.log(userId, agentId, │
│ │ message.substring(0, 200)) │
│ │ // 截取前200字符记录 │
│ │ │
│ ├── ON_AGENT_ERROR: │
│ │ auditLogService.log(userId, agentId, │
│ │ "ERROR: " + errorMessage) │
│ │ │
│ └── ON_AGENT_TIMEOUT: │
│ auditLogService.log(userId, agentId, │
│ "TIMEOUT after " + timeout + "s") │
└──────────────────────────────────────────────────┘
基于 Spring AI Alibaba 的 TelemetryHook,为每次 Agent 执行创建 OpenTelemetry 分布式追踪 Span:
TelemetryHook
文件: hub-agent-core/.../hook/TelemetryHook.java
┌──────────────────────────────────────────────────┐
│ @Component │
│ @ConditionalOnClass(name = "...Tracer") │
│ public class TelemetryHook implements AgentHook { │
│ │
│ 支持的Hook类型: │
│ ├── BEFORE_AGENT_EXECUTION ← 创建 Span │
│ ├── AFTER_AGENT_EXECUTION ← 标记成功,关闭 │
│ ├── ON_AGENT_ERROR ← 标记错误,关闭 │
│ └── ON_AGENT_TIMEOUT ← 标记超时,关闭 │
│ │
│ 执行顺序: 10 (高优先级) │
│ 是否异步: false │
│ │
│ Span 标签: │
│ ├── agent.id ← Agent 唯一标识 │
│ ├── task.id ← 任务 ID │
│ ├── conversation.id ← 会话 ID │
│ └── model.id ← 模型 ID │
│ │
│ 跨生命周期 Span 管理: │
│ ConcurrentHashMap<taskId, Span> activeSpans │
│ BEFORE 创建 → AFTER/ERROR/TIMEOUT 关闭 │
│ │
│ ON_AGENT_TIMEOUT 特殊处理: │
│ 防止超时场景下 Span 泄漏(永不关闭), │
│ 确保 Span 正确结束并标记 status=timeout │
└──────────────────────────────────────────────────┘
Hook返回值的三种结果:
AgentHookResult.ALLOW
┌──────────────────────────────────────┐
│ 继续执行,一切正常 │
│ 后续Hook和主流程继续 │
└──────────────────────────────────────┘
AgentHookResult.WARN("警告信息")
┌──────────────────────────────────────┐
│ 记录警告日志 │
│ 继续执行,不阻断 │
└──────────────────────────────────────┘
AgentHookResult.BLOCK("拒绝原因")
┌──────────────────────────────────────┐
│ 只有 BEFORE_* 和 ON_USER_MESSAGE │
│ 类型的Hook才能生效: │
│ │
│ · 立即停止执行后续Hook │
│ · 主流程也停止 │
│ · 返回错误响应给用户 │
│ │
│ 其他类型的BLOCK被忽略(只记录日志) │
└──────────────────────────────────────┘
阻断流程示例:
┌──────────────────────────────────────────────┐
│ Hook A (order=10): ALLOW ✅ │
│ Hook B (order=20): BLOCK("敏感内容") ❌ │
│ Hook C (order=30): (不执行) │
│ 主流程: (不执行) │
│ → 用户收到: "请求被拦截: 敏感内容" │
└──────────────────────────────────────────────┘
HookContext 预定义的键: ┌──────────────────────────────────────────────────┐ │ HookContext (ConcurrentHashMap) │ │ │ │ 标准键: │ │ ├── MODIFIED_MESSAGE ← ON_USER_MESSAGE用 │ │ │ Hook写入修改后的消息,Controller读取 │ │ │ │ │ ├── MODIFIED_SYSTEM_PROMPT ← ON_SYSTEM_PROMPT用 │ │ │ Hook写入修改后的提示词,Agent读取 │ │ │ │ │ ├── SKIP_AGENT ← BEFORE_AGENT用 │ │ │ Hook设置跳过Agent执行 │ │ │ │ │ └── CONFIDENCE_SCORE ← Pipeline用 │ │ Pipeline审核者写入置信度分数 │ │ │ │ 使用示例: │ │ // Hook中修改用户消息 │ │ event.getContext().put( │ │ HookContext.MODIFIED_MESSAGE, │ │ sanitizedMessage │ │ ); │ │ │ │ // Controller中读取修改后的消息 │ │ String msg = event.getContext() │ │ .get(HookContext.MODIFIED_MESSAGE); │ └──────────────────────────────────────────────────┘
// 示例: 添加一个内容审核Hook
// 文件: hub-api/src/main/java/com/example/agenthub/api/hook/ContentModerationHook.java
@Component
public class ContentModerationHook implements AgentHook {
@Override
public AgentHookType[] supportedTypes() {
// 监听用户消息,在执行前审核内容
return new AgentHookType[]{ AgentHookType.ON_USER_MESSAGE };
}
@Override
public int getOrder() {
return 20; // 在SecurityEventHook(10)之后执行
}
@Override
public boolean isAsync() {
return false; // 同步执行,可以阻断流程
}
@Override
public AgentHookResult onHook(AgentHookEvent event) {
String message = event.getRequest().getMessage();
// 检查是否包含违禁词
if (containsBannedContent(message)) {
return AgentHookResult.BLOCK("内容包含违禁信息,请求已拦截");
}
return AgentHookResult.ALLOW;
}
private boolean containsBannedContent(String text) {
// 实现内容审核逻辑...
return false;
}
}
// 就这么简单!@Component注解会自动注册到AgentHookPublisher
// 无需修改任何其他代码
| 机制 | 触发方式 | 能阻断? | 适用场景 | 本项目使用 |
|---|---|---|---|---|
| Hook | 事件驱动 | 是(BEFORE类型) | 审计、安全、监控 | SecurityEventHook |
| Filter | 请求拦截 | 是 | 认证、CORS、编码 | JwtAuthenticationFilter |
| Interceptor | 方法拦截 | 是 | 日志、事务 | Spring AOP |
| Listener | 事件监听 | 否 | 异步通知、统计 | DelayedResponseEvent |
| Plugin | 动态加载 | 否 | 运行时扩展Agent | PluginManager |
PiiDetectionHook 负责在 Agent 管道中自动检测并脱敏个人隐私信息(PII — Personally Identifiable Information)。它在输入侧和输出侧各设置一道扫描关卡,确保敏感数据不会泄露给 LLM 或最终用户。
| PII 类型 | 模式特征 | 正则示例 | 说明 |
|---|---|---|---|
| 身份证号 | 18 位数字 + 校验位 | \d{17}[\dXx] | 支持末位 X/x |
| 手机号 | 11 位,1 开头 | 1[3-9]\d{9} | 覆盖所有运营商号段 |
| 银行卡号 | 16-19 位数字 | \d{16,19} | Luhn 校验辅助判定 |
| 邮箱地址 | 标准 email 格式 | [\w.+-]+@[\w-]+\.[\w.]+ | 常见域名 |
| 中文姓名 | 2-4 个汉字 | [一-龥]{2,4} | 需上下文辅助判断 |
// PII 正则模式注册
public class PiiPatterns {
private static final Map<PiiType, Pattern> PATTERNS = Map.of(
PiiType.ID_CARD, Pattern.compile("(?<!\\d)(\\d{17}[\\dXx])(?!\\d)"),
PiiType.PHONE, Pattern.compile("(?<!\\d)(1[3-9]\\d{9})(?!\\d)"),
PiiType.BANK_CARD, Pattern.compile("(?<!\\d)(\\d{16,19})(?!\\d)"),
PiiType.EMAIL, Pattern.compile("([\\w.+-]+@[\\w-]+\\.[\\w.]+)"),
PiiType.NAME, Pattern.compile("(?<=姓名[::]\\s{0,2})([\\u4e00-\\u9fa5]{2,4})")
);
}
| 策略 | 效果示例 | 适用场景 |
|---|---|---|
| 掩码 (MASK) | 130****1234 / 3201**********1234 | 保留部分特征,便于用户核对 |
| 替换 (REPLACE) | [PHONE] / [ID_CARD] | 完全消除,适合发送给 LLM |
| 删除 (REMOVE) | 直接移除整段文本 | 高敏感场景 |
// 脱敏策略应用
public String sanitize(String text, PiiType type, MaskStrategy strategy) {
return switch (strategy) {
case MASK -> applyMask(text, type); // 130****1234
case REPLACE -> "[" + type.name() + "]"; // [PHONE]
case REMOVE -> ""; // 删除
};
}
PiiDetectionHook 同时注册了两个 Hook 类型:
ON_USER_MESSAGE — 输入侧扫描:在用户消息进入 Agent 前检测并脱敏ON_OUTPUT — 输出侧扫描:在 LLM 响应返回用户前检测并脱敏
PII 检测双关卡流程:
User Input User
│ ▲
▼ │
┌─────────────────────┐ ┌─────────────────────┐
│ ON_USER_MESSAGE │ │ ON_OUTPUT │
│ ┌─────────────────┐ │ │ ┌─────────────────┐ │
│ │ PII Scan │ │ │ │ PII Scan │ │
│ │ ├── 身份证检测 │ │ │ │ ├── 身份证检测 │ │
│ │ ├── 手机号检测 │ │ │ │ ├── 手机号检测 │ │
│ │ ├── 银行卡检测 │ │ │ │ ├── 银行卡检测 │ │
│ │ ├── 邮箱检测 │ │ │ │ ├── 邮箱检测 │ │
│ │ └── 姓名检测 │ │ │ │ └── 姓名检测 │ │
│ └────────┬────────┘ │ │ └────────┬────────┘ │
│ ▼ │ │ ▼ │
│ Mask / Replace │ │ Mask / Replace │
└──────────┬──────────┘ └──────────┬──────────┘
▼ │
Clean Input ──────▶ LLM ──────▶ Raw Output ──────────────┘
SummarizationHook 在对话历史 Token 数接近模型上下文窗口极限时,自动压缩历史消息。默认阈值为 80000 tokens,超出后触发摘要流程。
Token 超阈值时的消息处理策略:
原始消息列表 (超过 80000 tokens):
┌──────────────────────────────────────────────────┐
│ [0] System Prompt ← 始终保留 │
│ [1] User: 你好 │
│ [2] Assistant: 你好! │
│ [3] User: 帮我分析数据... ┐ │
│ [4] Assistant: 好的... │ │
│ [5] User: 继续深入分析... ├── 中间消息 → 摘要 │
│ [6] Assistant: 分析结果... │ │
│ [7] Tool Call: query_db... │ │
│ [8] Tool Result: {...} ┘ │
│ [9] User: 最新的问题 ┐ │
│ [10] Assistant: 最新回复 ├── 最近 N 条保留 │
│ [11] User: 当前问题 ┘ │
└──────────────────────────────────────────────────┘
压缩后:
┌──────────────────────────────────────────────────┐
│ [0] System Prompt ← 始终保留 │
│ [1] Summary: "用户请求数据分析,经过多轮讨论 │
│ 和数据库查询,得出了XXX结论..." ← LLM摘要 │
│ [2] User: 最新的问题 ← preserveRecent │
│ [3] Assistant: 最新回复 ← preserveRecent │
│ [4] User: 当前问题 ← preserveRecent │
└──────────────────────────────────────────────────┘
// 摘要 Prompt 模板
private static final String SUMMARIZE_PROMPT = """
请将以下对话历史压缩为一段简洁的摘要。要求:
1. 保留所有关键信息:用户意图、重要数据、工具调用结果、中间结论
2. 保留专有名词、数值、ID 等不可推断的信息
3. 使用第三人称描述("用户请求了..."、"系统返回了...")
4. 控制在 500 字以内
对话历史:
{messages}
""";
@Override
public AgentHookResult onHook(AgentHookEvent event) {
HookContext ctx = event.getContext();
// 防重入:同一轮 ReAct 循环中只摘要一次
if (ctx.containsKey(CTX_SUMMARIZED)) {
return AgentHookResult.ALLOW;
}
int tokenCount = tokenCounter.count(event.getMessages());
if (tokenCount < tokenThreshold) {
return AgentHookResult.ALLOW;
}
try {
String summary = chatModel.call(buildSummarizePrompt(event));
event.getMessages().replaceMiddle(summary, preserveRecentMessages);
ctx.put(CTX_SUMMARIZED, true);
} catch (Exception e) {
// Fallback: chatModel 不可用时,简单截断保留最近 N 条
log.warn("Summarization failed, falling back to truncation", e);
event.getMessages().truncateKeepRecent(preserveRecentMessages);
}
return AgentHookResult.ALLOW;
}
| 指标名 | 类型 | 说明 |
|---|---|---|
hook.summarization.triggered | Counter | 摘要触发次数 |
hook.summarization.tokens.before | Gauge | 摘要前 Token 数 |
hook.summarization.tokens.after | Gauge | 摘要后 Token 数 |
hook.summarization.fallback | Counter | 降级截断次数 |
hook.summarization.duration_ms | Timer | 摘要耗时 |
HumanInTheLoopHook 为敏感操作(如工具调用)提供人类审批环节,确保 Agent 不会自动执行高风险动作。
| 策略 | 行为 | 适用场景 |
|---|---|---|
| AUTO | 直接放行,无需审批 | 低风险工具(查询天气、搜索) |
| APPROVE | 暂停执行,等待人类审批 | 中高风险工具(发邮件、修改数据) |
| DENY | 直接拒绝,不允许执行 | 禁止工具(删除数据库、系统命令) |
# application.yml — 工具审批策略配置
agent:
human-in-the-loop:
enabled: true
default-policy: APPROVE # 默认策略:需要审批
timeout: 300s # 审批超时时间(默认 300 秒)
tool-policies:
weather_query: AUTO # 天气查询:自动放行
web_search: AUTO # 网页搜索:自动放行
send_email: APPROVE # 发送邮件:需要审批
execute_sql: APPROVE # 执行 SQL:需要审批
delete_record: DENY # 删除记录:直接拒绝
shell_command: DENY # Shell 命令:直接拒绝
HumanInTheLoop 审批流程:
Agent ReAct Loop
│
▼ BEFORE_TOOL_CALL
┌─────────────────────────────────────────────────────┐
│ HumanInTheLoopHook.onHook() │
│ │
│ toolName = event.getToolCall().getName() │
│ policy = toolPolicies.getOrDefault(toolName, │
│ defaultPolicy) │
│ │
│ switch(policy): │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ │
│ │ AUTO │ │ APPROVE │ │ DENY │ │
│ │ return │ │ 挂起执行 │ │ return │ │
│ │ ALLOW │ │ 保存状态 │ │ BLOCK │ │
│ └────┬────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
└────────┼────────────┼──────────────┼─────────────────┘
│ │ │
▼ ▼ ▼
继续执行 ┌──────────┐ 拒绝并返回
│ 前端审批 │ 错误信息
│ UI │
│ │
│ ✅ 批准 │──────▶ 恢复执行, 从 Checkpoint 继续
│ ❌ 拒绝 │──────▶ 返回"操作已被管理员拒绝"
│ ⏰ 超时 │──────▶ 返回"审批超时(300s)"
└──────────┘
// 审批等待核心逻辑
public AgentHookResult waitForApproval(String taskId, String toolName) {
// 1. 保存当前执行状态到 Checkpoint
Checkpoint checkpoint = checkpointService.save(taskId);
// 2. 发送审批请求到前端
approvalService.requestApproval(ApprovalRequest.builder()
.taskId(taskId)
.toolName(toolName)
.toolArgs(event.getToolCall().getArguments())
.timeout(timeout)
.build());
// 3. 阻塞等待审批结果(带超时)
ApprovalResult result = approvalService
.waitForResult(taskId, timeout);
return switch (result.getStatus()) {
case APPROVED -> AgentHookResult.ALLOW;
case REJECTED -> AgentHookResult.BLOCK("操作已被管理员拒绝");
case TIMEOUT -> AgentHookResult.BLOCK(
"审批超时(" + timeout.toSeconds() + "s),操作已取消");
};
}
Hook(本项目自定义机制)与 Interceptor(Spring AI Alibaba 拦截器链)在 Agent 执行过程中交替触发,共同构成完整的扩展点体系:
完整执行流程 — Hook 与 Interceptor 协作:
Request (用户请求)
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ ON_USER_MESSAGE (Hook) ← PII检测、内容审核 │
└──────────────────┬───────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────┐
│ BEFORE_AGENT_EXECUTION (Hook) ← 遥测Span创建、限流检查 │
└──────────────────┬───────────────────────────────────────────────────┘
▼
╔══════════════════════════════════════════════════════════════════════╗
║ ReAct Loop (循环执行) ║
║ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ ON_ITERATION_START (Hook) ← 摘要检查、Token统计 │ ║
║ └──────────────────┬───────────────────────────────────────────┘ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ Interceptor.beforeModel() ← 洋葱模型外层→内层 │ ║
║ └──────────────────┬───────────────────────────────────────────┘ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ BEFORE_LLM_CALL (Hook) ← 消息列表最终修改 │ ║
║ └──────────────────┬───────────────────────────────────────────┘ ║
║ ▼ ║
║ ┌─────────────────┐ ║
║ │ LLM 调用 │ ║
║ └────────┬────────┘ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ AFTER_LLM_CALL (Hook) ← 响应后处理 │ ║
║ └──────────────────┬───────────────────────────────────────────┘ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ Interceptor.afterModel() ← 洋葱模型内层→外层 │ ║
║ └──────────────────┬───────────────────────────────────────────┘ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ BEFORE_TOOL_CALL (Hook) ← 人机协同审批 │ ║
║ └──────────────────┬───────────────────────────────────────────┘ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ Interceptor.beforeTool() ← 工具调用前拦截 │ ║
║ └──────────────────┬───────────────────────────────────────────┘ ║
║ ▼ ║
║ ┌─────────────────┐ ║
║ │ Tool 执行 │ ║
║ └────────┬────────┘ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ Interceptor.afterTool() ← 工具结果后处理 │ ║
║ └──────────────────┬───────────────────────────────────────────┘ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ AFTER_TOOL_CALL (Hook) ← 工具结果审计 │ ║
║ └──────────────────┬───────────────────────────────────────────┘ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────┐ ║
║ │ ON_ITERATION_END (Hook) ← 循环结束统计 │ ║
║ └──────────────────┬───────────────────────────────────────────┘ ║
║ │ ║
║ ▼ (判断是否继续循环) ║
╚══════════════════════════════════════════════════════════════════════╝
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ AFTER_AGENT_EXECUTION (Hook) ← 遥测Span关闭、结果统计 │
└──────────────────┬───────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────┐
│ ON_OUTPUT (Hook) ← PII输出检测、内容过滤 │
└──────────────────┬───────────────────────────────────────────────────┘
▼
Response (返回用户)
supportedTypes()。
CTX_SUMMARIZED)防重复;设计 Hook 为幂等操作;使用 AtomicBoolean 等 CAS 操作避免并发重入。例如 SummarizationHook 在首次执行后设置标记,后续调用直接返回 ALLOW。对于审计类 Hook,重复写入不影响正确性(幂等天然成立)。
<code>、```);② 白名单机制——配置 exclude-patterns 排除已知安全内容;③ 置信度阈值——综合多个信号(正则匹配 + Luhn 校验 + 上下文),置信度 > 0.8 才脱敏;④ 用户可配置例外规则。
CTX_SUMMARIZED 标记防止同一轮 ReAct 循环中重复触发;preserveRecentMessages 保留最近 N 条消息不被摘要;只在 Token 超阈值(默认 80000)时触发;摘要 Prompt 明确要求保留关键信息(工具调用结果、中间结论、专有名词和数值)。
hookAsyncExecutor),异常被 catch 并记录日志但不中断管道。这是刻意设计——异步 Hook 适合遥测、审计日志等非关键路径操作。如果业务要求 Hook 失败时阻断主流程,必须使用同步 Hook(isAsync() = false)。
AgentHookResult 可携带 JumpTo 指令,Publisher 记录最后一个非 NONE 的跳转目标并最终返回给调度器。场景举例:PiiDetectionHook 检测到用户输入包含恶意注入指令 → 返回 JumpTo("safe_response_node") → Graph 跳过正常 Agent 节点,直接执行安全响应节点返回预设的拒绝消息。
KeyStrategy 机制:LAST_WINS(后执行的 Hook 覆盖前者)、MERGE_LIST(列表类型值自动合并)、MERGE_MAP(Map 类型值深度合并)。Hook 可在 getKeyStrategies() 方法中声明每个 key 的合并策略。未声明时默认 LAST_WINS。这确保了多个 Hook 协作修改状态时的行为可预测。