Technology Sharing

3. LangChain4j Chat Memory

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Description of Chat Memory

        Chat MemoryIt is called chat memory, which stores historical conversations between users and big models, so that big models can use these historical conversations to understand what users have said recently and what they mean.

However, if historical conversations are stored in Chat Memory all the time, the required storage space will become larger and larger. Therefore, Chat Memory also supports extended functions such as window limitation, elimination mechanism, persistence mechanism, etc.


ChatMemory is an interface that provides two implementation classes by default:

  • MessageWindowChatMemory

According to the size of the collection, old messages are eliminated. As a sliding window, keep N The newest message is pushed in first, and old messages that no longer fit are evicted. However, since each message can contain a different number of tokens,MessageWindowChatMemory Very useful for rapid prototyping.

  • TokenWindowChatMemory

Based on the size of the token, old messages are eliminated. It also operates as a sliding window, but focuses on retaining N The latest token, and evicts older messages as needed. Messages are indivisible. If a message does not fit, it will be evicted entirely. TokenWindowChatMemory requires aTokenizer To calculate eachChatMessage Token in .


Difference between TokenWindowChatMemory and MessageWindowChatMemory

TokenWindowChatMemory is similar to MessageWindowChatMemory, but the difference is that the capacity is calculated in different ways. MessageWindowChatMemory directly takes List<ChatMessage> The size of the TokenWindowChatMemory will use the specified Tokenizer to<ChatMessage> The corresponding number of Tokens is estimated and then compared with the set maxTokens. If it exceeds maxTokens, it will be eliminated, and the oldest ChatMessage will also be eliminated.

Tokenizer is an interface. The default OpenAiTokenizer implementation class is used to estimate how many tokens a ChatMessage corresponds to. Many large model APIs charge based on the number of tokens used. Therefore, when you are sensitive to costs, it is recommended to use TokenWindowChatMemory to control the total number of tokens used in a session.

Both implementation classes have a ChatMemoryStore property inside. ChatMemoryStore is also an interface, and there is an InMemoryChatMemoryStore implementation class by default. Over time, out-of-the-box implementations will be added for popular storages such as SQL databases, document storage, etc. In the meantime, you can implement this interface to connect to any storage of your choice


Case practice

Case 1: Building a popular naming master based on ChatMemory
  1. public class NameDemo {
  2. interface NamingMaster {
  3. String talk(String desc);
  4. }
  5. public static void main(String[] args) {
  6. ChatLanguageModel chatModel = ZhipuAiChatModel.builder()
  7. .apiKey("智普apikey")
  8. .build();
  9. ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
  10. NamingMaster namingMaster = AiServices.builder(NamingMaster.class)
  11. .chatLanguageModel(chatModel)
  12. .chatMemory(chatMemory)
  13. .build();
  14. System.out.println(namingMaster.talk("我姓李,帮我取一个好听的女孩名字,就一个你觉得最好的"));
  15. System.out.println("---");
  16. System.out.println(namingMaster.talk("换一个"));
  17. }
  18. }
Case 2: Customize ChatMemoryStore to persist ChatMessage to disk

Introducing Maven dependencies

  1. <dependency>
  2. <groupId>org.mapdb</groupId>
  3. <artifactId>mapdb</artifactId>
  4. <version>3.0.9</version>
  5. <exclusions>
  6. <exclusion>
  7. <groupId>org.jetbrains.kotlin</groupId>
  8. <artifactId>kotlin-stdlib</artifactId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>

 Customize ChatMemoryStore to implement persistent storage

  1. public class PersistentChatMemoryStore implements ChatMemoryStore {
  2. private final DB db = DBMaker.fileDB("chat-memory.db").transactionEnable().make();
  3. private final Map<String, String> map = db.hashMap("messages", Serializer.STRING, Serializer.STRING).createOrOpen();
  4. @Override
  5. public List<ChatMessage> getMessages(Object memoryId) {
  6. String json = map.get((String) memoryId);
  7. return ChatMessageDeserializer.messagesFromJson(json);
  8. }
  9. @Override
  10. public void updateMessages(Object memoryId, List<ChatMessage> messages) {
  11. String json = ChatMessageSerializer.messagesToJson(messages);
  12. map.put((String) memoryId, json);
  13. db.commit();
  14. }
  15. @Override
  16. public void deleteMessages(Object memoryId) {
  17. map.remove((String) memoryId);
  18. db.commit();
  19. }
  20. }

  Code Testing

  1. public class PersistentDemo {
  2. interface NamingMaster {
  3. String talk(String desc);
  4. }
  5. public static void main(String[] args) {
  6. ChatLanguageModel chatModel = ZhipuAiChatModel.builder()
  7. .apiKey("智普apikey")
  8. .build();
  9. ChatMemory chatMemory = MessageWindowChatMemory.builder()
  10. .chatMemoryStore(new PersistentChatMemoryStore())
  11. .maxMessages(10)
  12. .build();
  13. NamingMaster namingMaster = AiServices.builder(NamingMaster.class)
  14. .chatLanguageModel(chatModel)
  15. .chatMemory(chatMemory)
  16. .build();
  17. System.out.println(namingMaster.talk("我姓李,帮我取一个好听的女孩名字,就一个你觉得最好的"));
  18. System.out.println("---");
  19. System.out.println(namingMaster.talk("换一个"));
  20. }
  21. }
Case 3: Storing chat history for each user separately
  1. public class NameDemo {
  2. interface NamingMaster {
  3. String talk(@MemoryId Integer userId, @UserMessage String desc);
  4. }
  5. public static void main(String[] args) {
  6. ChatLanguageModel chatModel = ZhipuAiChatModel.builder()
  7. .apiKey("智普apikey")
  8. .build();
  9. NamingMaster namingMaster = AiServices.builder(NamingMaster.class)
  10. .chatLanguageModel(chatModel)
  11. .chatMemoryProvider(userId -> MessageWindowChatMemory.withMaxMessages(10))
  12. .build();
  13. System.out.println(namingMaster.talk(1, "我姓李,帮我取一个好听的女孩名字,就一个你觉得最好的"));
  14. System.out.println("---");
  15. System.out.println(namingMaster.talk(2, "我姓赵,帮我取一个好听的男孩名字,就一个你觉得最好的"));
  16. System.out.println("---");
  17. System.out.println(namingMaster.talk(1, "换一个"));
  18. }
  19. }