找回密码
 立即注册
首页 业界区 业界 Spring with AI (6): 记忆保持——会话与长期记忆 ...

Spring with AI (6): 记忆保持——会话与长期记忆

兼罔 昨天 20:31
本文代码:
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链)。
  1. @Bean
  2. ChatClient chatClient(ChatClient.Builder chatClientBuilder,
  3.     ChatMemory chatMemory,
  4.     VectorStore vectorStore) {
  5.     // 设置顾问配置项
  6.     return chatClientBuilder
  7.         .defaultAdvisors(
  8.             MessageChatMemoryAdvisor.builder(chatMemory)
  9.                 .build(),
  10.             QuestionAnswerAdvisor.builder(vectorStore)
  11.                 .searchRequest(SearchRequest.builder().build())
  12.                 .build())
  13.         .build();
  14. }
复制代码
为了防止对话内容溢出,需要控制上下文最大条数:
  1. @Bean
  2. ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
  3.     return MessageWindowChatMemory.builder()
  4.             .chatMemoryRepository(chatMemoryRepository)
  5.             .maxMessages(50) // 最大条数50,即:提问25条、回答25条
  6.             .build();
  7. }
复制代码
2 引入会话ID

2.1 Controller层引入

现实中我们当然不能把所有会话、都统一记忆;其实还是要通过会话ID进行区分(类似Session):
  1.     @PostMapping(path = "/ask", produces = "application/json")
  2.     public ChatAnswer ask(
  3.         @RequestHeader(name = "X_CONVERSATION_ID", defaultValue = "default") String conversationId,
  4.         @RequestBody ChatQuestion chatQuestion) {
  5.         return chatService.ask(chatQuestion, conversationId);
  6.     }
复制代码
2.2 Service层:引入会话ID前的简化

在修改Advisor、引入会话ID的同时,将上一章的Rules形式、改为Expression形式:
  1. package com.junteam.ai.demo.service.impl;
  2. import org.springframework.ai.chat.client.ChatClient;
  3. import org.springframework.beans.factory.annotation.Value;
  4. import org.springframework.core.io.Resource;
  5. import org.springframework.stereotype.Service;
  6. import com.junteam.ai.demo.model.ChatAnswer;
  7. import com.junteam.ai.demo.model.ChatQuestion;
  8. import com.junteam.ai.demo.service.ChatService;
  9. import static org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor.FILTER_EXPRESSION;
  10. import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
  11. @Service
  12. public class OpenAIChatServiceImpl implements ChatService {
  13.     private final ChatClient chatClient;
  14.     public OpenAIChatServiceImpl(ChatClient chatClient) {
  15.         this.chatClient = chatClient;
  16.     }
  17.     @Value("classpath:/promptTemplates/questionPromptTemplate.st")
  18.     Resource questionPromptTemplate;
  19.     @Override
  20.     public ChatAnswer ask(ChatQuestion chatQuestion, String conversationId) {
  21.         var countryTitleMatch = String.format(
  22.             "countryTitle == '%s'",
  23.             chatQuestion.title());
  24.         return chatClient
  25.             .prompt()
  26.             .system(systemSpec -> systemSpec
  27.                 .text(questionPromptTemplate)
  28.                 .param("countryTitle", chatQuestion.title()))
  29.             .advisors(advisorSpec -> advisorSpec
  30.                 .param(FILTER_EXPRESSION, countryTitleMatch)
  31.             .user(chatQuestion.question())
  32.             .call()
  33.             .entity(ChatAnswer.class);
  34.     }
  35. }
复制代码
相应的,提示词模板修改:
  1. 你是一个有用的助手,负责回答有关{countryTitle}的历史地理风俗问题。
复制代码
2.3 Service层引入会话ID

修改Service接口和实现类:
  1. package com.junteam.ai.demo.service;
  2. import com.junteam.ai.demo.model.ChatAnswer;
  3. import com.junteam.ai.demo.model.ChatQuestion;
  4. public interface ChatService {
  5.     ChatAnswer ask(ChatQuestion question, String conversationId);
  6. }
复制代码
  1. package com.junteam.ai.demo.service.impl;
  2. import org.springframework.ai.chat.client.ChatClient;
  3. import org.springframework.beans.factory.annotation.Value;
  4. import org.springframework.core.io.Resource;
  5. import org.springframework.stereotype.Service;
  6. import com.junteam.ai.demo.model.ChatAnswer;
  7. import com.junteam.ai.demo.model.ChatQuestion;
  8. import com.junteam.ai.demo.service.ChatService;
  9. import static org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor.FILTER_EXPRESSION;
  10. import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;
  11. @Service
  12. public class OpenAIChatServiceImpl implements ChatService {
  13.     private final ChatClient chatClient;
  14.     public OpenAIChatServiceImpl(ChatClient chatClient) {
  15.         this.chatClient = chatClient;
  16.     }
  17.     @Value("classpath:/promptTemplates/questionPromptTemplate.st")
  18.     Resource questionPromptTemplate;
  19.     @Override
  20.     public ChatAnswer ask(ChatQuestion chatQuestion, String conversationId) {
  21.         var countryTitleMatch = String.format(
  22.             "countryTitle == '%s'",
  23.             chatQuestion.title());
  24.         return chatClient
  25.             .prompt()
  26.             .system(systemSpec -> systemSpec
  27.                 .text(questionPromptTemplate)
  28.                 .param("countryTitle", chatQuestion.title()))
  29.             .advisors(advisorSpec -> advisorSpec
  30.                 .param(FILTER_EXPRESSION, countryTitleMatch)
  31.                 .param(CONVERSATION_ID, conversationId)) // 引入会话ID
  32.             .user(chatQuestion.question())
  33.             .call()
  34.             .entity(ChatAnswer.class);
  35.     }
  36. }
复制代码
需要注意的是:
会话内容是存储在内存中的,服务一旦重启、那么所有历史内容都会清空。
如果需要持久化存储、并且多服务节点时能共享会话内容,需要使用向量数据库等手段存储。
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方式与其他相比,需要再引入驱动:
  1. <dependency>
  2.   <groupId>com.mysql</groupId>
  3.   mysql-connector-j</artifactId>
  4. </dependency>
复制代码
3、找到相关初始SQL,再数据库执行SQL:
classpathrg/springframework/ai/chat/memory/repository/jdbc/schema-mysql.sql
1.png
  1. CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
  2.     `conversation_id` VARCHAR(36) NOT NULL,
  3.     `content` TEXT NOT NULL,
  4.     `type` ENUM('USER', 'ASSISTANT', 'SYSTEM', 'TOOL') NOT NULL,
  5.     `timestamp` TIMESTAMP NOT NULL,
  6.     INDEX `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX` (`conversation_id`, `timestamp`)
  7. );
复制代码
3.1.2 数据库配置

配置文件添加数据库地址:
  1. spring:
  2.   # 数据库配置
  3.   datasource:
  4.     driver-class-name: com.mysql.cj.jdbc.Driver
  5.     url: jdbc:mysql://${DB_SERVER}:3306/chat_db
  6.     username: ${DB_USERNAME}
  7.     password: ${DB_PASSWORD}
复制代码
3.1.3 JAVA代码适配

JAVA代码适配、全部通过ChatMemoryRepository的@Bean注入(AiConfig类);
JDBC也特殊一点,需要自定义数据库方言(比如MySQL/PostgreSQL/Oracle/SQLServer等)
  1. @Bean
  2. ChatMemoryRepository chatMemoryRepository(DataSource dataSource) {
  3.     return JdbcChatMemoryRepository.builder()
  4.         .dialect(new MysqlChatMemoryRepositoryDialect())
  5.         .dataSource(dataSource)
  6.         .build();
  7. }
复制代码
Service实现类不需要进行变更。
3.2 VectorStoreChatMemoryAdvisor实现持久化

spring-ai-advisors-vector-store依赖里已经包含相关Advisor,直接修改AiConfig即可;
这不需要额外的数据源,是简洁方便的方式:
  1. package com.junteam.ai.demo.config;
  2. import org.springframework.ai.chat.client.ChatClient;
  3. import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
  4. import org.springframework.ai.chat.client.advisor.vectorstore.VectorStoreChatMemoryAdvisor;
  5. import org.springframework.ai.vectorstore.SearchRequest;
  6. import org.springframework.ai.vectorstore.VectorStore;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. @Configuration
  10. public class AiConfig {
  11.     @Bean
  12.     ChatClient chatClient(ChatClient.Builder chatClientBuilder,
  13.         VectorStore vectorStore) {
  14.         // 设置顾问配置项
  15.         return chatClientBuilder
  16.             .defaultAdvisors(
  17.                 VectorStoreChatMemoryAdvisor
  18.                     .builder(vectorStore)
  19.                     .build(),
  20.                 QuestionAnswerAdvisor
  21.                     .builder(vectorStore)
  22.                     .searchRequest(SearchRequest.builder().build())
  23.                     .build())
  24.             .build();
  25.     }
  26. }
  27. ···
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册