Spring AI Alibaba入门学习(五)

一、什么是Chat Memory?

Chat Memory是指AI模型在对话过程中能够"记住"之前交流内容的能力,使得多轮对话保持上下文连贯.

为什么需要Chat Memory?

大语言模型(LLM)本质上是"无状态"的------每次调用都是独立的请求,模型不会自动记住之前的对话内容。如果不加记忆,就会出现这种情况:

复制代码
用户:我叫张三
AI:你好,张三!

用户:我叫什么名字?
AI:抱歉,我不知道你的名字,你能告诉我吗?  ❌ 尴尬了,刚才明明说过

而有了Chat Memory,AI就能基于完整对话历史生成响应,实现真正的连贯对话。

重要概念区分

聊天记忆和聊天历史的区分:

Spring AI的ChatMemory抽象旨在管理聊天内存,但不适合存储完整的聊天历史。如果需要保存所有消息,应该使用Spring Data等其他方案。

二、Chat Memory的核心机制

2.1 记忆的存储结构

Spring AI通过两层抽象来实现聊天记忆 :

  • ChatMemory :高层接口,决定保留哪些消息 以及何时删除

  • ChatMemoryRepository :底层存储,负责实际的消息存取

这种分离设计让你可以灵活组合不同的记忆策略和存储介质。

2.2 记忆类型(Memory Type)

Spring AI提供了多种内置的记忆类型 :

2.2.1 Message Window(消息窗口)

复制代码
MessageWindowChatMemory memory = MessageWindowChatMemory.builder()
    .maxMessages(10)  // 保留最近10条消息
    .build();

工作原理 :维护一个固定大小的滑动窗口,超过限制时自动淘汰最旧的消息 -4。这是Spring AI默认的记忆类型。

特点:

  • 简单直观,适合快速原型开发

  • 系统消息(SystemMessage)会被特殊保留,不会被淘汰

  • 消息是不可分割的单位

2.2.2 Token Window(令牌窗口)

复制代码
// 需要Tokenizer来统计token数
TokenWindowChatMemory memory = TokenWindowChatMemory.builder()
    .maxTokens(2000)  // 保留最近2000个token
    .tokenizer(tokenizer)
    .build();

工作原理:以token数为单位进行滑动窗口,更适合精确控制上下文窗口。

特点:

  • 更精确地控制token使用量

  • 消息仍作为整体保留或淘汰

  • 需要配合Tokenizer使用

2.3 记忆的特殊处理规则

Chat Memory对特定类型的消息有特殊处理逻辑,这些规则确保记忆内容对LLM始终是有效和可用的:

三、如何实现连续对话

在Spring AI Alibaba中,实现连续对话的核心是**Advisor机制** ,特别是MessageChatMemoryAdvisor

3.1 工作原理示意图

复制代码
用户请求 ──→ [Advisor Chain] ──→ LLM ──→ 响应
                 ↑                          ↓
            ┌─────┴──────┐            ┌─────┴──────┐
            │ before逻辑  │            │ after逻辑  │
            │ 1. 获取历史 │            │ 1. 保存响应│
            │ 2. 保存当前 │            │ 到Memory   │
            │ 3. 合并消息 │            └─────┬──────┘
            └─────┬──────┘                   │
                  └──────────→ 记忆存储 ←─────┘

3.2 代码示例

第一版:

  • 新建配置类

    package com.wx.config;

    import org.springframework.ai.chat.memory.ChatMemory;
    import org.springframework.ai.chat.memory.MessageWindowChatMemory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    /**

    • @Description

    • @author: wangxin

    • @date: 2026/3/15 13:36
      */
      @Configuration
      public class ChatMemoryConfig {

      @Bean
      public ChatMemory chatMemory() {
      // 配置保留最近20条消息
      return MessageWindowChatMemory.builder()
      .maxMessages(20)
      .build();
      }
      }

  • 会话service

    package com.wx.service;

    import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
    import org.springframework.ai.chat.memory.ChatMemory;
    import org.springframework.ai.chat.model.ChatModel;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Scope;
    import org.springframework.context.annotation.ScopedProxyMode;
    import org.springframework.stereotype.Component;

    import java.util.UUID;

    /**

    • @Description

    • @author: wangxin

    • @date: 2026/3/15 13:42
      */
      @Component
      @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) // 每个会话一个实例
      public class ChatService {

      private final ChatClient chatClient;
      private final String conversationId;

      public ChatService(@Qualifier("dashscopeChatModel") ChatModel chatModel, ChatMemory chatMemory) {
      // 为每个会话生成唯一ID
      this.conversationId = "session_" + UUID.randomUUID().toString();

      复制代码
       // 使用 ChatModel 创建 ChatClient.Builder
       ChatClient.Builder builder = ChatClient.builder(chatModel);
      
       // 构建带记忆的ChatClient
       this.chatClient = builder
               .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
               .build();

      }

      public String chat(String message) {
      return chatClient.prompt()
      .user(message)
      .advisors(a -> a.param("conversation_id", conversationId)) // 传入会话ID
      .call()
      .content();
      }

      public String getConversationId() {
      return conversationId;
      }

    }

  • 测试

    package com.wx.controller;

    import com.wx.service.ChatService;
    import jakarta.annotation.Resource;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.HashMap;
    import java.util.Map;

    /**

    • @Description

    • @author: wangxin

    • @date: 2026/3/15 13:37
      */
      @RestController
      @RequestMapping("/memory")
      public class MemoryChatController {

      @Resource
      private ChatService chatService; // 注意:这是会话作用域的

      @GetMapping("/chat")
      public Map<String, String> chat(@RequestParam String msg) {
      String response = chatService.chat(msg);

      复制代码
       Map<String, String> result = new HashMap<>();
       result.put("response", response);
       result.put("conversationId", chatService.getConversationId());
       System.out.println("返回result:"+ result);
       return result;

      }

    }

启动服务测试:

同一会话多次访问:

重启服务,再次访问(不同会话):

结果可以看到会话记忆已消失。这一版虽然配置的有会话记忆,但是我们设置了会话的作用域,重新开启一个会话,就不生效了。下面来优化版,持久化Chat Memory,就可以解决这种情况。

**四、**持久化:让记忆跨会话保存

上面的例子中,记忆保存在内存里,应用重启后就会丢失。要实现真正的持久化,需要使用ChatMemoryRepository。我们这里使用Redis持久化。

  • 添加Spring AI Alibaba支持的Redis依赖

    复制代码
          <dependency>
              <groupId>com.alibaba.cloud.ai</groupId>
              <artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
          </dependency>
          <!--jedis-->
          <dependency>
              <groupId>redis.clients</groupId>
              <artifactId>jedis</artifactId>
          </dependency>
  • 配置文件

    server:
    port: 8005
    servlet:
    encoding:
    enabled: true
    force: true
    charset: utf-8

    spring:
    application:
    name: demo5
    # ====ollama Config=============
    ai:
    dashscope:
    api-key: ${aliQwen-api}
    # Redis配置
    data:
    redis:
    host: localhost
    port: 6379
    password: # 如果有密码,在这里填写
    database: 0
    timeout: 2000ms
    lettuce:
    pool:
    max-active: 8
    max-idle: 8
    min-idle: 0

  • 配置类修改

    package com.wx.config;

    import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository;
    import org.springframework.ai.chat.memory.ChatMemory;
    import org.springframework.ai.chat.memory.MessageWindowChatMemory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    /**

    • @Description

    • @author: wangxin

    • @date: 2026/3/15 13:36
      */
      @Configuration
      public class ChatMemoryConfig {

      @Value("{spring.data.redis.host}") private String host; @Value("{spring.data.redis.port}")
      private int port;

      @Bean
      public RedisChatMemoryRepository redisChatMemoryRepository()
      {
      return RedisChatMemoryRepository.builder()
      .host(host)
      .port(port)
      .build();
      }

      @Bean
      public ChatMemory chatMemory() {
      // 配置保留最近20条消息
      return MessageWindowChatMemory.builder()
      .maxMessages(20)
      .build();
      }
      }

    package com.wx.config;

    import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
    import com.alibaba.cloud.ai.memory.redis.RedisChatMemoryRepository;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
    import org.springframework.ai.chat.memory.MessageWindowChatMemory;
    import org.springframework.ai.chat.model.ChatModel;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    /**

    • @Description

    • @author: wangxin

    • @date: 2026/3/11 22:11
      */
      @Configuration
      public class LLMConfig {

      @Bean
      public DashScopeApi dashScopeApi() {
      return DashScopeApi.builder().apiKey(System.getenv("aliQwen-api")).build();
      }

      @Bean
      public ChatClient chatClient(ChatModel dashscopeChatModel, RedisChatMemoryRepository redisChatMemoryRepository)
      {

      复制代码
       MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory.builder()
               .chatMemoryRepository(redisChatMemoryRepository)
               .maxMessages(10).build();
       return ChatClient.builder(dashscopeChatModel)
               .defaultAdvisors(MessageChatMemoryAdvisor.builder(messageWindowChatMemory).build())
               .build();

      }

    }

  • 业务类

    package com.wx.controller;

    import jakarta.annotation.Resource;
    import org.springframework.ai.chat.client.ChatClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;

    /**

    • @Description

    • @author: wangxin

    • @date: 2026/3/15 15:21
      */
      @RestController
      @RequestMapping("/memory/redis")
      public class RedisMemoryChatController {

      @Resource
      private ChatClient chatClient;

      @GetMapping("/chat")
      public String chat(@RequestParam String msg) {
      String response = chatClient.prompt()
      .user(msg)
      .call()
      .content();
      System.out.println("返回result:"+ response);
      return response;
      }

    }

  • 测试

先查看redis:

访问接口:

重启服务,再次访问:

可以发现已解决重启服务,持久化已解决重启服务,跨会话记忆消失的问题。

查看Redis存储的会话信息:

相关推荐
愣锤2 小时前
深入分析OpenClaw爆火出圈的根本原因
人工智能·openai·agent
RuiBo_Qiu2 小时前
【LLM进阶-Agent】9. self-discover Agent 介绍
人工智能·ai-native
阿拉斯攀登2 小时前
记忆的困境:RNN 与 LSTM 的底层逻辑
人工智能·rnn·深度学习·ai·大模型·llm·lstm
乱世刀疤2 小时前
云主机ubuntu24上安装openclaw后配置飞书通道
人工智能·openclaw
irpywp2 小时前
拒绝 AI 盲目梭哈:拆解 Garry Tan 的 gstack 架构逻辑
人工智能·架构
SmartBrain2 小时前
FastAPI + LangGraph 与 SpringAI 在医疗场景应用及分析
人工智能·spring boot·spring·fastapi
AnalogElectronic2 小时前
RP2040学习4,LED点亮,OLED显示,DHT11温湿度传感器数据读取
单片机·嵌入式硬件·学习
adore.9682 小时前
3.15 复试学习
学习
工业机器视觉设计和实现2 小时前
为什么bn+tanh比bn+relu效果好?
人工智能·cudnn微积分