LangChain4j(5)——聊天记忆的多用户支持和持久化存储

支持多用户

上篇文章中,我们使用Chat Memory实现了对聊天内容的储存,但是,其中的代码案例只支持存储一个用户的聊天数据。实际应用中,我们的系统肯定是多用户的,每个用户都需要维护各自的聊天数据。为了解决这个问题,我们可以使用ChatMemoryProvider实现,ChatMemoryProvider的源码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package dev.langchain4j.memory.chat;

import dev.langchain4j.memory.ChatMemory;

@FunctionalInterface
public interface ChatMemoryProvider {
    ChatMemory get(Object var1);
}

从源码中,我们可以看到,ChatMemoryProvider中仅包含一个get方法,我们可以使用用户id做为该方法的参数。

支持多用户的代码如下:

package com.renr.langchain4jnew.app;

import com.renr.langchain4jnew.constant.CommonConstants;
import dev.langchain4j.community.model.zhipu.ZhipuAiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;

import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class App7 {

    public static void main(String[] args) {

        // 表示将聊天的上下文信息存储到内容中
        InMemoryChatMemoryStore memoryStore = new InMemoryChatMemoryStore();
        // 创建对象,通过lambda表达式重写其中的get方法
        ChatMemoryProvider chatMemoryProvider = userId -> MessageWindowChatMemory.builder()
                .id(userId)
                .maxMessages(10)
                .chatMemoryStore(memoryStore)
                .build();

        // 创建智谱的模型对象
        ZhipuAiChatModel zhipuAiChatModel = ZhipuAiChatModel.builder()
                // 模型key
                .apiKey(CommonConstants.API_KEY)
                // 精确度
                .temperature(0.9)
                .model("GLM-4-Flash")
                .maxRetries(3)
                .callTimeout(Duration.ofSeconds(60))
                .connectTimeout(Duration.ofSeconds(60))
                .writeTimeout(Duration.ofSeconds(60))
                .readTimeout(Duration.ofSeconds(60))
                // 请求的日志
                //.logRequests(true)
                // 响应数据的日志
                //.logResponses(true)
                .build();

        // 用户发送的消息
        UserMessage firstUserMessage = UserMessage.from("你好,我叫张三");
        // 将问题添加到chat memory
        // 假设用户id是111,根据chatMemoryProvider.get(111)获取该用户的ChatMemory对象
        chatMemoryProvider.get(111).add(firstUserMessage);

        // 发送消息时,直接通过ChatMemory获取聊天的上下文
        AiMessage firstAiMessage = zhipuAiChatModel.chat(chatMemoryProvider.get(111).messages()).aiMessage();
        System.out.println("第一次回答:" + firstAiMessage.text());
        // 将回答的内容添加到chat memory
        chatMemoryProvider.get(111).add(firstAiMessage);

        UserMessage secondUserMessage = UserMessage.from("请问我叫什么名字?");
        chatMemoryProvider.get(111).add(secondUserMessage);


        AiMessage secondAiMessage = zhipuAiChatModel.chat(chatMemoryProvider.get(111).messages()).aiMessage();
        System.out.println("第二次回答:" + secondAiMessage.text());
        chatMemoryProvider.get(111).add(secondAiMessage);
    }


}


本例中,我们将上下文信息存储在InMemoryChatMemoryStore对象中。我们创建了一个ChatMemoryProvider对象,并且通过lambda表达式作为参数,用于返回ChatMemory对象。

在创建MessageWindowChatMemory对象时,对其指定一个id属性(本例使用用户id表示)。

发送聊天消息时,需要先根据用户id获取ChatMemory对象,然后将消息内容添加到ChatMemory中。本例使用的用户id是111,实际业务中需要通过相关的业务逻辑获取用户id。

持久化存储

上面的例子中,我们将聊天信息是存储在内存中的,如果需要进行持久化存储,用户只需要实现ChatMemoryStore接口即可。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package dev.langchain4j.store.memory.chat;

import dev.langchain4j.data.message.ChatMessage;
import java.util.List;

public interface ChatMemoryStore {
    List<ChatMessage> getMessages(Object var1);

    void updateMessages(Object var1, List<ChatMessage> var2);

    void deleteMessages(Object var1);
}

该接口包含3个方法:

getMessage:根据用户id获取聊天数据列表,当调用ChatMemoryProvider的get方法时,会自动调用该方法。

updateMessages:向聊天列表中添加聊天数据,或者聊天列表达到上限需要淘汰数据时,会自动调用该方法。我们可以在这个方法中进行聊天数据的持久化存储。

deleteMessages:根据用户id删除聊天列表。

本例使用mysql存储聊天数据,使用hutool进行数据库的crud操作

mysql中新建表

导入jar

相对于之前的jar,增加了mysql驱动和hutool的jar。

<dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j</artifactId>
            <version>1.0.0-beta2</version>
        </dependency>

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-community-zhipu-ai</artifactId>
            <version>1.0.0-beta2</version>
        </dependency>

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
            <version>1.0.0-beta2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.30</version>
        </dependency>

resource目录下增加db.setting文件

该文件中我们指定了连接数据库的驱动、用户名和密码登信息。hutool的数据库操作工具类,会自动加载该文件。

## db.setting文件
url = jdbc:mysql://localhost:3306/db2406
user = root
pass = root
## 可选配置
# 是否在日志中显示执行的SQL
showSql = true
# 是否格式化显示的SQL
formatSql = false
# 是否显示SQL参数
showParams = true

测试代码

package com.renr.langchain4jnew.app;

import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import com.renr.langchain4jnew.constant.CommonConstants;
import dev.langchain4j.community.model.zhipu.ZhipuAiChatModel;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import lombok.SneakyThrows;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

import static dev.langchain4j.data.message.ChatMessageDeserializer.messagesFromJson;
import static dev.langchain4j.data.message.ChatMessageSerializer.messagesToJson;


public class App8 {

    public static void main(String[] args) {

        // 创建自定义持久化类对象
        PersistentChatMemoryStore store = new PersistentChatMemoryStore();
        // 通过chatMemoryStore(store)指定持久化对象
        ChatMemoryProvider chatMemoryProvider = userId -> MessageWindowChatMemory.builder()
                .id(userId)
                .maxMessages(10)
                .chatMemoryStore(store)
                .build();

        // 创建智谱的模型对象
        ZhipuAiChatModel zhipuAiChatModel = ZhipuAiChatModel.builder()
                // 模型key
                .apiKey(CommonConstants.API_KEY)
                // 精确度
                .temperature(0.9)
                .model("GLM-4-Flash")
                .maxRetries(3)
                .callTimeout(Duration.ofSeconds(60))
                .connectTimeout(Duration.ofSeconds(60))
                .writeTimeout(Duration.ofSeconds(60))
                .readTimeout(Duration.ofSeconds(60))
                // 请求的日志
                //.logRequests(true)
                // 响应数据的日志
                //.logResponses(true)
                .build();

        // 删除原来的用户聊天数据
        store.deleteMessages(111);

        // 用户发送的消息
        UserMessage firstUserMessage = UserMessage.from("你好,我叫张三");
        // 将问题添加到chat memory
        chatMemoryProvider.get(111).add(firstUserMessage);
        // 发送消息时,直接通过ChatMemory获取聊天的上下文
        AiMessage firstAiMessage = zhipuAiChatModel.chat(chatMemoryProvider.get(111).messages()).aiMessage();
        System.out.println("第一次回答:" + firstAiMessage.text());
        chatMemoryProvider.get(111).add(firstAiMessage);

        // 用户发送的消息
        UserMessage secondUserMessage = UserMessage.from("请问我叫什么名字?");
        chatMemoryProvider.get(111).add(secondUserMessage);
        // ai返回的额消息,这里将之前的消息也一并发送,ai根据上下文返回合适的信息
        AiMessage secondAiMessage = zhipuAiChatModel.chat(chatMemoryProvider.get(111).messages()).aiMessage();
        System.out.println("第二次回答:" + secondAiMessage.text());
        chatMemoryProvider.get(111).add(secondAiMessage);

    }

    // 自定义实现持久化的类
    static class PersistentChatMemoryStore implements ChatMemoryStore {

        @SneakyThrows
        @Override
        public List<ChatMessage> getMessages(Object memoryId) {
            Entity chatMsg = Db.use().get(Entity.create("chat_msg").set("uid", memoryId));
            if (chatMsg != null) {
                String message = chatMsg.getStr("message");
                List<ChatMessage> chatMessages = messagesFromJson(message);
                return chatMessages;
            } else {
                return new ArrayList<>();
            }
        }

        @SneakyThrows
        @Override
        public void updateMessages(Object memoryId, List<ChatMessage> messages) {
            Entity chatMsg = Db.use().get(Entity.create("chat_msg").set("uid", memoryId));
            if (chatMsg != null) {
                Db.use().update(
                        Entity.create().set("message", messagesToJson(messages)), //修改的数据
                        Entity.create("chat_msg").set("uid", memoryId) //where条件
                );
            } else {
                Db.use().insert(
                        Entity.create("chat_msg")
                                .set("uid", memoryId)
                                .set("message", messagesToJson(messages))
                );
            }
        }

        @SneakyThrows
        @Override
        public void deleteMessages(Object memoryId) {
            Db.use().del(
                    Entity.create("chat_msg").set("uid", memoryId)//where条件
            );
        }
    }

}


注意:Db.use()是hutool中提供的操作数据库的方法,关于该类的使用,我们可以才考hutool的官网,本文不再赘述。

数据库(Hutool-db) | Hutool

执行后查看数据库数据

通过表中数据,可以看到聊天数据进行了正确的持久化。

需要说明的是,按照本例的写法,每次运行程序时都会先清空原来的聊天数据,并且按照json形式在一条记录中存储所有聊天记录。如果需要存储所有的聊天历史记录,读者可以自行对自定义的持久化类进行进一步的修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值