Spring AI Alibaba 记忆(Memory)机制详解与完整实战指南

Spring AI Alibaba 记忆(Memory)机制详解与完整实战指南

基于 Spring AI Alibaba 1.1.2.0 的 Agent 框架,深入讲解短期记忆的原理、实现及生产级代码示例


一、引言

在构建智能对话 Agent 时,记忆能力是决定用户体验的关键因素。Spring AI Alibaba 的 Agent 框架提供了完善的短期记忆(Short‑term Memory)管理机制,能够让 Agent 在同一个会话中记住先前的交互内容。本文将从环境搭建开始,逐步讲解记忆的核心概念、配置方法、消息修剪策略,并给出一个可直接运行的完整项目示例,帮助你快速上手。


二、环境准备

2.1 技术栈

  • JDK 17+
  • Spring Boot 3.2.5
  • Spring AI Alibaba 1.1.2.0
  • DashScope 模型(如 qwen-max 或 deepseek‑v4‑flash)
  • Maven 3.6+

2.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-memory-demo</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <java.version>17</java.version>
        <spring-ai-alibaba.version>1.1.2.0</spring-ai-alibaba.version>
        <jackson.version>2.16.2</jackson.version>
    </properties>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring AI Alibaba Agent Framework -->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-agent-framework</artifactId>
            <version>${spring-ai-alibaba.version}</version>
        </dependency>

        <!-- Spring AI Alibaba DashScope Starter -->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
            <version>${spring-ai-alibaba.version}</version>
        </dependency>

        <!-- Jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.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

运行前请设置环境变量 DASHSCOPE_API_KEY 为你的 DashScope API 密钥。


注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

三、核心概念梳理

3.1 什么是短期记忆(Short‑term Memory)

短期记忆让应用程序能够在单个线程或会话 中记住先前的交互。会话可以隔离同一个 Agent 实例中的多个不同交互,类似于电子邮件在单个对话中分组消息。在 Spring AI Alibaba 中,短期记忆作为 Agent 状态(State) 的一部分进行管理,存储在 Graph 的状态中。

3.2 记忆管理机制

  • 持久化 :状态使用 checkpointer 持久化到数据库或内存,以便随时恢复线程。
  • 更新时机:记忆在调用 Agent 或完成步骤(如工具调用)时更新,并在每个步骤开始时读取状态。
  • 会话隔离 :通过 threadId 实现不同会话之间的记忆隔离。

3.3 上下文过长问题

保留所有对话历史是最常见的短期记忆实现方式,但长对话可能导致:

问题 影响
上下文窗口超限 LLM 有最大 token 限制,超限会导致错误或截断
模型表现下降 过时或偏离主题的内容会分散模型注意力
响应变慢 上下文越长,推理时间越长
Token 成本增加 每次请求都需要处理大量历史消息

为解决以上问题,Spring AI Alibaba 提供了多种消息处理 Hook,如修剪删除总结


四、记忆的使用方法

4.1 启用短期记忆(内存存储)

在创建 Agent 时指定 MemorySaver 作为 checkpointer:

java 复制代码
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import com.alibaba.cloud.ai.graph.RunnableConfig;

ReactAgent agent = ReactAgent.builder()
    .name("my_agent")
    .model(chatModel)
    .saver(new MemorySaver())   // 使用内存存储
    .build();

// 使用 thread_id 维护对话上下文
RunnableConfig config = RunnableConfig.builder()
    .threadId("session-001")
    .build();

agent.call("你好!我叫 Bob。", config);

4.2 生产环境:Redis Checkpointer

生产环境建议使用数据库支持的 checkpointer,例如 Redis:

java 复制代码
import com.alibaba.cloud.ai.graph.checkpoint.savers.RedisSaver;
import org.redisson.api.RedissonClient;

RedisSaver redisSaver = new RedisSaver(redissonClient);

ReactAgent agent = ReactAgent.builder()
    .name("my_agent")
    .model(chatModel)
    .saver(redisSaver)
    .build();

Redis 的配置将在后文扩展部分详细说明。

4.3 消息修剪(Message Trimming)

通过自定义 MessagesModelHook 在模型调用前修剪消息列表,保留关键信息:

java 复制代码
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;
import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook;
import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand;
import com.alibaba.cloud.ai.graph.agent.hook.messages.UpdatePolicy;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import org.springframework.ai.chat.messages.Message;
import java.util.ArrayList;
import java.util.List;

@HookPositions({HookPosition.BEFORE_MODEL})
public class MessageTrimmingHook extends MessagesModelHook {
    private static final int MAX_MESSAGES = 5;

    @Override
    public String getName() {
        return "message_trimming";
    }

    @Override
    public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
        if (previousMessages.size() <= MAX_MESSAGES) {
            return new AgentCommand(previousMessages);
        }
        // 保留第一条和最近三条
        Message firstMsg = previousMessages.get(0);
        List<Message> recentMessages = previousMessages.subList(
            previousMessages.size() - 3, 
            previousMessages.size()
        );
        List<Message> trimmedMessages = new ArrayList<>();
        trimmedMessages.add(firstMsg);
        trimmedMessages.addAll(recentMessages);
        return new AgentCommand(trimmedMessages, UpdatePolicy.REPLACE);
    }
}

4.4 消息删除(Message Deletion)

可删除最早的消息或清空全部:

java 复制代码
// 删除最早的两条消息
@HookPositions({HookPosition.AFTER_MODEL})
public class MessageDeletionHook extends MessagesModelHook {
    @Override
    public AgentCommand afterModel(List<Message> previousMessages, RunnableConfig config) {
        if (previousMessages.size() > 2) {
            List<Message> remaining = previousMessages.subList(2, previousMessages.size());
            return new AgentCommand(remaining, UpdatePolicy.REPLACE);
        }
        return new AgentCommand(previousMessages);
    }
}

4.5 消息总结(Message Summarization)

使用 LLM 对历史消息生成摘要,避免信息丢失:

java 复制代码
@HookPositions({HookPosition.BEFORE_MODEL})
public class MessageSummarizationHook extends MessagesModelHook {
    private final ChatModel summaryModel;
    private final int maxTokensBeforeSummary;
    private final int messagesToKeep;

    // 构造方法略

    @Override
    public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
        // 估算 token 数量,若超限则生成摘要
        int estimatedTokens = previousMessages.stream()
            .mapToInt(m -> m.getText().length() / 4)
            .sum();
        if (estimatedTokens < maxTokensBeforeSummary) {
            return new AgentCommand(previousMessages);
        }
        // 生成摘要并替换
        // ... 具体实现参考之前回答
    }
}

五、完整项目示例

5.1 项目目录结构

复制代码
spring-ai-memory-demo/
├── pom.xml
├── src/
│   └── main/
│       ├── java/
│       │   └── com/example/ai/
│       │       ├── SpringAiDemoApplication.java
│       │       ├── config/
│       │       │   └── AgentConfig.java
│       │       ├── controller/
│       │       │   └── AgentController.java
│       │       ├── service/
│       │       │   └── AgentService.java
│       │       └── hook/
│       │           └── MessageTrimmingHook.java
│       └── resources/
│           └── application.yml

5.2 启动类

java 复制代码
package com.example.ai;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringAiDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringAiDemoApplication.class, args);
    }
}

5.3 Agent 配置类

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.MessageTrimmingHook;
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("memory_agent")
                .model(chatModel)
                .saver(new MemorySaver())
                .hooks(new MessageTrimmingHook())
                .build();
    }
}

5.4 消息修剪 Hook

java 复制代码
package com.example.ai.hook;

import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;
import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand;
import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook;
import com.alibaba.cloud.ai.graph.agent.hook.messages.UpdatePolicy;
import org.springframework.ai.chat.messages.Message;

import java.util.ArrayList;
import java.util.List;

@HookPositions({HookPosition.BEFORE_MODEL})
public class MessageTrimmingHook extends MessagesModelHook {

    private static final int MAX_MESSAGES = 5;

    @Override
    public String getName() {
        return "message_trimming";
    }

    @Override
    public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
        if (previousMessages.size() <= MAX_MESSAGES) {
            return new AgentCommand(previousMessages);
        }

        Message firstMsg = previousMessages.get(0);
        int keepCount = 3;
        List<Message> recentMessages = previousMessages.subList(
                previousMessages.size() - keepCount,
                previousMessages.size()
        );

        List<Message> trimmedMessages = new ArrayList<>();
        trimmedMessages.add(firstMsg);
        trimmedMessages.addAll(recentMessages);

        return new AgentCommand(trimmedMessages, UpdatePolicy.REPLACE);
    }
}

5.5 Agent 服务层

java 复制代码
package com.badao.ai.service;


import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
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;
    }

    /**
     * 与 Agent 对话(带会话记忆)
     *
     * @param userMessage 用户消息
     * @param sessionId   会话 ID(用于记忆隔离)
     * @return Agent 回复
     */
    public String chat(String userMessage, String sessionId) {
        // 使用 threadId 维护对话上下文
        RunnableConfig config = RunnableConfig.builder()
                .threadId(sessionId)
                .build();

        AssistantMessage response = null;
        try {
            response = reactAgent.call(userMessage, config);
        } catch (GraphRunnerException e) {
            throw new RuntimeException(e);
        }
        return response.getText();
    }
}

5.6 REST 控制器

java 复制代码
package com.example.ai.controller;

import com.example.ai.service.AgentService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping("/api/agent")
public class AgentController {

    private final AgentService agentService;

    public AgentController(AgentService agentService) {
        this.agentService = agentService;
    }

    @PostMapping("/chat/session")
    public Map<String, Object> chatWithSession(
            @RequestParam String message,
            @RequestParam String sessionId) {

        String response = agentService.chat(message, sessionId);
        return Map.of(
                "success", true,
                "response", response,
                "sessionId", sessionId
        );
    }
}

六、运行与测试

6.1 启动应用

bash 复制代码
export DASHSCOPE_API_KEY="你的API密钥"
mvn clean spring-boot:run

6.2 测试多轮对话记忆

bash 复制代码
# 第一轮:告诉 Agent 名字
curl -X POST "http://localhost:885/api/agent/chat/session?message=你好,我叫张三&sessionId=test-001"

# 第二轮:询问名字(应能记住)
curl -X POST "http://localhost:885/api/agent/chat/session?message=我叫什么名字?&sessionId=test-001"

# 第三轮:新会话,记忆隔离
curl -X POST "http://localhost:885/api/agent/chat/session?message=我叫什么名字?&sessionId=test-002"

预期结果

  • 第一轮返回:"你好,张三!很高兴认识你。"

  • 第二轮返回:"你叫张三,你刚才告诉我的。"

  • 第三轮返回:"抱歉,我还不知道你的名字。"


七、生产环境扩展:使用 Redis 持久化

7.1 添加 Redisson 依赖

pom.xml 中增加:

xml 复制代码
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.27.0</version>
</dependency>

7.2 修改 AgentConfig

java 复制代码
@Configuration
public class AgentConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        return Redisson.create(config);
    }

    @Bean
    public ReactAgent reactAgent(ChatModel chatModel, RedissonClient redissonClient) {
        RedisSaver redisSaver = new RedisSaver(redissonClient);
        return ReactAgent.builder()
                .name("memory_agent")
                .model(chatModel)
                .saver(redisSaver)
                .hooks(new MessageTrimmingHook())
                .build();
    }
}

7.3 优势

  • 记忆不随应用重启丢失
  • 支持分布式部署,多实例共享记忆
  • 可通过 Redis 管理工具查看和清理会话状态

八、常见问题与最佳实践

8.1 如何选择修剪策略?

  • 消息数量过多 → 使用修剪 Hook,保留系统提示和最近 N 条。
  • 消息内容过长 → 使用总结 Hook,用 LLM 生成摘要。
  • 需要强制遗忘 → 使用删除 Hook 清除敏感或过时消息。

8.2 关于消息顺序的注意事项

某些 LLM 提供商(如 OpenAI)期望消息历史以 user 消息开头,且 assistant 的 tool 调用后必须紧跟对应的 tool 结果。修剪或删除时需保证消息序列有效,否则模型可能报错。

8.3 监控与日志

建议开启 com.alibaba.cloud.ai 的 DEBUG 级别日志,观察状态变化和 Hook 执行情况。


九、总结

本文全面讲解了 Spring AI Alibaba 中的短期记忆机制,从核心概念、配置方法到消息修剪策略,并给出了一套完整的可运行项目示例。通过 MemorySaver/RedisSaver 和自定义 MessagesModelHook,你可以轻松构建具备会话记忆能力的智能 Agent,同时有效控制上下文长度,保障性能和成本。

扩展方向

  • 结合工具(Tools)让 Agent 在记忆基础上执行操作
  • 使用 ModelInterceptor 实现动态提示注入
  • 探索长期记忆(Long‑term Memory)方案,如向量数据库

希望本文能帮助你快速掌握 Spring AI Alibaba 的记忆功能,并在实际项目中灵活运用。如有问题,欢迎查阅官方文档或参与社区讨论。