在 LangChain4j 中,AI 服务(AI Services)可以利用聊天记忆(Chat Memory)来“记住”之前的互动。
注意,聊天记忆详细介绍请参考“聊天记忆(Chat Memory)”部分。
整个会话程序仅仅提供了一个 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。
为了解决多用户聊天场景,我们可以使用 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)对应一个实例。
上面为每个聊天记忆 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 知识请阅读后续教程……