当前位置: 首页 > news >正文

Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成

一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和扩展。欢迎感兴趣的小伙伴们关注和 Star。

项目地址:https://github.com/java-ai-tech/spring-ai-summary

更多文章:https://mp.weixin.qq.com/s/72omFtMqinJs4MMX41MCyw

前言

在构建智能对话系统时,保持对话上下文的连贯性是提升用户体验的关键。Spring AI 框架提供了强大的 Chat Memory 机制,支持多种存储方式来持久化对话历史。本文将深入解析 Spring AI Chat Memory 的核心机制,并通过实际代码演示如何实现基于本地内存(Local)和数据库(JDBC)的两种存储方案。

Spring AI Chat Memory 核心机制

架构概览 Architecture Overview

Spring AI Chat Memory 采用分层架构设计:

┌─────────────────────────────────────┐
│           ChatClient Layer          │
├─────────────────────────────────────┤
│         ChatMemory Advisor          │
├─────────────────────────────────────┤
│         ChatMemory Interface        │
├─────────────────────────────────────┤
│      ChatMemoryRepository Layer     │
├─────────────────────────────────────┤
│    Storage Layer (Local/JDBC)       │
└─────────────────────────────────────┘

核心组件解析

  1. ChatMemory 接口:提供统一的对话记忆管理抽象
  2. ChatMemoryRepository:负责底层存储操作
  3. MessageChatMemoryAdvisor:基于 Advisor 模式的透明化处理
  4. MessageWindowChatMemory:支持消息窗口限制的实现

实现方案一:Local Memory (本地内存存储)

依赖配置

<!-- pom.xml -->
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

Step 1: 创建 ChatClient 配置

@Configuration
public class ChatClientConfigs {@Beanpublic ChatClient chatClient(OpenAiChatModel chatModel, ChatMemory chatMemory) {return ChatClient.builder(chatModel).defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()).defaultSystem("You are deepseek chat bot, you answer questions in a concise and accurate manner.").build();}
}

关键点解析

  • MessageChatMemoryAdvisor:采用 Advisor 模式,自动处理消息的存储和检索
  • defaultAdvisors:为 ChatClient 配置默认的 advisor,使 memory 功能透明化

Step 2: 实现 ChatMemoryService

@Service
public class ChatMemoryService {// 模拟一个会话 IDprivate static final String CONVERSATION_ID = "naming-20250528";@Autowiredprivate ChatClient chatClient;/*** 基于 Advisor 模式的聊天方法* ChatClient 会自动处理消息的存储和检索* * @param message 用户输入消息* @param conversationId 对话会话ID,如果为null则使用默认ID* @return AI的响应内容*/public String chat(String message, String conversationId) {String answer = this.chatClient.prompt().user(message)// 关键:通过 advisor 参数指定对话ID.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId == null ? CONVERSATION_ID : conversationId)).call().content();return answer;}
}

核心机制

  • 通过 ChatMemory.CONVERSATION_ID 参数指定对话会话 ID
  • ChatClient 自动从 memory 中检索历史消息并添加到 prompt 中
  • 响应后自动将对话记录存储到 memory 中

Step 3: 配置应用属性

# application.properties
spring.application.name=spring-ai-chat-memory-local
server.port=8083
spring.profiles.active=deepseek# DeepSeek API 配置
spring.ai.openai.api-key=${spring.ai.openai.api-key}
spring.ai.openai.chat.base-url=https://api.deepseek.com
spring.ai.openai.chat.completions-path=/v1/chat/completions
spring.ai.openai.chat.options.model=deepseek-chat

Local Memory 优缺点

优点

  • 配置简单,开箱即用
  • 响应速度快,无网络延迟
  • 适合开发和测试环境

缺点

  • 数据不持久化,重启后丢失
  • 不支持多实例间共享
  • 内存使用量随对话量增长

实现方案二:JDBC Memory (数据库存储)

依赖配置

<!-- pom.xml -->
<dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency>
</dependencies>

Step 1: 数据库表结构

-- schema-mysql.sql
CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (conversation_id VARCHAR(36) NOT NULL,content TEXT NOT NULL,type VARCHAR(10) NOT NULL,`timestamp` TIMESTAMP NOT NULL,CONSTRAINT TYPE_CHECK CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'))
);CREATE INDEX IF NOT EXISTS SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX
ON SPRING_AI_CHAT_MEMORY(conversation_id, `timestamp`);

表结构解析

  • conversation_id:对话会话标识,支持多会话隔离
  • content:消息内容
  • type:消息类型(用户、助手、系统、工具)
  • timestamp:时间戳,用于消息排序
  • 复合索引:优化按会话 ID 和时间的查询性能

Step 2: 实现 ChatMemoryService

@Service
public class ChatMemoryService {@Autowiredprivate ChatModel chatModel;@Autowiredprivate JdbcChatMemoryRepository chatMemoryRepository;private ChatMemory chatMemory;@PostConstructpublic void init() {this.chatMemory = MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).maxMessages(20)  // 限制消息窗口大小.build();}public String call(String message, String conversationId) {// 1. 创建用户消息UserMessage userMessage = new UserMessage(message);// 2. 存储用户消息到 memorythis.chatMemory.add(conversationId, userMessage);// 3. 从 memory 获取对话历史List<Message> messages = chatMemory.get(conversationId);// 4. 调用 ChatModel 生成响应ChatResponse response = chatModel.call(new Prompt(messages));// 5. 存储 AI 响应到 memorychatMemory.add(conversationId, response.getResult().getOutput());return response.getResult().getOutput().getText();}
}

核心机制

  • MessageWindowChatMemory:支持消息窗口限制的内存实现
  • maxMessages:控制保留的最大消息数量,避免 token 超限
  • 手动管理消息的存储和检索流程

Step 3: 配置数据源

# application.properties
spring.application.name=spring-ai-chat-memory-jdbc
server.port=8083# JDBC Memory Repository 配置
spring.ai.chat.memory.repository.jdbc.initialize-schema=always
spring.ai.chat.memory.repository.jdbc.schema=classpath:schema-@@platform@@.sql
spring.ai.chat.memory.repository.jdbc.platform=mysql# MySQL 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/spring_ai_chat_memory?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=${spring.datasource.password}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

JDBC Memory 优缺点

优点

  • 数据持久化,支持服务重启
  • 支持多实例间共享对话历史
  • 可扩展性强,支持大规模应用
  • 支持复杂查询和数据分析

缺点

  • 配置相对复杂
  • 存在网络延迟
  • 需要维护数据库

运行效果演示

Local Memory 运行日志

第一轮对话:
用户: hello, my name is glmapper
AI: Hello glmapper! Nice to meet you. How can I help you today?第二轮对话:
用户: do you remember my name?
AI: Yes, I remember! Your name is glmapper. Is there anything specific you'd like to discuss?

JDBC Memory 数据库记录

SELECT * FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = 'test-naming-202505281800';| conversation_id           | content                    | type      | timestamp           |
|--------------------------|----------------------------|-----------|--------------------|
| test-naming-202505281800 | hello, my name is glmapper | USER      | 2025-01-20 10:30:15 |
| test-naming-202505281800 | Hello glmapper! Nice to... | ASSISTANT | 2025-01-20 10:30:16 |
| test-naming-202505281800 | do you remember my name?   | USER      | 2025-01-20 10:31:20 |
| test-naming-202505281800 | Yes, I remember! Your...  | ASSISTANT | 2025-01-20 10:31:21 |

实战测试验证

测试对话连续性

@Test
@DisplayName("测试聊天记忆功能 - 上下文保持")
void testChatMemoryContextRetention() {String CONVERSATION_ID = "test-naming-202505281800";// 第一轮对话:自我介绍String firstMessage = "hello, my name is glmapper";String firstResponse = chatMemoryService.call(firstMessage, CONVERSATION_ID);// 第二轮对话:询问之前提到的信息String secondMessage = "do you remember my name?";String secondResponse = chatMemoryService.call(secondMessage, CONVERSATION_ID);// 验证AI是否记住了用户的名字assertTrue(secondResponse.contains("glmapper"), "AI 应该记住用户的名字");
}

测试对话隔离性

@Test
@DisplayName("测试对话ID的非一致性")
void testConversationIdNonConsistency() {String CONVERSATION_ID1 = "test-naming-202505281801";String CONVERSATION_ID2 = "test-naming-202505281802";String message1 = "请记住这个数字:12345";String message2 = "刚才我说的数字是什么?";String response1 = chatMemoryService.call(message1, CONVERSATION_ID1);String response2 = chatMemoryService.call(message2, CONVERSATION_ID2);// 验证不同对话ID间的隔离性assertFalse(response2.contains("12345"), "不同对话ID应该相互隔离");
}

方案对比与选择建议

特性Local MemoryJDBC Memory
数据持久化
配置复杂度
性能
多实例共享
扩展性
适用场景开发/测试生产环境

选择建议

  • 开发/测试阶段:使用 Local Memory,快速验证功能
  • 生产环境:使用 JDBC Memory,确保数据可靠性
  • 高并发场景:考虑使用 Redis 等缓存方案
  • 企业级应用:JDBC + 数据库集群方案

常见问题与解决方案

Q1: 为什么 AI 记不住之前的对话?

A: 检查对话 ID 是否一致,确保在同一会话中使用相同的 conversationId

Q2: JDBC Memory 初始化失败?

A: 确认数据库连接正常,检查 spring.ai.chat.memory.repository.jdbc.initialize-schema=always 配置。

Q3: 对话历史过长导致 Token 超限?

A: 设置合适的 maxMessages 参数限制消息窗口大小。

this.chatMemory = MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).maxMessages(10)  // 根据模型 token 限制调整.build();

最佳实践

  1. 会话 ID 管理:使用 UUID 或有意义的业务标识,建议格式:user_{userId}_{timestamp}
  2. 消息窗口控制:根据模型 token 限制合理设置 maxMessages(通常 10-20 条)
  3. 异常处理:实现 memory 操作的容错机制,避免单点故障
  4. 性能优化:使用数据库连接池,为高频查询字段建立索引
  5. 数据清理:定期清理过期对话数据,避免数据库膨胀
  6. 监控告警:监控 memory 操作的延迟和错误率

总结

Spring AI Chat Memory 提供了灵活的对话记忆管理能力,通过 Local 和 JDBC 两种存储方案,可以满足从开发测试到生产部署的不同需求。Local Memory 适合快速原型开发,而 JDBC Memory 则适合需要数据持久化的生产环境。

理解其核心机制和实现细节,有助于开发者根据实际场景选择合适的方案,构建出高质量的智能对话应用。


项目地址: spring-ai-summary

相关文档:

  • Spring AI Documentation
  • ChatMemory API Reference
http://www.xdnf.cn/news/958339.html

相关文章:

  • YoloV8改进策略:Block改进|FCM,特征互补映射模块|AAAI 2025|即插即用
  • 【三方库研读】facebook/folly中File类原理与作用深度解析
  • PydanticAI快速入门示例
  • JS手写代码篇----使用Promise封装AJAX请求
  • 内网im,局域网环境下BeeWorks 如何保障数据安全?
  • MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
  • 基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
  • GraphRAG优化新思路-开源的ROGRAG框架
  • python训练营打卡第49天
  • 三元组 题解
  • 日志的具体使用
  • deepseek+coze开发的智能体页面
  • 链表的实现与介绍
  • codeforces C. Cool Partition
  • X86架构离线环境安装Ollama
  • DPC密度峰值聚类
  • 【MPC-C++】qpOASES 源码编译与链接,编译器设置细节
  • bond配置与拆卸
  • 理解OpenFOAM案例中的blockMesh文件里的simpleGrading
  • 【AI论文】CASS:Nvidia到AMD的数据、模型和基准测试的转换
  • 应对无法定位程序输入点kernel32.dll错误的详尽指南:从问题分析到解决方案
  • 如何迁移Cordova应用到HarmonyOS 5 以及迁移时常见的问题?
  • RK3288项目(四)--linux内核之V4L2框架及ov9281驱动分析(中)
  • 深入理解Optional:处理空指针异常
  • upload-labs文件上传_文件上传漏洞_靶场
  • 【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
  • 易学探索助手-个人记录(十四)
  • 【使用LLM搭建系统】5 处理输入: 链式 Prompt Chaining Prompts
  • 运行vue项目报错 errors and 0 warnings potentially fixable with the `--fix` option.
  • React hook之useRef