AI Services:聊天记忆

在 LangChain4j 中,AI 服务(AI Services)可以利用聊天记忆(Chat Memory)来“记住”之前的互动。

注意,聊天记忆详细介绍请参考“聊天记忆(Chat Memory)”部分。

单个 ChatMemory

整个会话程序仅仅提供了一个 ChatMemory 实例,所有用户共享,如下:

package com.hxstrive.langchain4j.aiServices;

import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;

public class ChatMemoryGlobalDemo {
    // 推荐:将OPEN_API_KEY设置成环境变量, 避免硬编码或随着代码泄露
    // 注意,设置完环境变量记得重启IDEA,不然可能环境变量不会生效
    private static final String API_KEY = System.getenv("OPEN_API_KEY");

    // 定义业务接口
    interface Assistant {
        // userMessage 是用户输入的消息
        // 返回值是 AI 回复的内容呢
        String chat(String userMessage);
    }

    public static void main(String[] args) {
        // 创建 ChatModel 实现类(OpenAI 为例)
        ChatModel chatModel = OpenAiChatModel.builder()
                .baseUrl("https://api.xty.app/v1")
                .apiKey(API_KEY)
                .modelName("gpt-3.5-turbo")
                .temperature(0.7)
                .logRequests(true)
                .logResponses(false)
                .build();

        // 使用 AiServices 创建服务
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(chatModel)
                // 设置一个默认的 ChatMemory,最多能存储10条消息
                .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
                .build();

        // 发起对话
        String answer = assistant.chat("你好! 我的名字是张三。");
        System.out.println(answer);

        answer = assistant.chat("我叫什么名字?");
        System.out.println(answer);
    }
}

运行示例,输出日志如下:

09:51:35.168 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
  "model" : "gpt-3.5-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "你好! 我的名字是张三。"
  } ],
  "temperature" : 0.7,
  "stream" : false
}

你好,张三!😊  
很高兴认识你~今天想聊点什么,或者有什么我可以帮你的?
09:51:39.763 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
  "model" : "gpt-3.5-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "你好! 我的名字是张三。"
  }, {
    "role" : "assistant",
    "content" : "你好,张三!😊  \n很高兴认识你~今天想聊点什么,或者有什么我可以帮你的?"
  }, {
    "role" : "user",
    "content" : "我叫什么名字?"
  } ],
  "temperature" : 0.7,
  "stream" : false
}

你叫**张三**。

Process finished with exit code 0

在这种情况下,同一个 ChatMemory 实例将被用于 AI 服务的所有调用。但是,如果有多个用户,这种方法就行不通了,因为每个用户都需要自己的 ChatMemory 实例来维护他们各自的对话。这个问题的解决方案是使用 ChatMemoryProvider。

每个用户配备 ChatMemory

为了解决多用户聊天场景,我们可以使用 ChatMemoryProvider 根据聊天记忆 ID 为每个用户配备一个 ChatMemory,这样用户之间的聊天记忆就不会混乱。

示例如下:

package com.hxstrive.langchain4j.aiServices;

import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;

public class ChatMemoryEveryOneDemo {
    // 推荐:将OPEN_API_KEY设置成环境变量, 避免硬编码或随着代码泄露
    // 注意,设置完环境变量记得重启IDEA,不然可能环境变量不会生效
    private static final String API_KEY = System.getenv("OPEN_API_KEY");

    // 定义业务接口
    interface Assistant {
        // userMessage 是用户输入的消息
        // 返回值是 AI 回复的内容呢
        String chat(@MemoryId int memoryId, @UserMessage String userMessage);
    }

    public static void main(String[] args) {
        // 创建 ChatModel 实现类(OpenAI 为例)
        ChatModel chatModel = OpenAiChatModel.builder()
                .baseUrl("https://api.xty.app/v1")
                .apiKey(API_KEY)
                .modelName("gpt-3.5-turbo")
                .temperature(0.7)
                .logRequests(true)
                .logResponses(false)
                .build();

        // 使用 AiServices 创建服务
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(chatModel)
                //  为每个 memoryId 返回一个 ChatMemory,而不是公用一个
                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
                .build();

        // 发起对话
        System.out.println(assistant.chat(1, "你好! 我的名字是张三。"));
        System.out.println(assistant.chat(2, "你好! 我的名字是李四。"));

        System.out.println("用户1:" + assistant.chat(1,"我叫什么名字?"));
        System.out.println("用户2:" + assistant.chat(2,"我叫什么名字?"));
    }
}

运行示例,输出日志如下:

10:00:22.719 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
  "model" : "gpt-3.5-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "你好! 我的名字是张三。"
  } ],
  "temperature" : 0.7,
  "stream" : false
}

你好,张三!😊  

很高兴认识你。今天有什么我可以帮你的吗?

10:00:33.699 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
  "model" : "gpt-3.5-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "你好! 我的名字是李四。"
  } ],
  "temperature" : 0.7,
  "stream" : false
}

你好,李四!很高兴认识你 😊  

今天想聊点什么?

10:00:35.992 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
  "model" : "gpt-3.5-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "你好! 我的名字是张三。"
  }, {
    "role" : "assistant",
    "content" : "你好,张三!😊  \n\n很高兴认识你。今天有什么我可以帮你的吗?"
  }, {
    "role" : "user",
    "content" : "我叫什么名字?"
  } ],
  "temperature" : 0.7,
  "stream" : false
}

用户1:你叫 **张三**。🙂

10:00:38.691 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
  "model" : "gpt-3.5-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "你好! 我的名字是李四。"
  }, {
    "role" : "assistant",
    "content" : "你好,李四!很高兴认识你 😊  \n\n今天想聊点什么?"
  }, {
    "role" : "user",
    "content" : "我叫什么名字?"
  } ],
  "temperature" : 0.7,
  "stream" : false
}

用户2:你叫**李四**。

在这种情况下,ChatMemory 的两个不同实例将由 ChatMemoryProvider 提供,每个聊天记忆 ID(memoryId)对应一个实例。

清理 ChatMemory

上面为每个聊天记忆 ID 分配一个不同的 ChatMemory 实例,为避免内存泄漏,清除不再需要的 ChatMemory 也很重要。要使 AI 服务内部使用的 ChatMemory 可被访问,只需让定义该服务的接口继承 ChatMemoryAccess 接口即可:

// 定义业务接口
interface Assistant extends ChatMemoryAccess {
    // userMessage 是用户输入的消息
    // 返回值是 AI 回复的内容呢
    String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}

这就使得既可以访问单个对话的 ChatMemory 实例,又可以在对话终止时将其清除:

String answerToKlaus = assistant.chat(1, "你好,我叫克劳斯");
String answerToFrancine = assistant.chat(2, "你好,我叫弗朗辛");

// 获取 memoryId 为 1 的 ChatMemory 中的消息列表
List<ChatMessage> messagesWithKlaus = assistant.getChatMemory(1).messages();

// 清除 memoryId 为 2 聊天记忆
boolean chatMemoryWithFrancineEvicted = assistant.evictChatMemory(2);

示例如下:

package com.hxstrive.langchain4j.aiServices;

import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.memory.ChatMemoryAccess;
import java.util.List;

public class ChatMemoryCleanDemo {
    // 推荐:将OPEN_API_KEY设置成环境变量, 避免硬编码或随着代码泄露
    // 注意,设置完环境变量记得重启IDEA,不然可能环境变量不会生效
    private static final String API_KEY = System.getenv("OPEN_API_KEY");

    // 定义业务接口
    interface Assistant extends ChatMemoryAccess {
        // userMessage 是用户输入的消息
        // 返回值是 AI 回复的内容呢
        String chat(@MemoryId int memoryId, @UserMessage String userMessage);
    }

    public static void main(String[] args) {
        // 创建 ChatModel 实现类(OpenAI 为例)
        ChatModel chatModel = OpenAiChatModel.builder()
                .baseUrl("https://api.xty.app/v1")
                .apiKey(API_KEY)
                .modelName("gpt-3.5-turbo")
                .temperature(0.7)
                .logRequests(true)
                .logResponses(false)
                .build();

        // 使用 AiServices 创建服务
        Assistant assistant = AiServices.builder(Assistant.class)
                .chatModel(chatModel)
                //  为每个 memoryId 返回一个 ChatMemory,而不是公用一个
                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
                .build();

        // 发起对话
        System.out.println(assistant.chat(1, "你好! 我的名字是张三。"));
        System.out.println(assistant.chat(2, "你好! 我的名字是李四。"));

        // 获取 memoryId 为 1 的 ChatMemory 中的消息列表
        List<ChatMessage> messages = assistant.getChatMemory(1).messages();
        for(ChatMessage message : messages) {
            System.out.println("memoryId=1, message=" + message);
        }

        // // 清除 memoryId 为 2 聊天记忆
        assistant.evictChatMemory(2);

        System.out.println("用户1:" + assistant.chat(1,"我叫什么名字?"));
        System.out.println("用户2:" + assistant.chat(2,"我叫什么名字?"));
    }
}

运行示例,输出日志如下:

10:13:22.210 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
  "model" : "gpt-3.5-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "你好! 我的名字是张三。"
  } ],
  "temperature" : 0.7,
  "stream" : false
}

你好,张三!很高兴认识你 😊  

今天想聊点什么?

10:13:29.367 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
  "model" : "gpt-3.5-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "你好! 我的名字是李四。"
  } ],
  "temperature" : 0.7,
  "stream" : false
}

你好,李四!很高兴认识你 😊  

有什么我可以帮你的吗?

memoryId=1, message=UserMessage { name = null, contents = [TextContent { text = "你好! 我的名字是张三。" }], attributes = {} }
memoryId=1, message=AiMessage { text = "你好,张三!很高兴认识你 😊  

今天想聊点什么?", thinking = null, toolExecutionRequests = [], attributes = {} }

10:13:31.617 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
  "model" : "gpt-3.5-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "你好! 我的名字是张三。"
  }, {
    "role" : "assistant",
    "content" : "你好,张三!很高兴认识你 😊  \n\n今天想聊点什么?"
  }, {
    "role" : "user",
    "content" : "我叫什么名字?"
  } ],
  "temperature" : 0.7,
  "stream" : false
}

用户1:你叫**张三**呀 😄

10:13:35.306 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
  "model" : "gpt-3.5-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "我叫什么名字?"
  } ],
  "temperature" : 0.7,
  "stream" : false
}

用户2:我不知道你的名字,除非你告诉我。如果你愿意,可以告诉我你想让我怎么称呼你。

注意:

  • 如果 AI 服务方法没有带有 @MemoryId 注解的参数,那么 ChatMemoryProvider 中的 memoryId值将默认是字符串 "default"。

  • 不应针对相同的 @MemoryId 并发调用 AI 服务,因为这可能导致 ChatMemory 损坏。目前,AI 服务没有实现任何机制来防止针对相同 @MemoryId 的并发调用。

更多 LangChain4j 知识请阅读后续教程……

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号