对话记忆大型语言模型 (LLM) 是无状态的这意味着它们不会保留先前交互的信息。span stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-javaspan stylecolor:#015692Test/span span stylecolor:#015692public/span span stylecolor:#015692void/span span stylecolor:#b75501testChatOptions/span() { span stylecolor:#b75501String/span span stylecolor:#54790dcontent/span span stylecolor:#ab5656/span chatClient.prompt() .user(span stylecolor:#54790d我叫小兔子 /span) .call() .content(); System.out.println(content); System.out.println(span stylecolor:#54790d--------------------------------------------------------------------------/span); content chatClient.prompt() .user(span stylecolor:#54790d我叫什么 /span) .call() .content(); System.out.println(content); } /code/span/span那我们平常跟一些大模型聊天是怎么记住我们对话的呢实际上每次对话都需要将之前的对话消息内置发送给大模型这种方式称为多轮对话。SpringAi提供了一个ChatMemory的组件用于存储聊天记录允许您使用 LLM 跨多个交互存储和检索信息。并且可以为不同用户的多个交互之间维护上下文或状态。可以在每次对话的时候把当前聊天信息和模型的响应存储到ChatMemory 然后下一次对话把聊天记录取出来再发给大模型。span stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-java span stylecolor:#656e77//输出 名字叫徐庶/span /code/span/span但是这样做未免太麻烦 能不能简化 思考一下用我们之前的Advisor对话拦截是不是就可以不用每次手动去维护了。 并且SpringAi早已体贴的为我提供了ChatMemoryAutoConfiguration自动配置类span stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-xmlspan stylecolor:#b75501dependency/span span stylecolor:#b75501groupId/spanorg.springframework.ai/span stylecolor:#b75501groupId/span span stylecolor:#b75501artifactId/spanspring-ai-autoconfigure-model-chat-memory/span stylecolor:#b75501artifactId/span /span stylecolor:#b75501dependency/span /code/span/spanspan stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-javaspan stylecolor:#015692AutoConfiguration/span span stylecolor:#015692ConditionalOnClass({ ChatMemory.class, ChatMemoryRepository.class })/span span stylecolor:#015692public/span span stylecolor:#015692class/span span stylecolor:#b75501ChatMemoryAutoConfiguration/span { span stylecolor:#015692Bean/span span stylecolor:#015692ConditionalOnMissingBean/span ChatMemoryRepository span stylecolor:#b75501chatMemoryRepository/span() { span stylecolor:#015692return/span span stylecolor:#015692new/span span stylecolor:#b75501InMemoryChatMemoryRepository/span(); } span stylecolor:#015692Bean/span span stylecolor:#015692ConditionalOnMissingBean/span ChatMemory span stylecolor:#b75501chatMemory/span(ChatMemoryRepository chatMemoryRepository) { span stylecolor:#015692return/span MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).build(); } } /code/span/span所以我们可以这样用PromptChatMemoryAdvisorSpringAi提供了 PromptChatMemoryAdvisor 专门用于对话记忆的拦截span stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-javaspan stylecolor:#015692SpringBootTest/span span stylecolor:#015692public/span span stylecolor:#015692class/span span stylecolor:#b75501ChatMemoryTest/span { ChatClient chatClient; span stylecolor:#015692BeforeEach/span span stylecolor:#015692public/span span stylecolor:#015692void/span span stylecolor:#b75501init/span(span stylecolor:#015692Autowired/span DeepSeekChatModel chatModel, span stylecolor:#015692Autowired/span ChatMemory chatMemory) { chatClient ChatClient .builder(chatModel) .defaultAdvisors( span stylecolor:#656e77// PromptChatMemoryAdvisor拦截器 就会自动将我们与大模型的历史对话记录下来/span PromptChatMemoryAdvisor.builder(chatMemory).build() ) .build(); } span stylecolor:#015692Test/span span stylecolor:#015692public/span span stylecolor:#015692void/span span stylecolor:#b75501testChatOptions/span() { span stylecolor:#b75501String/span span stylecolor:#54790dcontent/span span stylecolor:#ab5656/span chatClient.prompt() .user(span stylecolor:#54790d我叫徐庶 /span) span stylecolor:#656e77// /span .advisors(span stylecolor:#015692new/span span stylecolor:#b75501ReReadingAdvisor/span()) .call() .content(); System.out.println(content); System.out.println(span stylecolor:#54790d--------------------------------------------------------------------------/span); content chatClient.prompt() .user(span stylecolor:#54790d我叫什么 /span) .advisors(span stylecolor:#015692new/span span stylecolor:#b75501ReReadingAdvisor/span()) .call() .content(); System.out.println(content); } } /code/span/span配置聊天记录最大存储数量你要知道 我们把聊天记录发给大模型 都是算token计数的。大模型的token是有上限了 如果你发送过多聊天记录可能就会导致token过长。如下是大模型存储的 token 历史条数上限。并且更多的token也意味更多的费用 更久的解析时间. 所以不建议太长DEFAULT_MAX_MESSAGES默认20即10次对话一旦超出DEFAULT_MAX_MESSAGES只会存最后面N条可以理解为先进先出参考MessageWindowChatMemory源码span stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-javaspan stylecolor:#015692Bean/span ChatMemory span stylecolor:#b75501chatMemory/span(span stylecolor:#015692Autowired/span ChatMemoryRepository chatMemoryRepository) { span stylecolor:#656e77// MessageWindowChatMemory 创建一个历史对话存储的配置/span span stylecolor:#015692return/span MessageWindowChatMemory .builder() .maxMessages(span stylecolor:#b7550110/span) span stylecolor:#656e77// 设置最大存储 10 条/span .chatMemoryRepository(chatMemoryRepository).build(); } /code/span/span配置多用户隔离记忆如果有多个用户在进行对话 肯定不能将对话记录混在一起 不同的用户的对话记忆需要隔离span stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-javaspan stylecolor:#015692Test/span span stylecolor:#015692public/span span stylecolor:#015692void/span span stylecolor:#b75501testChatOptions/span() { span stylecolor:#b75501String/span span stylecolor:#54790dcontent/span span stylecolor:#ab5656/span chatClient.prompt() .user(span stylecolor:#54790d我叫徐庶 /span) span stylecolor:#656e77// 注意:这里要先构建一个 ChatMemory的 Bean和上面类似这里我们设置历史对话的用户ID/span .advisors(advisorSpec - advisorSpec.param(ChatMemory.CONVERSATION_ID,span stylecolor:#54790d1/span)) .call() .content(); System.out.println(content); System.out.println(span stylecolor:#54790d--------------------------------------------------------------------------/span); content chatClient.prompt() .user(span stylecolor:#54790d我叫什么 /span) .advisors(advisorSpec - advisorSpec.param(ChatMemory.CONVERSATION_ID,span stylecolor:#54790d1/span)) .call() .content(); System.out.println(content); System.out.println(span stylecolor:#54790d--------------------------------------------------------------------------/span); content chatClient.prompt() .user(span stylecolor:#54790d我叫什么 /span) .advisors(advisorSpec - advisorSpec.param(ChatMemory.CONVERSATION_ID,span stylecolor:#54790d2/span)) .call() .content(); System.out.println(content); } /code/span/span会发现 不同的CONVERSATION_ID会有不同的记忆原理源码$主要有前置存储MessageWindowChatMemory具体存储实现ChatMemoryRepository数据库存储对话记忆默认情况 对话内容会存在jvm内存会导致一直存最终会撑爆JVM导致OOM。重启就丢了 如果已想存储到第三方存储进行持久化springAi内置提供了以下几种方式例如 Cassandra、JDBC 或 Neo4j 这里演示下JDBC方式添加依赖span stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-xml span stylecolor:#b75501dependency/span span stylecolor:#b75501groupId/spanorg.springframework.ai/span stylecolor:#b75501groupId/span span stylecolor:#b75501artifactId/spanspring-ai-starter-model-chat-memory-repository-jdbc/span stylecolor:#b75501artifactId/span /span stylecolor:#b75501dependency/span span stylecolor:#656e77!--jdbc--/span span stylecolor:#b75501dependency/span span stylecolor:#b75501groupId/spanorg.springframework.boot/span stylecolor:#b75501groupId/span span stylecolor:#b75501artifactId/spanspring-boot-starter-jdbc/span stylecolor:#b75501artifactId/span /span stylecolor:#b75501dependency/span span stylecolor:#656e77!--mysql驱动--/span span stylecolor:#b75501dependency/span span stylecolor:#b75501groupId/spancom.mysql/span stylecolor:#b75501groupId/span span stylecolor:#b75501artifactId/spanmysql-connector-j/span stylecolor:#b75501artifactId/span span stylecolor:#b75501scope/spanruntime/span stylecolor:#b75501scope/span /span stylecolor:#b75501dependency/span /code/span/span添加配置(目前我们的需要创建一个schema-mysql.sql 文件就是一个 SQL 脚本在后面有配置)SPRING_AI_CHAT_MEMORY 表存储用户的历史对话数据库我们自行定义将该数据表存储到那个数据库中即可。span stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-propertiesspan stylecolor:#015692spring.ai.chat.memory.repository.jdbc.initialize-schema/spanspan stylecolor:#54790dalways/span span stylecolor:#015692spring.ai.chat.memory.repository.jdbc.schema/spanspan stylecolor:#54790dclasspath:/schema-mysql.sql/span /code/span/span如下是 MySQL 的配置span stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-yamlspan stylecolor:#015692spring:/span span stylecolor:#015692datasource:/span span stylecolor:#015692username:/span span stylecolor:#54790droot/span span stylecolor:#015692password:/span span stylecolor:#b75501123456/span span stylecolor:#015692url:/span span stylecolor:#54790djdbc:mysql://localhost:3306/springai?characterEncodingutf8useSSLfalseserverTimezoneUTC/span span stylecolor:#015692driver-class-name:/span span stylecolor:#54790dcom.mysql.cj.jdbc.Driver/span /code/span/span配置类span stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-javaspan stylecolor:#015692Configuration/span span stylecolor:#015692public/span span stylecolor:#015692class/span span stylecolor:#b75501ChatMemoryConfig/span { span stylecolor:#015692Bean/span span stylecolor:#656e77// JdbcChatMemoryRepository 是已经被封装好自动装配好了就可以使用/span ChatMemory span stylecolor:#b75501chatMemory/span(span stylecolor:#015692Autowired/span JdbcChatMemoryRepository chatMemoryRepository) { span stylecolor:#015692return/span MessageWindowChatMemory .builder() .maxMessages(span stylecolor:#b755011/span) span stylecolor:#656e77// 设置存储为上面我们传的变量的 jdbc 的存储方式/span .chatMemoryRepository(chatMemoryRepository).build(); } } /code/span/spanresources/schema-mysql.sql目前1.0.0版本需要自己定义没有提供脚本创建这个SPRING_AI_CHAT_MEMORY 数据表来存储用户的历史对话span stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-javaCREATE TABLE IF NOT EXISTS span stylecolor:#b75501SPRING_AI_CHAT_MEMORY/span ( conversation_id VARCHAR(span stylecolor:#b7550136/span) NOT NULL, content TEXT NOT NULL, type VARCHAR(span stylecolor:#b7550110/span) NOT NULL, timestamp TIMESTAMP NOT NULL, INDEX SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX (conversation_id, timestamp) ); /code/span/span测试span stylecolor:#000000span stylebackground-color:#fefef2code classlanguage-javaspan stylecolor:#015692SpringBootTest/span span stylecolor:#015692public/span span stylecolor:#015692class/span span stylecolor:#b75501ChatMemoryTest/span { ChatClient chatClient; span stylecolor:#015692BeforeEach/span span stylecolor:#015692public/span span stylecolor:#015692void/span span stylecolor:#b75501init/span(span stylecolor:#015692Autowired/span DeepSeekChatModel chatModel, span stylecolor:#015692Autowired/span ChatMemory chatMemory) { chatClient ChatClient .builder(chatModel) .defaultAdvisors( PromptChatMemoryAdvisor.builder(chatMemory).build() ) .build(); } span stylecolor:#015692Test/span span stylecolor:#015692public/span span stylecolor:#015692void/span span stylecolor:#b75501testChatOptions/span() { span stylecolor:#b75501String/span span stylecolor:#54790dcontent/span span stylecolor:#ab5656/span chatClient.prompt() .user(span stylecolor:#54790d你好我叫徐庶/span) .advisors(span stylecolor:#015692new/span span stylecolor:#b75501ReReadingAdvisor/span()) .advisors(advisorSpec - advisorSpec.param(ChatMemory.CONVERSATION_ID,span stylecolor:#54790d1/span)) .call() .content(); System.out.println(content); System.out.println(span stylecolor:#54790d--------------------------------------/span/code/span/span