Spring AI Alibaba Agent Framework Hooks 与 Interceptors 完整指南

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 的执行顺序:

  1. Before Agent Hooks(按添加顺序)
  2. Agent 主循环开始
  3. Before Model Hooks(按添加顺序)
  4. Model Interceptors(嵌套调用,按添加顺序)
  5. Model 调用
  6. After Model Hooks(逆序)
  7. 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 手动测试步骤

  1. 启动应用 :确保 DASHSCOPE_API_KEY 环境变量已设置,运行 mvn spring-boot:run。

  2. 发送第一条消息:

    bash 复制代码
    curl -X POST "http://localhost:885/api/agent/chat?message=你好&sessionId=test"

    预期日志:

    复制代码
    >>> Agent 开始执行 (第 1 次调用)
    >>> 准备调用模型,当前消息数: 1
    <<< 模型调用完成,耗时 XXX ms
    <<< Agent 执行完成,本次耗时 XXX ms,累计耗时 XXX ms,总调用次数: 1
  3. 连续发送 6 条以上消息(测试修剪):

    bash 复制代码
    for i in {1..7}; do
      curl -X POST "http://localhost:885/api/agent/chat?message=消息$i&sessionId=test"
    done

    观察 :当前消息数 不应超过 5,说明 MessageTrimmingHook 生效。

  4. 会话隔离测试:

    bash 复制代码
    curl -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 返回"李四",验证记忆隔离。

  5. 检查 Interceptor 日志(DEBUG 级别):

    • 应输出 模型请求含 X 条消息 和 响应耗时 X ms。
  6. 验证系统提示注入 :

    询问 你觉得你是一个什么样的助手?,回复应体现"专业""帮助"等关键词,说明 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 行为的精细控制,而无需修改核心代码。

参考资源: