Spring AI Alibaba Agent Framework Hooks 与 Interceptors 完整指南
基于 Spring AI Alibaba 1.1.2.0,深入理解 Agent 扩展机制,通过完整示例掌握 Hooks 和 Interceptors 的开发与测试
Spring AI Alibaba 记忆(Memory)机制详解与完整实战指南:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/162238591
一、引言
Spring AI Alibaba Agent Framework 提供了强大的扩展能力 ,允许开发者在 Agent 执行流程的任意节点插入自定义逻辑。这套扩展机制由 Hooks 和 Interceptors 两大组件构成,可用于监控、修改、控制 Agent 行为,实现诸如日志记录、消息修剪、上下文增强、工具重试、人机协同等高级功能。
二、核心概念对比
| 维度 | Hooks | Interceptors |
|---|---|---|
| 作用对象 | Agent 执行流程的特定节点(开始/结束、模型调用前后) | 模型请求/响应、工具调用 |
| 典型用例 | 消息修剪、压缩、PII 检测、人机协同 | 日志记录、重试、动态工具管理、上下文编辑 |
| 执行方式 | 异步(返回 CompletableFuture) |
同步(直接返回结果) |
| 优先级 | 先于 Interceptors 执行(在模型调用环节) | 在模型调用前后嵌套执行 |
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
三、内置 Hooks
3.1 SummarizationHook(消息压缩)
在接近 token 限制时自动压缩对话历史。
java
SummarizationHook hook = SummarizationHook.builder()
.model(chatModel)
.maxTokensBeforeSummary(4000)
.messagesToKeep(20)
.build();
3.2 HumanInTheLoopHook(人机协同)
在特定工具调用时暂停,等待人工审批或编辑。
java
HumanInTheLoopHook hook = HumanInTheLoopHook.builder()
.approvalOn("sendEmailTool", ToolConfig.builder()
.description("请确认是否发送该邮件。")
.build())
.build();
必须配合 Checkpointer(如
RedisSaver)使用以保存中断状态。
3.3 ModelCallLimitHook(调用次数限制)
限制模型调用次数,防止无限循环或过度消耗。
java
ModelCallLimitHook hook = ModelCallLimitHook.builder()
.runLimit(5)
.build();
3.4 PIIDetectionHook(PII 检测)
检测并处理敏感信息(如邮箱、手机号等)。
java
PIIDetectionHook hook = PIIDetectionHook.builder()
.piiType(PIIType.EMAIL)
.strategy(RedactionStrategy.REDACT)
.applyToInput(true)
.build();
四、内置 Interceptors
4.1 ToolRetryInterceptor(工具重试)
自动重试失败的工具调用。
java
ToolRetryInterceptor interceptor = ToolRetryInterceptor.builder()
.maxRetries(2)
.onFailure(OnFailureBehavior.RETURN_MESSAGE)
.build();
4.2 TodoListInterceptor(规划)
在工具执行前强制进行规划步骤。
java
TodoListInterceptor interceptor = TodoListInterceptor.builder().build();
4.3 ToolSelectionInterceptor(工具选择)
使用 LLM 在多个工具间智能选择。
java
ToolSelectionInterceptor interceptor = ToolSelectionInterceptor.builder().build();
4.4 ToolEmulatorInterceptor(工具模拟)
用 LLM 模拟工具输出,无需真实执行。
java
ToolEmulatorInterceptor interceptor = ToolEmulatorInterceptor.builder()
.model(chatModel)
.build();
4.5 ContextEditingInterceptor(上下文编辑)
在发送给 LLM 前修改上下文(如清理旧消息)。
java
ContextEditingInterceptor interceptor = ContextEditingInterceptor.builder()
.trigger(120000) // 触发阈值(毫秒)
.clearAtLeast(60000)
.build();
五、自定义 Hooks 开发
5.1 MessagesModelHook(推荐)
专门用于操作消息列表,使用简单。
java
@HookPositions({HookPosition.BEFORE_MODEL})
public class ContextEnhancementHook extends MessagesModelHook {
@Override
public String getName() {
return "context_enhancement";
}
@Override
public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
List<Message> enhanced = new ArrayList<>();
enhanced.add(new SystemMessage("你是一个专业的AI助手,请提供准确、有帮助的回答。"));
enhanced.addAll(previousMessages);
return new AgentCommand(enhanced, UpdatePolicy.REPLACE);
}
}
5.2 ModelHook(灵活)
可访问完整的状态数据。
java
@HookPositions({HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL})
public class LoggingModelHook extends ModelHook {
@Override
public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {
int size = ((List<?>) state.value("messages").orElse(List.of())).size();
log.info(">>> 调用模型,消息数: {}", size);
config.context().put("__start__", System.currentTimeMillis());
return CompletableFuture.completedFuture(Map.of());
}
@Override
public CompletableFuture<Map<String, Object>> afterModel(OverAllState state, RunnableConfig config) {
long duration = System.currentTimeMillis() - (long) config.context().get("__start__");
log.info("<<< 调用完成,耗时 {} ms", duration);
return CompletableFuture.completedFuture(Map.of());
}
}
5.3 AgentHook(Agent 级别)
在 Agent 整体执行前后介入。
java
@HookPositions({HookPosition.BEFORE_AGENT, HookPosition.AFTER_AGENT})
public class AgentMonitoringHook extends AgentHook {
private static final String COUNT_KEY = "__agent_call_count__";
@Override
public CompletableFuture<Map<String, Object>> beforeAgent(OverAllState state, RunnableConfig config) {
int count = (int) config.context().getOrDefault(COUNT_KEY, 0) + 1;
config.context().put(COUNT_KEY, count);
config.context().put("__agent_start__", System.currentTimeMillis());
log.info(">>> Agent 开始 (第{}次)", count);
return CompletableFuture.completedFuture(Map.of());
}
@Override
public CompletableFuture<Map<String, Object>> afterAgent(OverAllState state, RunnableConfig config) {
long duration = System.currentTimeMillis() - (long) config.context().get("__agent_start__");
int count = (int) config.context().get(COUNT_KEY);
log.info("<<< Agent 完成,本次耗时 {} ms,总调用次数: {}", duration, count);
return CompletableFuture.completedFuture(Map.of());
}
}
5.4 MessageTrimmingHook(修剪示例)
限制消息数量。
java
@HookPositions({HookPosition.BEFORE_MODEL})
public class MessageTrimmingHook extends MessagesModelHook {
private static final int MAX = 5;
@Override
public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
if (previousMessages.size() <= MAX) {
return new AgentCommand(previousMessages);
}
List<Message> trimmed = previousMessages.subList(previousMessages.size() - MAX, previousMessages.size());
return new AgentCommand(trimmed, UpdatePolicy.REPLACE);
}
}
六、自定义 Interceptors
6.1 ModelInterceptor
java
public class LoggingInterceptor extends ModelInterceptor {
@Override
public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
log.debug("模型请求含 {} 条消息", request.getMessages().size());
long start = System.currentTimeMillis();
ModelResponse response = handler.call(request);
log.debug("响应耗时 {} ms", System.currentTimeMillis() - start);
return response;
}
@Override
public String getName() {
return "LoggingInterceptor";
}
}
七、执行顺序
多个 Hooks 和 Interceptors 的执行顺序:
- Before Agent Hooks(按添加顺序)
- Agent 主循环开始
- Before Model Hooks(按添加顺序)
- Model Interceptors(嵌套调用,按添加顺序)
- Model 调用
- After Model Hooks(逆序)
- After Agent Hooks(逆序)
八、完整示例项目
8.1 项目结构
spring-ai-hooks-demo/
├── pom.xml
├── src/main/
│ ├── java/com/example/ai/
│ │ ├── SpringAiHooksDemoApplication.java
│ │ ├── config/
│ │ │ └── AgentConfig.java
│ │ ├── controller/
│ │ │ └── AgentController.java
│ │ ├── service/
│ │ │ └── AgentService.java
│ │ ├── hook/
│ │ │ ├── ContextEnhancementHook.java
│ │ │ ├── MessageTrimmingHook.java
│ │ │ ├── LoggingModelHook.java
│ │ │ └── AgentMonitoringHook.java
│ │ └── interceptor/
│ │ └── LoggingInterceptor.java
│ └── resources/
│ └── application.yml
8.2 pom.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<groupId>com.example.ai</groupId>
<artifactId>spring-ai-hooks-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<spring-ai-alibaba.version>1.1.2.0</spring-ai-alibaba.version>
<jackson.version>2.17.2</jackson.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
8.3 application.yml
yaml
server:
port: 885
spring:
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: deepseek-v4-flash # 或 qwen-max
logging:
level:
com.alibaba.cloud.ai: debug
com.example.ai: debug
8.4 关键 Java 文件
启动类 SpringAiHooksDemoApplication.java
java
package com.example.ai;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringAiHooksDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAiHooksDemoApplication.class, args);
}
}
Agent 配置 AgentConfig.java
java
package com.example.ai.config;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import com.example.ai.hook.*;
import com.example.ai.interceptor.LoggingInterceptor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AgentConfig {
@Bean
public ReactAgent reactAgent(ChatModel chatModel) {
return ReactAgent.builder()
.name("hook_demo_agent")
.model(chatModel)
.saver(new MemorySaver())
.hooks(
new AgentMonitoringHook(),
new ContextEnhancementHook(),
new MessageTrimmingHook(),
new LoggingModelHook()
)
.interceptors(new LoggingInterceptor())
.build();
}
}
其他 Hook 类(完整代码见上文第五节)
Service 和 Controller
java
// AgentService.java
package com.example.ai.service;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.stereotype.Service;
@Service
public class AgentService {
private final ReactAgent reactAgent;
public AgentService(ReactAgent reactAgent) { this.reactAgent = reactAgent; }
public String chat(String userMessage, String sessionId) {
RunnableConfig config = RunnableConfig.builder()
.threadId(sessionId)
.build();
AssistantMessage response = reactAgent.call(userMessage, config);
return response.getText();
}
}
java
// AgentController.java
package com.example.ai.controller;
import com.example.ai.service.AgentService;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/agent")
public class AgentController {
private final AgentService agentService;
public AgentController(AgentService agentService) { this.agentService = agentService; }
@PostMapping("/chat")
public Map<String, Object> chat(@RequestParam String message,
@RequestParam(defaultValue = "default") String sessionId) {
return Map.of("success", true,
"response", agentService.chat(message, sessionId),
"sessionId", sessionId);
}
}
九、测试与验证
9.1 手动测试步骤
-
启动应用 :确保
DASHSCOPE_API_KEY环境变量已设置,运行mvn spring-boot:run。 -
发送第一条消息:
bashcurl -X POST "http://localhost:885/api/agent/chat?message=你好&sessionId=test"预期日志:
>>> Agent 开始执行 (第 1 次调用) >>> 准备调用模型,当前消息数: 1 <<< 模型调用完成,耗时 XXX ms <<< Agent 执行完成,本次耗时 XXX ms,累计耗时 XXX ms,总调用次数: 1 -
连续发送 6 条以上消息(测试修剪):
bashfor i in {1..7}; do curl -X POST "http://localhost:885/api/agent/chat?message=消息$i&sessionId=test" done观察 :
当前消息数不应超过 5,说明MessageTrimmingHook生效。 -
会话隔离测试:
bashcurl -X POST "http://localhost:885/api/agent/chat?message=我叫张三&sessionId=A" curl -X POST "http://localhost:885/api/agent/chat?message=我叫李四&sessionId=B" curl -X POST "http://localhost:885/api/agent/chat?message=我叫什么名字?&sessionId=A" curl -X POST "http://localhost:885/api/agent/chat?message=我叫什么名字?&sessionId=B"预期:sessionA 返回"张三",sessionB 返回"李四",验证记忆隔离。
-
检查 Interceptor 日志(DEBUG 级别):
- 应输出
模型请求含 X 条消息和响应耗时 X ms。
- 应输出
-
验证系统提示注入 :
询问
你觉得你是一个什么样的助手?,回复应体现"专业""帮助"等关键词,说明ContextEnhancementHook已注入系统提示。
9.2 常见问题排查
| 现象 | 可能原因 | 解决 |
|---|---|---|
| Hook 日志未输出 | 日志级别未设为 debug | 检查 application.yml |
| 消息数始终为1 | 未配置 MemorySaver |
检查 AgentConfig 是否 .saver(new MemorySaver()) |
| 修剪未生效 | Hook 未添加或顺序错误 | 确认 MessageTrimmingHook 在 .hooks() 列表中 |
| 累计统计不递增 | context 中 key 不一致 |
检查所有读写使用相同 key(如 __agent_call_count__) |
十、总结
- Hooks 用于在 Agent 执行的关键点(开始/结束、模型前后)插入逻辑,适合监控、消息处理、人机协同等场景。
- Interceptors 用于更细粒度的拦截(模型请求/响应、工具调用),适合重试、日志、动态工具管理。
- MessagesModelHook 是消息处理的首选,
ModelHook可访问完整状态,AgentHook可用于全局统计。 - RunnableConfig.context() 是 Hook 间共享数据的桥梁,需注意 key 命名避免冲突。
- 通过组合多种 Hooks 和 Interceptors,可以实现对 Agent 行为的精细控制,而无需修改核心代码。
参考资源: