Spring AI Alibaba零基础速成(5) ---- Memory(记忆)

大模型默认只能单轮对话,每次对话完成后就会丢失当前对话记忆,我们之前了解过可以通过AssistantMessage把大模型回复结果存储起来下次提问时在发送给大模型,不过使用过于麻烦和受限,Spring AI 和Spring AI Alibaba都实现了更好实现记忆化的方式:

1. 内存存储

java 复制代码
@Configuration
public class SaaLLMConfig {
    private final String QWEN_MODEL = "qwen-plus";
    @Bean
    public DashScopeApi dashScopeApi() {
        return DashScopeApi.builder().apiKey(System.getenv("AliQWenAPIKey")).build();
    }
    @Bean
    public ChatModel qwenChatModel(DashScopeApi dashScopeApi) {
        return DashScopeChatModel
                .builder()
                .dashScopeApi(dashScopeApi)
                .defaultOptions(DashScopeChatOptions.builder().model(QWEN_MODEL).build())
                .build();
    }
    @Bean
    public ChatClient qwenChatClient(@Qualifier("qwenChatModel") ChatModel qwenChatModel) {

        MessageWindowChatMemory windowChatMemory = MessageWindowChatMemory.builder()
                .maxMessages(10)
                .chatMemoryRepository(new InMemoryChatMemoryRepository())
                .build();

        return ChatClient
                .builder(qwenChatModel)
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(windowChatMemory).build())
                .build();
    }
}
  • MessageWindowChatMemory:维护固定容量的消息窗口(默认 20 条)。当消息超限时,自动移除较早的对话消息(始终保留系统消息)。
  • InMemoryChatMemoryRepository: 基于 ConcurrentHashMap 实现内存存储。默认情况下,若未配置其他 Repository,Spring AI 将自动配置 InMemoryChatMemoryRepository 类型的 ChatMemoryRepository Bean供直接使用。

2. 源码解析

关键在于MessageChatMemoryAdvisor这个类,可以看到我们在配置ChatClient时配置了一个该对象,这个就是生效的关键,虽然名字叫 Advisor ,但它和 Spring AOP 一点关系都没有 。它是 Spring AI 自己实现的一套独立拦截器机制

java 复制代码
	@Override
	public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
		String conversationId = getConversationId(chatClientRequest.context(), this.defaultConversationId);

		// 1. Retrieve the chat memory for the current conversation.
		List<Message> memoryMessages = this.chatMemory.get(conversationId);

		// 2. Advise the request messages list.
		List<Message> processedMessages = new ArrayList<>(memoryMessages);
		processedMessages.addAll(chatClientRequest.prompt().getInstructions());

		// 3. Create a new request with the advised messages.
		ChatClientRequest processedChatClientRequest = chatClientRequest.mutate()
			.prompt(chatClientRequest.prompt().mutate().messages(processedMessages).build())
			.build();

		// 4. Add the new user message to the conversation memory.
		UserMessage userMessage = processedChatClientRequest.prompt().getUserMessage();
		this.chatMemory.add(conversationId, userMessage);

		return processedChatClientRequest;
	}

	@Override
	public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
		List<Message> assistantMessages = new ArrayList<>();
		if (chatClientResponse.chatResponse() != null) {
			assistantMessages = chatClientResponse.chatResponse()
				.getResults()
				.stream()
				.map(g -> (Message) g.getOutput())
				.toList();
		}
		this.chatMemory.add(this.getConversationId(chatClientResponse.context(), this.defaultConversationId),
				assistantMessages);
		return chatClientResponse;
	}

在该类源码中我们可以看到关键的两个方法,before和after

  • before:在发送请求前执行先从memoryMessages中获取历史对话,再把本次对话的用户问题也存入chatMemory中。
  • after:在大模型返回后执行,把大模型返回内容存储到chatMemory中

3. Redis存储

配置redis:

XML 复制代码
spring:
  data:
    redis:
      host: localhost
      port: 6379
      database: 0

要使用Redis需要引入依赖:

XML 复制代码
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- Alibaba Spring AI Redis 记忆模块 -->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
        </dependency>

可以看到这个依赖适配了Jedis和RedissonRedis以及springBoot的Lettuce

java 复制代码
@Configuration
public class SaaLLMConfig {
    private final String DEEPSEEK_MODEL = "deepseek-v3.2";

    @Bean
    public ChatModel deepseekChatModel() {
        return DashScopeChatModel
                .builder()
                .dashScopeApi(DashScopeApi.builder().apiKey(System.getenv("AliQWenAPIKey")).build())
                .defaultOptions(DashScopeChatOptions.builder().model(DEEPSEEK_MODEL).build())
                .build();
    }

    @Bean
    public ChatClient deepseekChatClient(@Qualifier("deepseekChatModel") ChatModel deepseekChatModel) {
        LettuceRedisChatMemoryRepository redisCMR = LettuceRedisChatMemoryRepository.builder().build();

        MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
                .maxMessages(10)
                .chatMemoryRepository(redisCMR)
                .build();

        return ChatClient
                .builder(deepseekChatModel)
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
                .build();
    }
}

同时,一个大模型通常会服务多个用户,对于不同用户的对话应该分开存储,我们可以通过设置CONVERSATION_ID来实现:

java 复制代码
@RestController
public class ChatMemoryController {
    @Resource(name = "deepseekChatClient")
    ChatClient deepseekChatClient;

    @GetMapping("/chatmemory/chat2")
    public Flux<String> chat2(String userId, String question) {
        return deepseekChatClient
                .prompt()
                .user(question)
                .advisors(
                        advisorSpec -> advisorSpec.param(CONVERSATION_ID, userId)
                )
                .stream()
                .content();
    }
}

通过.advisors方法设置,如果不设置会使用默认的,这样所有用户的对话是共享的:

可以看到是按id分别存储的

4. 关系型数据库存储

导入依赖:

XML 复制代码
        <!-- MySQL 驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Spring JDBC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!--Spring AI JDBC 聊天记忆启动器-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
        </dependency>

增加配置:

XML 复制代码
spring:
  ai:
    chat:
      #开启Schema初始化,会自动创建表用
      memory:
        repository:
          jdbc:
            initialize-schema: always
  # 数据库连接
  datasource:
    url: jdbc:mysql://localhost:3306/ai_chat?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

  # JDBC 自动建表
  sql:
    init:
      mode: always

配置ChatClient:

java 复制代码
@Configuration
public class SaaLLMConfig {
    private final String DEEPSEEK_MODEL = "deepseek-v3.2";
    private final String QWEN_MODEL = "qwen-plus";
    private final String GLM5 = "glm-5";

    @Bean
    public DashScopeApi dashScopeApi() {
        return DashScopeApi.builder().apiKey(System.getenv("AliQWenAPIKey")).build();
    }
    @Bean
    public ChatModel glmChatModel() {
        return DashScopeChatModel
                .builder()
                .dashScopeApi(DashScopeApi.builder().apiKey(System.getenv("AliQWenAPIKey")).build())
                .defaultOptions(DashScopeChatOptions.builder().model(GLM5).build())
                .build();
    }
    @Bean
    public ChatClient glmChatClient(@Qualifier("glmChatModel") ChatModel glmChatModel, JdbcChatMemoryRepository jdbcChatMemoryRepository) {
        MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder()
                .maxMessages(10)
                .chatMemoryRepository(jdbcChatMemoryRepository)
                .build();

        return ChatClient
                .builder(glmChatModel)
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
                .build();
    }

Spring AI 为 JdbcChatMemoryRepository 提供自动配置,可直接注入。

java 复制代码
@RestController
public class ChatMemoryController {
    @Resource(name = "glmChatClient")
    ChatClient glmChatClient;

    @GetMapping("/chatmemory/chat3")
    public Flux<String> chat3(String userId, String question) {
        return glmChatClient
                .prompt()
                .user(question)
                .advisors(
                        advisorSpec -> advisorSpec.param(CONVERSATION_ID, userId)
                )
                .stream()
                .content();
    }
}
相关推荐
苏生Susheng3 分钟前
【软件实施】Linux企业运维常用命令手册
java·linux·运维·服务器·springboot·springcloud·软件实施
想要成为老金高手4 分钟前
Kubernetes 调度器详解:从 nodeName 到污点容忍
java·容器·kubernetes
吴长建先生重名了8 分钟前
ElasticSearch 检索系统性能优化实战:基准测试
java
用户938515635078 分钟前
大模型记忆指南:从短时缓存到永久记忆,手把手带你吃透 LangChain Memory 管理
javascript·人工智能
武子康10 分钟前
机器人策略 90% 与 92%:为什么两个百分点通常不足以证明更
人工智能·llm·agent
阿里云大数据AI技术13 分钟前
PAI支持一键部署Qwen3.8-Flash-Next、GLM-5.3等最新开源模型
人工智能·开源·llm
SleepNW_POV14 分钟前
家居科普|床垫溜边塌陷成因解析与边缘加固床垫选购指南
人工智能·科技·智能家居·睡眠
FII工业富联科技服务15 分钟前
从 Vera Rubin NVL72 拆解高密度 AI 液冷:GPU 的热到底怎么被带走?
大数据·人工智能
西索斯coding16 分钟前
doubao-seed-2.1-turbo 调用一直 401 怎么办?pro 版同样的 Key 却正常——5 分钟排查定位指南
java·服务器·数据库·ai
oliver_sys_log16 分钟前
RocketMQ 4.5.1 延迟消息"发送成功但消费不到"排查分析报告
后端·rocketmq