SpringBoot集成大模型应用开发企业级项目--【篇五:SpringBoot集成聊天记忆ChatMemory】

359 阅读7分钟

五、聊天记忆功能(Chat Memory)

1、测试对话是否有记忆

    @Autowired
    private Assistant assistant;
​
    /**
     * 1、测试聊天记忆(此方式并没有记忆功能)
     */
    @Test
    void testChatMemory() {
        String answer = assistant.chat("我是环环");
        System.out.println(answer);
​
        String answer1 = assistant.chat("我是谁");
        System.out.println(answer1);
    }

很显然,此测试对话,没有记忆功能~

2、聊天记忆的简单实现

可以使用以下测试实现简单的聊天记忆功能

    /**
     * 2、测试聊天记忆的简单实现(此时已经有了记忆功能了)
     */
    @Autowired
    private QwenChatModel qwenChatModel;
​
    void testChatMemory2() {
        // 第一轮对话
        UserMessage userMessage = UserMessage.userMessage("我是哈哈哈");
        ChatResponse chatResponse = qwenChatModel.chat(userMessage);
        AiMessage aiMessage = chatResponse.aiMessage();
        // 输出大语言模型的对话
        System.out.println(aiMessage.text());
​
        // 第二轮对话
        UserMessage userMessage1 = UserMessage.userMessage("你知道我是谁吗");
        ChatResponse chatResponse1 = qwenChatModel.chat(Arrays.asList(userMessage, aiMessage, userMessage1));
        AiMessage aiMessage1 = chatResponse1.aiMessage();
        // 输出大语言模型的对话
        System.out.println(aiMessage1.text());
    }

3、使用ChatMemory实现聊天记忆

使用AIService可以封装多轮对话的复杂性,使聊天记忆功能的实现变得简单

    /**
     * 3、使用AIService可以封装多轮对话的复杂性,使聊天记忆功能的实现变得简单
     * 使用ChatMemory实现聊天记忆功能
     */
    @Test
    void testChatMemory3() {
        // 1、创建 chatMemory
        MessageWindowChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
​
        // 2、创建AIService
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatLanguageModel(qwenChatModel)
                .chatMemory(chatMemory)
                .build();
​
        // 3、调用service的接口
        String answer = assistant.chat("我是环环");
        System.out.println(answer);
​
        String answer1 = assistant.chat("我是谁");
        System.out.println(answer1);
    }

4、使用AIService实现聊天记忆

4.1、创建记忆对话智能体

当AIService由多个组件(大模型、聊天记忆等)组成的时候,我们就称他为智能体了。

import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.service.spring.AiService;
​
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
​
/**
 * @description 有记忆功能的 AIService, 是一个初级的智能体
 */
@AiService(
        // wiringMode: 标明使用哪个大模型聊天
        wiringMode = EXPLICIT,
        // chatModel: 具体的聊天大模型
        chatModel = "qwenChatModel",
        // chatMemory: 聊天记忆功能
        chatMemory = "chatMemory"
)
public interface MemoryChatAssistant {
​
//    @UserMessage("你是我的好朋友,请用东北话回答问题,并且添加一些表情符号。 {{it}}") // 用户提示词, 这里的it是占位符(默认的)
    @UserMessage("你是我的好朋友,请用东北话回答问题,并且添加一些表情符号。 {{m}}") // 用户提示词
    String chat(@V("m") String message);
}

4.2、配置ChatMemory

import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
/**
 * @description 配置有记忆功能的AIService
 */
@Configuration
public class MemoryAssistantConfig {
​
    @Bean
    public ChatMemory chatMemory() {
        return MessageWindowChatMemory.withMaxMessages(10);
    }
}

4.3、测试记忆功能

    /**
     * 4、使用AIService可以封装多轮对话的复杂性,使聊天记忆功能的实现变得简单
     * 使用交给Spring管理的 ChatMemory 实现聊天记忆功能
     */
    @Autowired
    private MemoryChatAssistant memoryAssistant;
​
    @Test
    void testChatMemory4() {
​
        // 1、调用service的接口
        String answer = memoryAssistant.chat("我是哇哈哈哈");
        System.out.println(answer);
​
        String answer1 = memoryAssistant.chat("我是谁");
        System.out.println(answer1);
    }

5、隔离聊天记忆

为每个用户的新聊天或者不同的用户区分聊天记忆

5.1、创建记忆隔离对话智能体

import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.service.spring.AiService;
​
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
​
​
/**
 * @description 隔离聊天记忆功能的 AIService 智能体
 */
@AiService(
        // wiringMode: 标明使用哪个大模型聊天
        wiringMode = EXPLICIT,
        // chatModel: 具体的聊天大模型
        chatModel = "qwenChatModel",
        // chatMemoryProvider: 隔离聊天记忆功能的提供者
        chatMemoryProvider = "chatMemoryProvider",
        // tools: 工具类, 配置工具(Function Calling)
        tools = "calculatorTools"
)
public interface SeparateChatAssistant {
​
    /**
     * 隔离聊天记忆功能
     *
     * @param memoryId 聊天ID
     * @param message  用户信息
     */
//    @SystemMessage("你是我的好朋友,请用东北话回答问题。今天是{{current_date}}") // 系统消息提示词 prompt
    @SystemMessage(fromResource = "my-prompt-template.txt")
    // 系统消息提示词 prompt 模版配置
    String chat(@MemoryId int memoryId, @UserMessage String message);
​
​
    @UserMessage("你是我的好朋友,请用上海话回答问题。今天是{{message}}")
    String chat2(@MemoryId int memoryId, @V("message") String message);
​
    @SystemMessage(fromResource = "my-prompt-template3.txt")
    String chat3(
            @MemoryId int memoryId,
            @UserMessage String userMessage,
            @V("userName") String userName,
            @V("age") int age
    );
}

5.2、配置ChatMemoryProvider

import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.xg.store.MongoChatMemoryStore;
​
/**
 * @description 隔离聊天记忆功能的 AIService 配置
 */
@Configuration
public class SeparateChatAssistantConfig {
​
    @Autowired
    private MongoChatMemoryStore mongoChatMemoryStore;
​
    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        return memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(10)
                // chatMemoryStore: 隔离聊天记忆功能的存储
                // 两种存储:1、SingleSlotChatMemoryStore --> private List<ChatMessage> messages = new ArrayList();
                //          2、InMemoryChatMemoryStore --> private Map<Integer, List<ChatMessage>> messages = new HashMap();
//                .chatMemoryStore(new InMemoryChatMemoryStore())
                // 使用自定义的 MongoChatMemoryStore 实现聊天记忆的持久化
                .chatMemoryStore(mongoChatMemoryStore)
                .build();
    }
}

5.3、测试隔离聊天记忆功能

    /**
     * 5、使用AIService可以封装多轮对话的复杂性,使聊天记忆功能的实现变得简单
     * 聊天记忆隔离功能测试
     */
    @Autowired
    private SeparateChatAssistant separateChatAssistant;
​
    @Test
    void testChatMemory5() {
​
        // 第一组对话
        String answer = separateChatAssistant.chat(1, "我是哇哈哈哈");
        System.out.println(answer);
        String answer1 = separateChatAssistant.chat(1, "我是谁");
        System.out.println(answer1);
​
        // 第二组对话
        String answer3 = separateChatAssistant.chat(2, "我是谁");
        System.out.println(answer3);
    }

6、持久化聊天记忆功能

默认情况下,聊天记忆存储在内存中。如果需要持久化存储,可以实现一个自定义的聊天记忆存储类,以便将聊天消息存储在你选择的任何持久化存储介质中。

6.1、存储介质的选择

大模型中聊天记忆的存储选择哪种数据库,需要综合考虑数据特点、应用场景和性能要求等因素,以下是一些常见的选择及其特点:

  • MySQL

    • 特点:

      • 关系型数据库。支持事务处理,确保数据的一致性和完整性,适用于结构化数据的存储和查询。
    • 适用场景:

      • 如果聊天记忆数据结构较为规整,例如包含固定的字段如对话 ID、用户 ID、时间戳、消息内容等,且需要进行复杂的查询和统计分析,如按用户统计对话次数、按时间范围查询特定对话等,MySQL 是不错的选择。
  • Redis

    • 特点:

      • 内存数据库,读写速度极高。它适用于存储热点数据,并且支持多种数据结构,如字符串、哈希表、列表等,方便对不同类型的聊天记忆数据进行处理。
    • 适用场景:

      • 对于实时性要求极高的聊天应用,如在线客服系统或即时通讯工具,Redis 可以快速存储和获取最新的聊天记录,以提供流畅的聊天体验。
  • MongoDB

    • 特点:

      • 文档型数据库,数据以 JSON - like 的文档形式存储,具有高度的灵活性和可扩展性。它不需要预先定义严格的表结构,适合存储半结构化或非结构化的数据。
    • 适用场景:

      • 当聊天记忆中包含多样化的信息,如文本消息、图片、语音等多媒体数据,或者消息格式可能会频繁变化时,MongoDB 能很好地适应这种灵活性。例如,一些社交应用中用户可能会发送各种格式的消息,使用 MongoDB 可以方便地存储和管理这些不同类型的数据。
  • Cassandra

    • 特点:

      • 是一种分布式的 NoSQL 数据库,具有高可扩展性和高可用性,能够处理大规模的分布式数据存储和读写请求。适合存储海量的、时间序列相关的数据。
    • 适用场景:

      • 对于大型的聊天应用,尤其是用户量众多、聊天数据量巨大且需要分布式存储和处理的场景,Cassandra 能够有效地应对高并发的读写操作。例如,一些面向全球用户的社交媒体平台,其聊天数据需要在多个节点上进行分布式存储和管理,Cassandra 可以提供强大的支持。

6.2、项目中使用MongoDB

MongoDB安装以及使用忽略

6.3、持久化聊天

  • 实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
​
/**
 * @description 持久化聊天记忆的记录实体类
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
@Document("chat_messages")
public class ChatMessages {
​
    /**
     * 唯一标识,映射到 MongoDB 文档的 _id 字段
     */
    @Id
    private ObjectId messageId;
​
    /**
     * 聊天记忆的ID
     */
    private int memoryId;
​
    /**
     * 存储当前聊天记录列表的json字符串
     */
    private String content;
}
  • 创建持久化类
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import org.xg.bean.ChatMessages;
​
import java.util.LinkedList;
import java.util.List;
​
/**
 * @description MongoDB 持久化聊天记录功能,即自定义实现 ChatMemoryStore
 */
@Component
public class MongoChatMemoryStore implements ChatMemoryStore {
​
    @Autowired
    private MongoTemplate mongoTemplate;
​
    /**
     * 获取聊天记录
     *
     * @param memoryId 聊天记录的ID
     * @return 聊天记录列表
     */
    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        Criteria criteria = Criteria.where("memoryId").is(memoryId);
        Query query = new Query(criteria);
        ChatMessages chatMessages = mongoTemplate.findOne(query, ChatMessages.class);
        if (null == chatMessages) {
            return new LinkedList<>();
        }
        String contentJson = chatMessages.getContent();
        return ChatMessageDeserializer.messagesFromJson(contentJson);
    }
​
    /**
     * 更新聊天记录
     *
     * @param memoryId 聊天记录的ID
     * @param list     聊天记录列表
     */
    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> list) {
        Criteria criteria = Criteria.where("memoryId").is(memoryId);
        Query query = new Query(criteria);
​
        Update update = new Update();
        // 将聊天记录列表转化为JSON
        String messagesJson = ChatMessageSerializer.messagesToJson(list);
        update.set("content", messagesJson);
​
        mongoTemplate.upsert(query, update, ChatMessages.class);
    }
​
    /**
     * 通过聊天记录的ID,删除聊天记录
     *
     * @param memoryId 聊天记录的ID
     */
    @Override
    public void deleteMessages(Object memoryId) {
        Criteria criteria = Criteria.where("memoryId").is(memoryId);
        Query query = new Query(criteria);
        mongoTemplate.remove(query, ChatMessages.class);
    }
}
  • 测试忽略

福利:项目地址如下: gitee.com/xuegang001/…