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();
    }
}
相关推荐
陈天伟教授7 小时前
图解人工智能(32)深度学习前沿
人工智能·深度学习
RSTJ_16257 小时前
PYTHON+AI LLM DAY FIFITY-TWO
人工智能
月落归舟7 小时前
一文掌握Spring AOP:从入门到底层原理
java·后端·spring
幂律智能7 小时前
从AI使用风险到合同智能审查重构企业风控能力
人工智能·重构
QuZhengRong7 小时前
【Luck-Report】缓存
java·前端·后端·vue·excel
视***间7 小时前
端侧大模型落地新标杆:视程空间将GPT-OSS边缘AI深度导入NVIDIA Jetson平台
人工智能·gpt·边缘计算·nvidia·ai算力·gpt-oss·视程空间
XiYang-DING7 小时前
【Spring】SpringMVC
java·后端·spring
想学习java初学者7 小时前
SpringBoot整合GS1编码解码
java·spring boot·后端
日月云棠8 小时前
2 快速入门实战指南
java·后端