首页 > 基础资料 博客日记
Spring with AI (6): 记忆保持——会话与长期记忆
2026-04-01 22:30:02基础资料围观1次
本文代码:
https://github.com/JunTeamCom/ai-demo/tree/release-6.0 (JDBC-MySQL版本的会话持久化)
https://github.com/JunTeamCom/ai-demo/tree/release-6.1 (VectorStore-Qrant版本的会话持久化)
本章讲解聊天内容的存储:短期记忆(即会话级别)、存储方式、长期记忆(用户级别),这是对前面两个Topic(OpenAI接入、RAG与向量数据库)的延伸。
1 注入聊天记忆类
参考上一章内容,也是通过Advisor类注入控制聊天内容存储:
- MessageChatMemoryAdvisor
- PromptChatMemoryAdvisor
- VectorStoreChatMemoryAdvisor
前两者为短期记忆,不同之处是MessageChatMemoryAdvisor能够按角色存储会话(即用户和助手两种角色,不过部分大模型不支持),PromptChatMemoryAdvisor是把历史信息转换为大字符串、注入到提示词中。
VectorStoreChatMemoryAdvisor则是用向量数据库、记录历史消息(类似RAG)。
集成方式,是构建一个*MemoryAdvisor类,然后defaultAdvisors入参加入(方法入参为Advisor链)。
@Bean
ChatClient chatClient(ChatClient.Builder chatClientBuilder,
ChatMemory chatMemory,
VectorStore vectorStore) {
// 设置顾问配置项
return chatClientBuilder
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory)
.build(),
QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder().build())
.build())
.build();
}
为了防止对话内容溢出,需要控制上下文最大条数:
@Bean
ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(50) // 最大条数50,即:提问25条、回答25条
.build();
}
2 引入会话ID
2.1 Controller层引入
现实中我们当然不能把所有会话、都统一记忆;其实还是要通过会话ID进行区分(类似Session):
@PostMapping(path = "/ask", produces = "application/json")
public ChatAnswer ask(
@RequestHeader(name = "X_CONVERSATION_ID", defaultValue = "default") String conversationId,
@RequestBody ChatQuestion chatQuestion) {
return chatService.ask(chatQuestion, conversationId);
}
2.2 Service层:引入会话ID前的简化
在修改Advisor、引入会话ID的同时,将上一章的Rules形式、改为Expression形式:
package com.junteam.ai.demo.service.impl;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import com.junteam.ai.demo.model.ChatAnswer;
import com.junteam.ai.demo.model.ChatQuestion;
import com.junteam.ai.demo.service.ChatService;
import static org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor.FILTER_EXPRESSION;
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
@Service
public class OpenAIChatServiceImpl implements ChatService {
private final ChatClient chatClient;
public OpenAIChatServiceImpl(ChatClient chatClient) {
this.chatClient = chatClient;
}
@Value("classpath:/promptTemplates/questionPromptTemplate.st")
Resource questionPromptTemplate;
@Override
public ChatAnswer ask(ChatQuestion chatQuestion, String conversationId) {
var countryTitleMatch = String.format(
"countryTitle == '%s'",
chatQuestion.title());
return chatClient
.prompt()
.system(systemSpec -> systemSpec
.text(questionPromptTemplate)
.param("countryTitle", chatQuestion.title()))
.advisors(advisorSpec -> advisorSpec
.param(FILTER_EXPRESSION, countryTitleMatch)
.user(chatQuestion.question())
.call()
.entity(ChatAnswer.class);
}
}
相应的,提示词模板修改:
你是一个有用的助手,负责回答有关{countryTitle}的历史地理风俗问题。
2.3 Service层引入会话ID
修改Service接口和实现类:
package com.junteam.ai.demo.service;
import com.junteam.ai.demo.model.ChatAnswer;
import com.junteam.ai.demo.model.ChatQuestion;
public interface ChatService {
ChatAnswer ask(ChatQuestion question, String conversationId);
}
package com.junteam.ai.demo.service.impl;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import com.junteam.ai.demo.model.ChatAnswer;
import com.junteam.ai.demo.model.ChatQuestion;
import com.junteam.ai.demo.service.ChatService;
import static org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor.FILTER_EXPRESSION;
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
@Service
public class OpenAIChatServiceImpl implements ChatService {
private final ChatClient chatClient;
public OpenAIChatServiceImpl(ChatClient chatClient) {
this.chatClient = chatClient;
}
@Value("classpath:/promptTemplates/questionPromptTemplate.st")
Resource questionPromptTemplate;
@Override
public ChatAnswer ask(ChatQuestion chatQuestion, String conversationId) {
var countryTitleMatch = String.format(
"countryTitle == '%s'",
chatQuestion.title());
return chatClient
.prompt()
.system(systemSpec -> systemSpec
.text(questionPromptTemplate)
.param("countryTitle", chatQuestion.title()))
.advisors(advisorSpec -> advisorSpec
.param(FILTER_EXPRESSION, countryTitleMatch)
.param(CONVERSATION_ID, conversationId)) // 引入会话ID
.user(chatQuestion.question())
.call()
.entity(ChatAnswer.class);
}
}
需要注意的是:
会话内容是存储在内存中的,服务一旦重启、那么所有历史内容都会清空。
如果需要持久化存储、并且多服务节点时能共享会话内容,需要使用向量数据库等手段存储。
3 持久化会话数据
从单体应用,迈向无状态的分布式服务集群,会话数据必须持久化(即将临时存储的内容——如内存数据等,通过文件或数据库等长期存储)。
这既可以通过ChatMemoryRepository实现,也可以直接使用VectorStoreChatMemoryAdvisor。
3.1 ChatMemoryRepository实现持久化
具体来说,ChatMemoryRepository主要有三种实现方式(都是使用数据库):
- CassandraChatMemoryRepository
- JdbcChatMemoryRepository
- Neo4jChatMemoryRepository
分别对应Cassandra文档数据库、JDBC关系型数据库、Neo4j图数据库(相关概念不再赘述,搜索引擎即可搜到准确简明的概念说明)。
如果要使用,引入相关的Starter、注入ChatMemoryRepository、配置数据库链接地址即可。
下面以JDBC-MySQL为例、进行扩展。
3.1.1 依赖引入
1、引入Starter:
org.springframework.ai: spring-ai-starter-model-chat-memory-repository-jdbc
2、JDBC方式与其他相比,需要再引入驱动:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
3、找到相关初始SQL,再数据库执行SQL:
classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-mysql.sql

CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
`conversation_id` VARCHAR(36) NOT NULL,
`content` TEXT NOT NULL,
`type` ENUM('USER', 'ASSISTANT', 'SYSTEM', 'TOOL') NOT NULL,
`timestamp` TIMESTAMP NOT NULL,
INDEX `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX` (`conversation_id`, `timestamp`)
);
3.1.2 数据库配置
配置文件添加数据库地址:
spring:
# 数据库配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${DB_SERVER}:3306/chat_db
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
3.1.3 JAVA代码适配
JAVA代码适配、全部通过ChatMemoryRepository的@Bean注入(AiConfig类);
JDBC也特殊一点,需要自定义数据库方言(比如MySQL/PostgreSQL/Oracle/SQLServer等)
@Bean
ChatMemoryRepository chatMemoryRepository(DataSource dataSource) {
return JdbcChatMemoryRepository.builder()
.dialect(new MysqlChatMemoryRepositoryDialect())
.dataSource(dataSource)
.build();
}
Service实现类不需要进行变更。
3.2 VectorStoreChatMemoryAdvisor实现持久化
spring-ai-advisors-vector-store依赖里已经包含相关Advisor,直接修改AiConfig即可;
这不需要额外的数据源,是简洁方便的方式:
package com.junteam.ai.demo.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.VectorStoreChatMemoryAdvisor;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiConfig {
@Bean
ChatClient chatClient(ChatClient.Builder chatClientBuilder,
VectorStore vectorStore) {
// 设置顾问配置项
return chatClientBuilder
.defaultAdvisors(
VectorStoreChatMemoryAdvisor
.builder(vectorStore)
.build(),
QuestionAnswerAdvisor
.builder(vectorStore)
.searchRequest(SearchRequest.builder().build())
.build())
.build();
}
}
···
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签:
相关文章
最新发布
- OpenAI 官方出手:把 Codex 接进 Claude Code
- 【OpenClaw】通过 Nanobot 源码学习架构---(2)外层控制逻辑
- 【Pwn】堆学习之glibc2.31下的__free_hook劫持
- Spring with AI (6): 记忆保持——会话与长期记忆
- 实际的 c++2026
- 第十九届全国大学生信息安全竞赛_babygame:Godot 游戏逆向与 AES 运行时密钥替换
- 关于列式存储(Column-base Storage)的几个要点解读
- 同样都是九年义务教育,他知道的AI算力科普好像比我多耶
- Slickflow 与 OpenClaw 结合实践:技术原理、集成方式与 Skill 说明
- 如何使用 UEFI Shell 执行 Hello World 程序

