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

Spring AI 初步集成(2)-添加记忆

添加记忆

大语言模型(LLMs)是无状态的,这意味着它们不会保留关于先前交互的信息。当你希望在多次交互中保持上下文或状态时,这可能会成为一个限制。为了解决这个问题,Spring AI提供了保存上下文功能,定义为Chat Memory,允许你在与LLM的多次交互中存储和检索信息。

但是在此之前,我们需要理解,Chat Memory并不等于Chat History。

  • Chat Memory:大型语言模型在整个对话过程中保留并用于保持语境感知的信息。
  • Chat History:整个对话历史,包括用户与模型之间交换的所有消息,通常用于审计或回顾。

添加Chat Memory

Spring AI会自动配置一个ChatMemory Bean,我们可以在应用程序中直接使用。默认情况下,它使用内存中的存储库(InMemoryChatMemoryRepository)来存储消息,并使用MessageWindowChatMemory实现来管理对话历史。

修改src/main/java/com/example/canaan/config/ai/ChatConfiguration.java文件,在构建ChatClient的实例时添加Chat Memory配置:

package com.chestnut.canaan.config.ai; import jakarta.annotation.Resource; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ChatConfiguration { @Resource private ChatMemory chatMemory; @Bean public ChatClient chatClient(ChatClient.Builder builder) { return builder .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) .defaultSystem("you are a friendly assistant!").build(); } }

在这个配置中,我们使用了MessageChatMemoryAdvisor,它会将ChatMemory与ChatClient关联起来。这样,每次调用ChatClient时,都会自动使用Chat Memory。因为我们没有额外的配置,所以默认使用InMemoryChatMemoryRepository来存储消息。InMemoryChatMemoryRepository会将消息存在ConcurrentHashMap中。每次使用chatClient的相关方法时,Chat Memory会自动更新。

测试效果:

目前所有的请求都共用同一份Chat Memory,这意味着不同的用户之间的Chat Memory是共享的。如果我们希望每个对话有独立的Chat Memory,我们可以在每次对话时传入一个conversationId来区分不同的对话。

修改src/main/java/com/example/canaan/service/AiService.javasrc/main/java/com/example/canaan/service/impl/AiServiceImpl.java文件,添加一个conversationId参数:

package com.chestnut.canaan.service; import reactor.core.publisher.Flux; public interface AiService { Flux<String> chat(String input,String conversationId); }

package com.chestnut.canaan.service.impl; import com.chestnut.canaan.service.AiService; import jakarta.annotation.Resource; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; @Service public class AiServiceImpl implements AiService { @Resource private ChatClient chatClient; @Override public Flux<String> chat(String input,String conversationId) { return chatClient.prompt() .advisors(it -> it.param(ChatMemory.CONVERSATION_ID, conversationId)) .user(input).stream().content(); } }

现在,为了快速检测结果,我们修改一下控制器src/main/java/com/example/canaan/controller/AiController.java,使其支持传入conversationId参数:

package com.chestnut.canaan.controller; import com.chestnut.canaan.service.AiService; import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; @RestController @RequestMapping("/ai") public class AiController { @Resource private AiService aiService; @GetMapping("/chat") public Flux<String> chat(String input,String conversationId) { return aiService.chat(input,conversationId); } }

现在,我们可以通过访问/ai/chat?input=问题&conversationId=对话ID来与模型进行交互。每个不同的conversationId将会有独立的Chat Memory。

记忆持久化

目前,Chat Memory是存储在内存中的,这意味着当应用程序重启时,所有的Chat Memory都会丢失。为了持久化Chat Memory,我们可以使用数据库或其他持久化存储。

Spring AI提供了ChatMemoryRepository抽象来存储Chat Memory,并提供了内置存储库及其使用方法。如有需要,我们也可以实现自己的存储库。以下是几种内置存储库:

  • InMemoryChatMemoryRepository:默认的内存存储库。
  • JdbcChatMemoryRepository:使用JDBC将消息存储在关系型数据库中。支持多种数据库,开箱即用,适用于需要持久存储Chat Memory的应用程序。
  • CassandraChatMemoryRepository: 使用Apache Cassandra来存储消息。适用于需要持久存储Chat Memory的应用程序,尤其适用于对可用性、持久性、扩展性有要求,以及需要利用生存时间(TTL)功能的场景。
  • Neo4j ChatMemoryRepository:使用Neo4j将Chat Memory作为节点和关系存储在属性图数据库中。适用于希望利用Neo4j的图功能来持久化Chat Memory的应用程序。

使用JDBC存储Chat Memory

我们将使用JDBC存储Chat Memory。数据库可以是MySQL、PostgreSQL等关系型数据库。以下是使用MySQL的步骤:

首先,我们需要添加依赖项。在build.gradle文件的dependencies中添加以下依赖:

dependencies { // JDBC memory repository 核心依赖 implementation 'org.springframework.ai:spring-ai-starter-model-chat-memory-repository-jdbc' // MySQL JDBC驱动 runtimeOnly 'com.mysql:mysql-connector-j' }

其次,我们需要新建一个数据库,名称随意,我取名assists:

CREATE DATABASE `assists` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';

配置数据库连接。在src/main/resources/application.yml文件中添加以下配置:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://${PERSONAL_SERVER_IP}:3306/assists username: ${DATABASE_USERNAME} password: ${DATABASE_PASSWORD} ai: chat: memory: repository: jdbc: platform: mariadb initialize-schema: always

其中spring.datasource.*配置的是数据库相关信息

spring.ai.chat.memory.repository.jdbc.platform使用mariadb而不是mysql,是因为默认的数据表初始化文件放在依赖包classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-@@platform@@.sql位置,但是我们可以看到,在此目录下并不存在数据表初始化文件schema-mysql.sql

因为mariadb兼容MySQL,所以我们可以使用mariadb的初始化脚本。或者我们也可以通过spring.ai.chat.memory.repository.jdbc.schema手动指定脚本位置。

默认情况下,初始化存储Chat Memory表SPRING_AI_CHAT_MEMORY仅对嵌入式数据库(H2、HSQL、Derby等)生效,所以我们需要使用spring.ai.chat.memory.repository.jdbc.initialize-schema来让初始化脚本对MySQL生效。

因为Spring AI 会自动配置一个ChatMemory Bean,所以我们不需要手动配置ChatMemory。Spring AI会根据spring.ai.chat.memory.repository.jdbc的配置来创建一个JdbcChatMemoryRepository实例,并以此为基础创建一个ChatMemory实例。并且由于有了jdbc配置,InMemoryChatMemoryRepository将不会被创建。

最后,启动服务,在数据库assists下面应该已经出现了数据表SPRING_AI_CHAT_MEMORY

重新调用接口/ai/chat?input=问题&conversationId=对话ID,你会发现Chat Memory已经被存储在数据库中了。每次调用接口时,Chat Memory都会被更新。

总结

在本章中,我们了解了如何为Spring AI应用程序添加Chat Memory。我们了解了Chat Memory的概念,并通过配置ChatClient来启用Chat Memory功能。我们还知道了如何使用JDBC存储Chat Memory,以便在应用程序重启后仍然对话仍然可用。通过这些步骤,我们可以在多次交互中保持上下文或状态,从而使应用程序更加智能和人性化。

http://www.cnnetsun.cn/news/1385327.html

相关文章:

  • 终极缓动函数指南:从命名规范到实战应用的完整教程
  • PolarCTF 2025冬季赛Crypto题目精解:从自定义群运算到离散对数攻击
  • 手办卖家看过来:如何用Nano Banana零成本生成‘开箱测评’级产品图?(避坑指南)
  • Labview与欧姆龙PLC通过FINS tcp协议通讯那些事儿
  • 若依微服务实战:从零构建Nacos版Ruoyi-Cloud前后端分离项目
  • 肿瘤微环境分析新选择:BayesPrism与CIBERSORTx的深度对比测试(附数据集)
  • Win10微软输入法隐藏技巧:除了全拼双拼切换,这些高效设置你可能也没开
  • 从解码到共生:AI驱动的脑机接口如何重塑人机交互新范式
  • 从复高斯到非中心卡方:一个通信工程师必须知道的概率分布转换
  • fnOS Docker一键部署Guovin/TV iptv指南:Compose文件保姆级配置
  • 如何正确使用Dagger Singleton:确保依赖对象全局唯一的完整指南
  • 告别枯燥路线图:用免费工具Google Maps和ScreenToGif打造动态演示的3个创意用法
  • QMCDump:让QQ音乐加密文件解码不再受限于平台
  • deepseek-r1本地部署实战:从零到推理的完整流程
  • Realistic Vision V5.1 Streamlit界面响应速度优化:异步加载与缓存机制实践
  • SiameseAOE中文-base生产环境验证:日均处理10万+条评论的稳定性报告
  • BERT文本分割-中文-通用领域实战案例:提升下游NLP任务性能的关键预处理
  • 国产替代方案:经纬恒润INTEWORK-DDC工具链实战评测(ODX 2.2.0)
  • PCIe流量控制机制详解:如何避免数据丢失与提升传输效率
  • 架构之MySQL集群复制模式对比分析
  • Qwen3-0.6B-FP8实际作品:100+语言翻译对比与专业术语一致性验证
  • Vector 日志收集工具:如何利用 Rust 实现 10 倍性能提升
  • 【数电实战】从移位寄存器到计数器:时序逻辑电路核心模块设计与应用解析
  • EPLAN P8电气设计10个高频问题解决指南(附详细操作截图)
  • 让前主体性蒙尘:学术研究中被遗忘的源初场域
  • Python实战:weixin库对接微信支付全流程(附避坑指南)
  • 不用驱动器!S7-200SMART定时器玩转四相步进电机:3档调速+正反转实战
  • 漫画脸描述生成效果展示:角色关系设定(CP/敌对/师徒)提示词扩展能力
  • C++ queue容器适配器-队列
  • Vivado用户必看:Notepad--代码对比功能实战(含2.0版本新特性)