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();
    }
}
相关推荐
蜜桃味女焊匠人几秒前
气保焊怎样改造,稳定达成40%-60%节气降耗效果?
人工智能·经验分享·其他·机器人
geovindu10 分钟前
java: Facade Pattern
java·开发语言·后端·外观模式·结构型模式
listening77712 分钟前
HarmonyOS 6.1 元服务深度优化:从“秒开”到“常驻”的极致体验
java·开发语言·spring
布朗克16814 分钟前
Go 入门到精通-29-测试与基准
开发语言·后端·golang·测试与基准
Ai_easygo24 分钟前
多Agent协作与A2A协议——CrewAI + LangGraph搭建AI协作团队
人工智能
你驴我27 分钟前
WhatsApp Cloud API 速率限制下的令牌桶与退避策略实践
网络·后端·python
AC赳赳老秦30 分钟前
司法公开数据采集应用:OpenClaw 抓取裁判文书公开信息,批量整理同类参考案例
java·大数据·python·数据挖掘·数据分析·php·openclaw
HIT_Weston32 分钟前
151、【Agent】【OpenCode】启动分析(CLI 命令注册)
人工智能·agent·opencode
笑小枫37 分钟前
用 Claude Code 推翻重写笑小枫网站
java·人工智能·spring boot·ai编程
audyxiao0011 小时前
人工智能顶会AAAI 2026论文分享|SlideBot:用于生成信息丰富、可靠、多模态幻灯片的多智能体框架
人工智能·大模型·aaai·智能体·幻灯片