Spring AI 2.0:Memory

一、简介

大模型是没有记忆的,Spring AI提供了聊天记录的持久化织入器,Spring AI 提供了多种持久化方式,支持内存(默认InMemoryChatMemoryRepository)、Redis、MongoDB、ES、其它数据库等。

二、ChatMemory

所谓记忆就是将历史之前的所有提示出和大模型返回的结果都作为下次的输入。

java 复制代码
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
String conversationId = "xs001";        // 当前对话唯一标识

// 第一次对话(将请求和响应添加到chatMemory中)
UserMessage userMessage1 = new UserMessage("我叫张三");
chatMemory.add(conversationId, userMessage1);
ChatResponse response1 = chatModel.call(new Prompt(chatMemory.get(conversationId)));
chatMemory.add(conversationId, response1.getResult().getOutput());

// 第二次对话
UserMessage userMessage2 = new UserMessage("我是谁?");
chatMemory.add(conversationId, userMessage2);
ChatResponse response2 = chatModel.call(new Prompt(chatMemory.get(conversationId)));
chatMemory.add(conversationId, response2.getResult().getOutput());

System.out.println(response2.getResult().getOutput().getText());

InMemoryChatMemoryRepository

默认存到 Map<String, List<Message>> chatMemoryStore = new ConcurrentHashMap<>(); 中,Map的key表示conversationId用于区分用户的sessionId(conversationId如果没有听默认为default),当数据多的时候会出现内存溢出OOM,只适合简单Demo一下。

MessageWindowChatMemory

每个大模型的上下文长度都是有最大限制的,窗口式会话内存,保存对话历史,可以设置最大消息条数(默认20),超过就丢弃最早消息。

RedisChatMemoryRepository

Redis相比Jdbc会更快。

xml 复制代码
<properties>
    <jedis.version>5.2.0</jedis.version>
</properties>

<!-- Spring AI Alibaba Redis聊天记忆starter -->
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- Jedis Redis客户端 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>${jedis.version}</version>
</dependency>
yaml 复制代码
spring:
  ai:
    memory:
      redis:
        host: 127.0.0.1
        port: 6379
        password: ""
        timeout: 2000
        # key前缀,redis里面的key = spring:ai:memory:{sessionId}
        key-prefix: spring:ai:memory
java 复制代码
@Bean
    ChatMemory chatMemory(RedisChatMemoryRepository redisChatMemoryRepository) {
        return MessageWindowChatMemory.builder()
                .maxMessages(10)  // 窗口大小,保留最近10条消息
                .chatMemoryRepository(redisChatMemoryRepository)
                .build();
    }

JdbcChatMemoryRepository

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/ai_chat?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 你的密码
    driver-class-name: com.mysql.cj.jdbc.Driver

默认使用schema‑mysql.sql会创建该表。

sql 复制代码
CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
    `conversation_id` VARCHAR(36) NOT NULL,
    `content` TEXT NOT NULL,
    `type` VARCHAR(10) NOT NULL,
    `timestamp` TIMESTAMP NOT NULL,
    CONSTRAINT SPRING_AI_CHAT_MEMORY_TYPE_CHECK CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'))
);

CREATE INDEX IF NOT EXISTS SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX
ON SPRING_AI_CHAT_MEMORY (conversation_id, `timestamp`);

ElasticsearchChatMemoryRepository

bash 复制代码
<!-- spring‑ai‑alibaba ES chat memory 官方starter -->
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-memory-elasticsearch</artifactId>
    <version>1.0.0.2</version>
</dependency>

<!-- Spring Data Elasticsearch 底层驱动 -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-elasticsearch</artifactId>
</dependency>
yaml 复制代码
spring:
  elasticsearch:
    uris: http://127.0.0.1:9200
    #username: elastic
    #password: xxx

  ai:
    chat:
      memory:
        repository:
          elasticsearch:
            index-name: spring-ai-alibaba-chat-memory # ES索引名称,自动创建
java 复制代码
@Bean
public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
    return MessageWindowChatMemory.builder()
            .maxMessages(10)
            .chatMemoryRepository(chatMemoryRepository)
            .build();
}

@Bean
public RedisChatMemoryRepository chatMemoryRepository(RedisTemplate<String, Object> redisTemplate) {
    return RedisChatMemoryRepository.builder()
            .redisTemplate(redisTemplate)
            .build();
}

@Bean
ChatMemory chatMemory(JdbcChatMemoryRepository chatMemoryRepository) {
    return MessageWindowChatMemory.builder()
            .maxMessages(10)
            .chatMemoryRepository(chatMemoryRepository)
            .build();
}

/**
* 直接注入alibaba官方ES ChatMemoryRepository,自动读取yml配置
*/
@Bean
public ChatMemory chatMemory(ElasticsearchChatMemoryRepository esChatMemoryRepository) {
   return MessageWindowChatMemory.builder()
           .chatMemoryRepository(esChatMemoryRepository)
           .maxMessages(20) //内存窗口上限,ES中完整保存全部历史消息
           .build();
}

三、代码

java 复制代码
@Configuration
public class SpringAiConfig {
	
    @Bean
    public ChatClient chatClient(DeepSeekChatModel deepSeekChatModel, ChatMemory chatMemory) {
        return ChatClient.builder(deepSeekChatModel)
			.defaultAdvisors(
				MessageChatMemoryAdvisor.builder(chatMemory).build()
			)
        	.build();
    }
}
java 复制代码
@RestController
public class SpringAIController {

    @Autowired
    private ChatClient chatClient;

    @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chat(@RequestParam("message") String message) {
        Flux<String> content = chatClient.prompt()
                .user(message)
                // 消息持久化需要一个user_id
                .advasors(a -> a.param(ChatMemory.CONVERSATION_ID, "zhangsan"))
                .stream()
                .content()
                .withConcat(Flux.just("[complete]"));  // 自定义一个结束标记,前端解析到就不会再请求,表示本轮会话全部结束
        return content;
    }
	
	@GetMapping("/history")
    public List<Message> getHistory(@RequestParam String sid) {
        return chatMemory.get(sid);
    }

    @GetMapping("/clear")
    public void clear(@RequestParam String sid) {
        chatMemory.clear(sid);
    }
}

先告诉大模型我叫什么。

再问大模型我叫什么

AI多层次记忆架构(模仿人类记忆)

整体分为近期记忆、中期记忆、长期记忆三层,正好对应Spring AI里面不同组件。

1. 近期记忆(Short‑term)

保留上下文窗口最近N轮对话,每轮对话结束立刻存储,对应 ChatMemory

  • 实现:MessageWindowChatMemory,底层存储Redis/MySQL(JdbcChatMemoryRepository / RedisChatMemoryRepository)
  • 特点:
    1. 直接放在prompt上下文传给大模型;
    2. 有窗口上限(maxMessages,例如10条),超过就丢弃最早消息;
    3. 只能记住最近几轮,老对话直接被裁剪掉
  • 局限:会话轮次一多,早期历史会丢失。

2. 中期记忆(Middle‑term)

RAG向量检索历史对话,解决"近期记忆窗口不够"的痛点

  • 流程:每轮对话结束后,异步把本轮对话文本转向量,存入向量数据库
  • 推理阶段:用户提问时,把用户问题向量化,去向量库检索相似的旧对话,把检索出来的历史拼入prompt。
  • 特点:
    • 不受LLM上下文窗口限制,可以检索很久之前对话;
    • 缺点:是相似度召回,不一定能命中真正需要的内容
    • 存储:向量数据库(Milvus、DashScope向量库等)。

对比:近期记忆是全部塞进prompt ;中期记忆是检索挑选一部分塞进prompt

3. 长期记忆(Long‑term)

提炼固化关键事实、用户偏好,不是原始聊天记录,是高度摘要信息。

不存完整对话,只存结构化要点:比如用户年龄、喜好、习惯、重要事实。

两种实现方案:

方式① 定时批处理

  • 定时任务(每日/每周)批量读取该用户全部对话;
  • 调用LLM做总结,提取关键信息、用户偏好;
  • 写入数据库作为长期记忆;
  • 优点:计算集中、成本低,适合大量用户;
  • 缺点:不能实时感知用户刚说的新信息,有延迟

方式② 关键点实时处理(写入触发器)

  • 每一轮对话,额外调用一次LLM,识别当前消息是否包含关键信息:
    用户个人信息、偏好、永久性设置;
  • 如果识别到,立刻提取、更新长期记忆库
  • 优点:实时生效,说完立刻记住;
  • 缺点:每轮多一次LLM调用,增加token开销。

三层在请求链路中的执行顺序

  1. 用户提问;
  2. 长期记忆读出用户关键偏好,放到System Prompt;
  3. 用用户问题去向量库检索中期记忆(历史相关对话片段),加入prompt;
  4. 取出近期记忆(最近N轮完整对话),拼入消息列表;
  5. 合并全部内容发给大模型;
  6. 模型返回结果;
  7. 写回:保存本轮对话到近期记忆;异步写入向量库(中期记忆);触发长期记忆更新逻辑(批处理/实时触发器)。

三者对比总结

记忆层级 存储内容 存储介质 SpringAI对应组件
近期记忆 原始最近N轮消息 Redis/MySQL ChatMemoryMessageWindowChatMemory
中期记忆 全部历史对话向量 向量数据库 RAG向量检索
长期记忆 结构化摘要、用户偏好 MySQL/Redis 自己开发(SpringAI无内置组件)

重点:Spring AI只原生提供近期记忆 ;中期记忆靠RAG;长期记忆需要自己开发,没有现成starter

落地选型建议

  1. 简单对话机器人:只用近期记忆(Redis ChatMemory),够用;
  2. 需要记住几千轮历史:加中期记忆RAG向量库
  3. 需要记住用户永久偏好(记住用户姓名、喜好):再实现一层长期记忆
相关推荐
码匠许师傅1 小时前
【C++ 面试真题】聊聊 C++ 的移动语义与右值引用
java·c++·面试
万少1 小时前
给 DeepSeek Harness 装个"应用商店":一条命令,595 个插件随你逛
前端·javascript·后端
小席是个热心肠2 小时前
AI相关的自我学习
java·人工智能·学习
Java成神之路-4 小时前
Spring Cloud 微服务契约先行:为什么 Controller 建议要实现 Feign 接口?
spring·spring cloud·微服务
数智化管理手记4 小时前
海量数据如何沉淀有效数据资产?数据标准化治理方案如何搭建?
java·大数据·人工智能
cfm_29144 小时前
SpringAI + Ollama 本地大模型
java·开发语言·人工智能·语言模型
鲸采云SRM采购管理系统5 小时前
采购管理系统哪家好?2026最新测评对比
java·服务器·数据库
IT_陈寒5 小时前
Python的GIL让我多写了500行代码
前端·人工智能·后端
0x536 小时前
Java网络编程(二)
java·服务器·网络