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 执行流程的任意节点插入自定义逻辑。这套扩展机制由 HooksInterceptors 两大组件构成,可用于监控、修改、控制 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 行为的精细控制,而无需修改核心代码。

参考资源