15-06 AgentScope Java特性:Hook
✅AgentScope Java特性:Hook
就像Spring AI Alibaba一样,ASJ中也提供了完善的Hook机制,Hook 是一个事件拦截器链,横跨 ReActAgent 的”推理-行动-总结”全生命周期。他可以说是ASJ的一个基石。很多功能都要基于这个Hook机制来实现。
Hook 机制就是一套统一事件模型,允许开发者在 Agent 执行的各个阶段插入自定义逻辑,用于监控、拦截和修改 Agent 行为。所有 Hook 通过实现 Hook 接口来接入系统。
Hook 系统自 2.0.0 起已被标记为 @Deprecated(forRemoval = true),官方推荐迁移到 MiddlewareBase 中间件系统。但由于大量现有代码和扩展仍依赖 Hook,理解其工作原理仍然非常必要。
Hook机制介绍
Hook的接口定义如下:
1 | public interface Hook { |
onEvent:唯一的事件处理入口。泛型
意味着传入什么事件类型就返回什么类型——hook 可以修改事件内容但不能改变事件类型。返回 Mono 支持异步操作 **priority()**:数值越小越优先。框架按升序排列 hook,优先级相同则按注册顺序。官方建议分段:0-50 系统级(鉴权/安全);51-100 高优(验证/预处理);101-500 业务逻辑;501-1000 低优(日志/监控)。
HookEvent是所有Hook事件的基类。
1 | public abstract sealed class HookEvent permits PreCallEvent, PostCallEvent, ReasoningEvent, ActingEvent, SummaryEvent, ErrorEvent { |
sealed class:限定了事件家族的全部成员,编译器能检查 switch 穷举性。你不能随便扩展新事件类型——这是刻意的封闭设计。
所有事件共享 Agent agent:任何 hook 随时能拿到当前 Agent 实例(向上转型后访问 getAgentState()、getName()、toolkit 等)。
HookEventType 就是具体的事件枚举:
1 | public enum HookEventType { |
以上这些事件都会对应一个事件类,这些事件类都是HookEvent这个类的实现类。
| Column 1 | Column 2 | Column 3 | Column 4 |
|---|---|---|---|
| 事件类 | 时机 | 可修改? | 关键 setter / 能力 |
| PreCallEvent | agent.call() 开始 | Yes | setInputMessages setSystemMessage appendSystemContent |
| PostCallEvent | agent.call() 结束 | Yes | setFinalMessage |
| PreReasoningEvent | 每轮推理前 | Yes | setInputMessages setGenerateOptions appendSystemContent |
| PostReasoningEvent | 推理完成 | Yes | setReasoningMessage stopAgent() gotoReasoning(msgs) |
| ReasoningChunkEvent | 流式 token 到达 | No | getIncrementalChunk() getAccumulated() |
| PreActingEvent | 单个工具执行前 | Yes | setToolUse(ToolUseBlock) |
| PostActingEvent | 单个工具执行后 | Yes | setToolResult stopAgent() |
| ActingChunkEvent | 工具流式输出 | No | getChunk() |
| PreSummaryEvent | 超 maxIters 进入总结前 | Yes | setInputMessages setGenerateOptions |
| PostSummaryEvent | 总结完成 | Yes | setSummaryMessage |
| SummaryChunkEvent | 总结流式输出 | No | getIncrementalChunk() getAccumulated() |
| ErrorEvent | 出错 | No | getError() |
PostReasoningEvent.stopAgent() —— 调用后 Agent 立即返回当前消息,不执行工具。实现 human-in-the-loop:用户可以审查 LLM 打算调什么工具,确认后再 agent.call() 继续。
PostReasoningEvent.gotoReasoning(msgs) —— 跳过 acting 阶段,直接回到下一轮 reasoning。典型用途:StructuredOutputHook 发现 LLM 输出格式不对,构造一条 hint 消息塞进去要求重试。内部有 ToolValidator.validateToolResultMatch 校验——如果原始推理里有 ToolUseBlock,你塞的 msgs 里必须包含对应的 ToolResult,否则抛异常。
PostActingEvent.stopAgent() —— 类似 PostReasoning 的 stop,但触发在工具执行之后。适合”执行完了先让人看看结果再继续”的场景。
Hook的生命管理
AgentBase 是所有 Agent 的抽象基类,负责 Hook 的生命周期管理:
1 | public abstract class AgentBase implements StateModule, Agent { |
这里面的getSortedHooks,是后续Hook调度的关键方法。
Hook的调度
Hook的调度,主要在两个地方,一个是AgentBase中,一个是ReActAgent中。
在AgentBase中,主要负责PreCall / PostCall / Error的调度。在ReActAgent中,主要负责 Reasoning / Acting / Summary的调度。
AgentBase中的notifyPreCall:
1 | private Mono<List<Msg>> notifyPreCall(List<Msg> msgs) { |
ReActAgent中的notifyReasoningChunk
1 | private Mono<Void> notifyReasoningChunk(Msg chunkMsg, ReasoningContext context) { |
把Hook注册到ReActAgent
想要把Hook注册到ReActAgent中也很简单,支持一次性注册多个Hook,也支持一次性注册单个Hook:
1 | ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .toolkit(toolkit) .hooks(List.of( new LoggingHook(), new HighPriorityHook(), new PromptEnhancingHook() )) .build(); |
1 | ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(model) .toolkit(toolkit) .hook(new LoggingHook()) .build(); |
内置Hook
在ASJ中,也有一些内置Hook可以直接用使用,或者说不是开发者使用,而是ASJ中的其他功能和机制依赖这些Hook实现。
StreamingHook — 流式事件转发
将 Agent 内部事件转换为 Event 对象并推送到 FluxSink,用于 AgentBase.stream() 方法的实现。它会拦截 PostReasoningEvent、ReasoningChunkEvent、PostActingEvent、ActingChunkEvent、PostSummaryEvent、SummaryChunkEvent,并根据 StreamOptions 的配置决定是否将事件发射出去。
特点:
由框架在调用 stream() 时自动创建和注册
调用结束后自动从 Hook 列表中移除
支持增量模式和累积模式
StructuredOutputHook — 结构化输出控制
确保模型在结构化输出模式下正确调用 generate_response 工具。
工作流程:
PreReasoningEvent:在 TOOL_CHOICE 模式下,强制设置 tool_choice 为 generate_response
PostReasoningEvent:检查模型是否调用了目标工具,如果没有则添加提醒消息并重新推理(最多重试 3 次)
PostActingEvent:当 generate_response 成功完成后,调用 stopAgent()
PostCallEvent:压缩记忆上下文,移除中间结构化输出相关消息
SkillHook — 技能目录注入
在 PreReasoningEvent 时将技能目录提示词注入到系统消息中。
1 | public class SkillHook implements Hook { |
这个是ASJ中Skill的实现重要Hook
✅AgentScope Java进阶:Skill
Skill之前我们有专门的讲过,包括Spring Ai Alibaba也介绍过他的支持Skill的原理。 ASJ当然也是支持Skill的,并且支持的要比SAA好。 数据模型 前面我们介绍过skill的结构,在ASJ中对应的就是AgentSk
LLMentor
StaticLongTermMemoryHook — 静态长期记忆
实现 STATIC_CONTROL 模式的长期记忆自动管理。
工作流程:
PreCallEvent:提取最后一条用户消息作为查询,从长期记忆中检索相关内容,将结果包装在 <long_term_memory> 标签中注入到消息列表末尾
PostCallEvent:将对话记录异步保存到长期记忆
这个是ASJ中长期记忆的实现重要Hook
✅AgentScope Java特性:长期记忆
(虽然在ASJ的2.0的relesse note中提到:RAG (Knowledge / KnowledgeRetrievalTools / RAGMode) and long-term memory modules deprecated
LLMentor
GenericRAGHook — 通用 RAG 检索
在每次推理前自动从知识库检索相关知识并注入到提示中。
工作流程:
PreCallEvent:提取最后一条用户消息作为查询
调用 knowledge.retrieve(query, config) 检索相关文档
将检索结果格式化为 <retrieved_knowledge> 标签包裹的内容,作为用户消息追加到输入列表
这个是ASJ中RAG的实现重要Hook
✅AgentScope Java特性:RAG
(虽然在ASJ的2.0的relesse note中提到:RAG (Knowledge / KnowledgeRetrievalTools / RAGMode) and long-term memory modules deprecated
LLMentor
自定义Hook
如果内置的Hook不满足诉求,可以自定义一个Hook,只需要实现Hook接口,然后把他注册到ReActAgent中就好了,如:
1 | import io.agentscope.core.hook.Hook; |
还可以通过设计优先级调整执行顺序:
1 | public class AuthHook implements Hook { |
