Spring AI 聊天记忆功能实战(一):从接口设计到生产实践

在构建AI对话应用时,聊天记忆管理及存储是实现连贯上下文交互的关键组件。而大模型(LLM)本质上是无状态的,这意味着它们不会保留历史交互信息。当需要跨多轮交互保持上下文时,这一特性会带来局限。为此,Spring AI 提供了聊天记忆功能,支持在 LLM 交互过程中存储和检索上下文数据。

入门理解

Spring AI 框架通过模块化设计提供了灵活的聊天记忆解决方案,其核心在于ChatMemoryRepositoryChatMemory的协同工作机制。

ChatMemoryRepository 接口提供了各种存储方式实现的统一抽象,增加了聊天记忆功能的灵活性及扩展性,目前 Spring AI(1.0.0 版本)框架提供了基于 内存(默认)、JDBC、Cassandra、Neo4j 四种存储方式的实现。

public interface ChatMemoryRepository {// 获取所有会话IDList<String> findConversationIds();// 获取指定会话ID的聊天消息List<Message> findByConversationId(String conversationId);// 存储整个会话ID的历史消息(替换式更新)void saveAll(String conversationId, List<Message> messages);// 清理指定会话ID中的聊天消息void deleteByConversationId(String conversationId);
}

ChatMemory 抽象层支持实现多种记忆类型以满足不同场景需求。消息的底层存储由 ChatMemoryRepository 处理,其唯一职责是存储和检索消息。ChatMemory 实现类可自主决定消息保留策略 —— 例如保留最近 N 条消息、按时间周期保留或基于 Token 总量限制保留。

public interface ChatMemory {// 从聊天记忆的上下文检索会话IDString CONVERSATION_ID = "chat_memory_conversation_id";// 将聊天消息保存到指定会话ID的存储中void add(String conversationId, List<Message> messages);// 获取指定会话ID中的聊天消息List<Message> get(String conversationId);// 清理指定会话ID中的聊天消息void clear(String conversationId);
}

快速使用

Spring AI 自动配置 ChatMemory Bean 供直接使用。默认采用内存存储(InMemoryChatMemoryRepository)及 MessageWindowChatMemory 实现管理会话历史。若已配置其他 Repository(如 Cassandra / JDBC / Neo4j ),则自动切换至对应实现。

记忆类型:MessageWindowChatMemory 维护固定容量的消息窗口(默认 20 条)。当消息超限时,自动移除较早的对话消息(始终保留系统消息)。

// 自动注入
@Autowired
ChatMemory chatMemory;// 手动创建
MessageWindowChatMemory memory = MessageWindowChatMemory.builder().maxMessages(10).build();

记忆存储:InMemoryChatMemoryRepository 内存存储为默认的聊天记忆存储实现。同样地,Spring AI 提供了统一的自动配置,可直接自动注入使用。

// 自动注入
@Autowired
ChatMemoryRepository chatMemoryRepository;// 手动创建
ChatMemoryRepository repository = new InMemoryChatMemoryRepository();

自动配置在默认情况下,Spring AI 注入了如下 Bean 对象:

@Bean
ChatMemoryRepository chatMemoryRepository() {return new InMemoryChatMemoryRepository();
}@Bean
ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {return MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).build();
}

原理实现

聊天记忆存储的实现主要依靠 ChatMemoryRepository 接口 及 ChatMemory 接口,它们的默认实现类分别为 InMemoryChatMemoryRepositoryMessageWindowChatMemory

InMemoryChatMemoryRepository实现类:

public final class InMemoryChatMemoryRepository implements ChatMemoryRepository {Map<String, List<Message>> chatMemoryStore = new ConcurrentHashMap<>();// 方法实现...
}

InMemoryChatMemoryRepository 使用并发安全的 ConcurrentHashMap 实现聊天记录的增删改操作,存储实现依赖 Map 的 get、put、remove 操作。需要注意的是,这里 saveAll(写入记录)方法实现是替换旧数据的更新逻辑,若对应到外部存储的写入方式应该先删除后插入数据,而限制聊天对话历史长度的逻辑在 MessageWindowChatMemory 类实现。

MessageWindowChatMemory 实现类:

public final class MessageWindowChatMemory implements ChatMemory {private final ChatMemoryRepository chatMemoryRepository;private final int maxMessages;// 方法实现...private List<Message> process(List<Message> memoryMessages, List<Message> newMessages) {// 根据最大消息数处理消息窗口}
}

处理消息窗口容量的主要逻辑为:

  • 识别新增的SystemMessage,当有新系统消息时,清除所有旧系统消息、所有的新SystemMessage不受容量限制;
  • 保留非系统消息历史+新增消息,按添加顺序移除最早的非系统消息,确保 最终消息数 == maxMessages、返回处理后的新消息;

消息窗口的聊天记忆管理与数据存储交互的 UML 类图 如下:

«interface»
ChatMemory
+add(String, List<Message>)
+get(String) : List<Message>
+clear(String)
MessageWindowChatMemory
-chatMemoryRepository: ChatMemoryRepository
-maxMessages: int
-process(List<Message> memoryMessages, List<Message> newMessages) : List<Message>
«interface»
ChatMemoryRepository
+findConversationIds() : List<Message>
+findByConversationId(String) : List<Message>
+saveAll(String, List<Message>)
+deleteByConversationId(String)

实战案例

接下来我们使用MessageWindowChatMemory+JdbcChatMemoryRepository的方式实现一个AI聊天助手的功能。

JdbcChatMemoryRepository 是内置的 JDBC 实现,支持多种关系型数据库,适用于需要持久化存储聊天记忆的场景。JDBC 实现是基于 spring-jdbc 模块,我们可通过 JdbcTemplate 来配置任一数据源。

1、导入模块

首先,在 spring-boot 项目中添加以下依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId>
</dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency><dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId><scope>runtime</scope>
</dependency>

2、配置参数

新建 application.yml,添加配置 spring.aiJdbcTemplate 相关参数:

spring:application:name: AI Assistantai:openai:api-key: ${OPENAI_API_KEY:NONE}base-url: ${OPENAI_BASE_URL:NONE}chat:options:model: DeepSeek-V3completions-path: /v1/chat/completionschat:memory:repository:jdbc:initialize-schema: always # Always initializedatasource:url: jdbc:postgresql://localhost:5432/testusername: rootpassword: 123456driver-class-name: org.postgresql.Driver

3、创建 Bean 实例

Spring AI 为 JdbcChatMemoryRepository 提供了自动配置,可直接在应用中使用 (默认使用 hsqldb 嵌入式数据库),也可手动创建 Bean 实例。

@Configuration
public class GeneralChatClientConfig {private final JdbcChatMemoryRepository jdbcChatMemoryRepository;public GeneralChatClientConfig(JdbcChatMemoryRepository jdbcChatMemoryRepository) {this.jdbcChatMemoryRepository = jdbcChatMemoryRepository;}@Bean(name = "messageWindowChatMemoryWithJdbc")public MessageWindowChatMemory messageWindowChatMemoryWithJdbc() {int maxMessages = 20;return MessageWindowChatMemory.builder().chatMemoryRepository(jdbcChatMemoryRepository) // default: new InMemoryChatMemoryRepository().maxMessages(maxMessages).build();}
}

目前 Spring AI 支持的数据库与方言抽象层:

  • PostgreSQL
  • MySQL / MariaDB
  • SQL Server
  • HSQLDB

使用 JdbcChatMemoryRepositoryDialect.from(DataSource) 时可基于 JDBC URL 自动识别正确方言。通过实现 JdbcChatMemoryRepositoryDialect 接口可扩展其他数据库方言支持。

4、聊天记忆客户端

创建使用 ChatClient API 时,可通过注入 ChatMemory 实现来维护跨多轮交互的会话上下文。

Spring AI 提供多种内置 Advisor,用于按需配置 ChatClient 的记忆行为。

  • MessageChatMemoryAdvisor:通过指定 ChatMemory 实现管理会话记忆。每次交互时从记忆库检索历史消息,并将其作为消息集合注入提示词。
  • PromptChatMemoryAdvisor:基于指定 ChatMemory 实现管理会话记忆。每次交互时从记忆库检索历史对话,并以纯文本形式追加至系统(system)提示词。
  • VectorStoreChatMemoryAdvisor:通过指定 VectorStore 实现管理会话记忆。每次交互时从向量存储检索历史对话,并以纯文本形式追加至系统(system)消息。

例如,若需结合 MessageWindowChatMemoryPromptChatMemoryAdvisor,可按如下方式配置:

    @Bean(name = "deepseekV3ClientWithJdbc")public ChatClient deepseekV3ClientWithJdbc(@Value("${spring.ai.openai.base-url}") String baseUrl,@Value("${spring.ai.openai.chat.options.model}") String modelName,@Value("${spring.ai.openai.api-key}") String apiKey,@Qualifier("messageWindowChatMemoryWithJdbc") MessageWindowChatMemory messageWindowChatMemoryWithJdbc) {OpenAiApi build = OpenAiApi.builder().apiKey(apiKey).baseUrl(baseUrl).build();OpenAiChatModel openAiChatModel =OpenAiChatModel.builder().openAiApi(build).defaultOptions(OpenAiChatOptions.builder().model(modelName).build()).build();return ChatClient.builder(openAiChatModel).defaultAdvisors(PromptChatMemoryAdvisor.builder(messageWindowChatMemoryWithJdbc).build()).defaultAdvisors(new SimpleLoggerAdvisor()).build();}

调用 ChatClient 时,ChatMemoryAdvisor 将自动管理记忆存储。系统会根据指定的会话 ID 从记忆库检索历史对话。

我们新建一个 Controller ,注入配置好的 ChatClient,定义一个api实现大模型的 chat 调用,代码如下:

@RestController
@RequestMapping("/ai/v3/chat/")
@Slf4j
public class GeneralChatController {private final ChatClient jdbcChatClient;public GeneralChatController(@Qualifier("deepseekV3ClientWithJdbc") ChatClient jdbcChatClient) {this.jdbcChatClient = jdbcChatClient;}@PostMapping(value = "/t1", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})@ResponseBodypublic Object chatWithJdbc(@RequestBody String body) {JSONObject entries = JSONUtil.parseObj(body);String text = entries.getStr("text");Boolean stream = entries.getBool("stream", false);String conversationId = entries.getStr("conversationId");log.info("开始对话聊天,会话ID:{}", conversationId);var request = jdbcChatClient.prompt().system("你是乐观小王,回答问题简练精要。").advisors(advisor -> advisor.param(CONVERSATION_ID, conversationId)).user(text);try {log.info("开始生成回答,是否流式输出:{}", stream);return stream ? request.stream().content() : request.call().content();} catch (Exception e) {log.error("对话聊天发生异常,会话ID:{}", conversationId, e);throw e;}}
}

5、测试AI聊天功能

通过上述的简单配置和代码编写,我们已经实现了一个通用的带有记忆功能的AI聊天助手。

接下来让我们测试一下这个聊天助手的使用效果,设置几条 USER 的提问内容,具体内容如下:

# Openai Chat API
### 我的名字叫小明,你叫什么名字?
### 我是谁?
### 我们之间的第一句问话是什么?
POST http://localhost:10001/ai/v3/chat/t1
Content-Type: application/json{
"text": "我的名字叫小明,你叫什么名字?",
"stream": false,
"conversationId": "bnA9f525-l7ae-5c66-ae21-vh53547c96cf"
}

聊天消息记录会被存储到 PostgreSQL 数据库中,http顺序请求后的大模型返回结果 如图所示:

小总结

通过上述测试结果可以清晰看到,基于 Spring AI 搭建的聊天助手凭借聊天记忆存储功能,能够准确关联上下文信息,针对连续提问给出符合对话逻辑的回答。无论是识别用户身份,还是追溯对话起始内容,系统都能有效利用历史消息,实现连贯且智能的交互体验,充分利用聊天记忆存储机制可以更好的维护对话上下文及提高模型回答的准确性。

目前 Spring AI (1.0.0 版本) 官方还没有提供 Redis 的聊天记忆外部存储实现,那么,下一篇文章我们将聚焦于 自定义 Redis 聊天记忆的外部存储实现,通过 自定义一个外部存储方式,自己实现一个聊天记忆存储功能,来进一步提升聊天助手的扩展性与数据持久性,让AI聊天助手在高并发、大规模对话场景中稳定运行。敬请期待接下来的深度技术解析!

文章案例项目代码:spring-ai-model-chat-memory-repository-redis

  • https://github.com/Cyanty/spring-ai-model-chat-memory-repository-redis

大数据

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/85570.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/85570.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/85570.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Element Plus 对话框 el-dialog 和 抽屉 el-drawer 的使用注意项(使用 div 包裹)

总结&#xff1a;使用 div 包裹&#xff01;&#xff01;&#xff01; 详细说明&#xff1a; 对话框 el-dialog 或 抽屉 el-drawer 样式的设置说明&#xff1a; 要想有效设置 el-dialog 或 el-drawer 的样式&#xff0c;需确保 el-dialog 或 el-drawer 的上层不是template&am…

【python】简单演示 gateway、service、client的工作原理

gateway 看起来主要是做协议转换的A gateway is a network node that acts as an entrance and exit point, connecting two networks that use different protocols. It allows data to flow between these networks, essentially acting as a translator between different c…

数据仓库面试题合集⑥

实时指标体系设计 + Flink 优化实战:面试高频问题 + 项目答题模板 面试中不仅会问“你做过实时处理吗?”,更会追问:“实时指标体系是怎么搭建的?”、“你们的 Flink 稳定性怎么保证?” 本篇聚焦实时指标体系设计与 Flink 优化场景,帮你答出架构设计力,也答出调优实战感…

Vue + AbortController 请求取消弹窗 hook 封装

背景 实际业务开发场景中&#xff0c;往往存在有些大数据请求的需求&#xff0c;一旦请求发起加载遮罩后用户就无法操作了&#xff0c;直接尬住&#xff0c;所以提供一个支持取消查询的功能还是很有必要的&#xff0c;为了在全业务接口都能使用封装一个hook。 ✋为什么要用 A…

数据结构相关

1 问题 如何辨析数据对象和数据结构&#xff1f;如何设计多种储存结构以及他们特性有什么&#xff1f;内存条和硬盘的区别&#xff1f; 2 方法 明晰俩者的定义数据对象是性质相同的有限个数据元素的集合&#xff0c;他是数据的一个子集。数据结构是指所涉及的数据元素的集合以及…

MacOS内存管理-删除冗余系统数据System Data

文章目录 一、问题复现二、解决思路三、解决流程四、附录 一、问题复现 以题主的的 Mac 为例&#xff0c;我们可以看到System Data所占数据高达77.08GB&#xff0c;远远超出系统所占内存 二、解决思路 占据大量空间的是分散在系统中各个位置Cache数据&#xff1b; 其中容量最…

纯视觉SOTA!华科小米推出ReCogDrive:结合VLM和强化学习的端到端自动驾驶框架

摘要 端到端自动驾驶的研究目前越来越火热&#xff0c;现有方法通过视觉语言模型&#xff08;VLM&#xff09;来解决其在长尾场景中性能降低的问题&#xff0c;但是仍然存在一些局限性。本文提出了ReCogDrive&#xff0c;它将VLM与基于扩散的轨迹规划器相结合&#xff0c;并且采…

MySQL慢SQL优化全攻略:从诊断到调优

目录 慢SQL日志分析与诊断 开启慢查询日志 慢查询日志分析工具 慢SQL优化策略 1. 避免SELECT * 查询 2. 创建高效索引 索引选择原则 索引使用注意事项 3. 使用EXPLAIN分析执行计划 4. 优化排序操作 5. 解决深分页问题 6. 避免全表扫描 7. 优化JOIN操作 8. 合理使用…

OPENPPP2 VMUX 技术探秘(高级指南)

&#x1f680; VMUX技术分析&#xff1a;OPENPPP2中的虚拟多路复用技术 &#x1f31f; 一、技术目标 &#x1f517; 连接多路复用 通过单个或多个物理链路&#xff0c;承载多个逻辑TCP连接。 &#x1f680; 高性能传输 支持数据包乱序重组实现动态流量控制&#xff08;拥塞检测…

Linux系统时间不对导致mysql初始化失败:Data Dictionary initialization failed.(数据字典版本验证失败)

文章目录 问题描述分析**问题原因分析****解决方案****1. 修正系统时间****2. 检查数据目录完整性****3. 重新初始化数据目录****4. 调整 MySQL 配置** **验证与后续步骤****注意事项** 其他说明 问题描述 mysql数据初始化失败&#xff0c;发现系统时间是1970年&#xff0c;我…

有趣的python程序Part1:如何根据记忆曲线使用python编写一个单词记忆默写程序

目录 前言 1. 数据管理模块 2. 记忆算法实现 3. 持久化存储 4. 用户界面实现 5.整合与测试 前言 此篇文章为“有趣的python程序”专栏的第一篇文章&#xff0c;本专栏致力于分享一些有趣的编程作品&#xff0c;如果能够使您产生兴趣&#xff0c;不妨来动手改编使之成为更好…

【案例】性能优化在持续集成与持续交付中的应用

【案例】性能优化在持续集成与持续交付中的应用 为了更好地理解性能优化在CI/CD流程中的实际应用&#xff0c;本节将结合一个典型案例&#xff0c;从代码提交到部署上线的完整流程中&#xff0c;讲解如何嵌入性能检测与自动化优化机制&#xff0c;并使用结构化流程图直观展示关…

P7 QT项目----会学天气预报(完结)

7.8 QMap 在 Qt 中&#xff0c;如果你想要将 JSON 数据解析到一个 QMap 中&#xff0c;你可以遍历 JSON 对象的所有键值对&#xff0c;并将它们添加到 QMap 里。这个方法特别适合于当你的 JSON 对象是一个简单的键值对集合时。以下是一个如何实现这一点的示例。 示例&#…

操作系统笔记(关于进程引入和状态的切换)

1.前言 今天下午结束了英语的四六级考试&#xff0c;终于是结束了&#xff0c;最近的这个考试太密集&#xff0c;周四的专业基础课考试&#xff0c;周五的这个线性代数的考试和这个周六的英语四六级考试&#xff0c;吧我都要烤焦了&#xff0c;最近也是疲于应对这个考试&#…

M1芯片macOS安装Xinference部署大模型

如果你看的是官方手册&#xff1a;安装 — Xinference 千万不要直接运行&#xff1a; pip install "xinference[all]" 会遇到几个问题&#xff1a; 1&#xff09;Python版本如果太新可能安装失败 2&#xff09;全量安装会失败 3&#xff09;未科学上网可能会time…

【ONNX量化实战】使用ONNX Runtime进行静态量化

目录 什么是量化量化实现的原理实战准备数据执行量化 验证量化结语 什么是量化 量化是一种常见的深度学习技术&#xff0c;其目的在于将原始的深度神经网络权重从高位原始位数被动态缩放至低位目标尾数。例如从FP32&#xff08;32位浮点&#xff09;量化值INT8&#xff08;8位…

【量子计算】格罗弗算法

文章目录 &#x1f50d; 一、算法原理与工作机制⚡ 二、性能优势&#xff1a;二次加速的体现&#x1f310; 三、应用场景⚠️ 四、局限性与挑战&#x1f52e; 五、未来展望&#x1f48e; 总结 格罗弗算法&#xff08;Grover’s algorithm&#xff09;是量子计算领域的核心算法之…

C++ 互斥量

在 C 中&#xff0c;互斥量&#xff08;std::mutex&#xff09;是一种用于多线程编程中保护共享资源的机制&#xff0c;防止多个线程同时访问某个资源&#xff0c;从而避免数据竞争&#xff08;data race&#xff09;和不一致的问题。 &#x1f512; 一、基础用法&#xff1a;s…

CSS Content符号编码大全

资源宝整理分享&#xff1a;​https://www.httple.net​ 前端开发中常用的特殊符号查询工具&#xff0c;包含Unicode编码和HTML实体编码&#xff0c;方便开发者快速查找和使用各种符号。支持基本形状、箭头、数学符号、货币符号等多种分类。 前端最常用符号 图标形状十进制十…

RPC常见问题回答

项目流程和架构设计 1.服务端的功能&#xff1a; 1.提供rpc调用对应的函数 2.完成服务注册 服务发现 上线/下线通知 3.提供主题的操作 (创建/删除/订阅/取消订阅) 消息的发布 2.服务的模块划分 1.网络通信模块 net 底层套用的moude库 2.应用层通信协议模块 1.序列化 反序列化数…