LangChain4j学习(二)
前提:使用LangChain4j + SpringBoot + DashScope通义千问
JavaAI:LangChain4j学习(一) 集成SpringBoot和阿里通义千问DashScope
基础聊天
使用基础 API
ChatMessage
目前有四种类型的聊天消息
UserMessage:来自用户的消息。 用户可以是应用程序的最终用户 (人类) 或应用程序本身。 根据 LLM 支持的形式,可以包含文本 或其他方式。
AiMessage:由 AI 生成的消息,通常是AI模型接收消息后的响应结果,如果是聊天,则响应为回复的消息,如果是工具调用,则效应为工具调用的具体信息 。
SystemMessage:来自系统的消息。 通常位于对话的开头,作为开发人员应该定义此消息的内容。 通常会写LLM 在对话中的角色的说明, 它应该如何表现,以何种方式回答等。
CustomMessage:可以包含任意属性的自定义消息。此消息类型只能由支持它的实现使用(目前只有 Ollama)。
注意:此处的UserMessage导包为 import dev.langchain4j.data.message.UserMessage;
public String MultipleChatMessage( String message) {
ChatLanguageModel model = QwenChatModel.builder()
.apiKey("xxxxxxxxxxxxxxxxxxx")
.modelName("qwen-plus")
.build();
UserMessage firstUserMessage = UserMessage.from("你好,我是小可");
AiMessage firstAiMessage = model.chat(firstUserMessage).aiMessage();
UserMessage secondUserMessage = UserMessage.from("我是谁?");
AiMessage secondAiMessage = model.chat(firstUserMessage, firstAiMessage, secondUserMessage).aiMessage();
return model.chat(message);
}
记忆存储
基础API 和 高级API的 存储方式有所不同,以下介绍五种
- 低级:使用ConversationalChain
- 高级:使用AiService
- 高级:使用AiService ,每一位用户聊天记录单独存储
- 高级:使用AiService ,使用MapDB存储聊天内容
- 高级:使用AiService ,每一位用户聊天记录单独存储,使用MapDB存储聊天内容
使用ConversationalChain
优点:可以完全控制聊天记录,决定是否要将特定消息添加到内存中
如果需要,可以在保存之前处理/修改消息
public void memoryMaxToken1(){
ChatMemory chatMemory = TokenWindowChatMemory.withMaxTokens(300,new QwenTokenizer(apiKey, modelName));
ChatLanguageModel model = QwenChatModel.builder()
.modelName(modelName)
.build();
chatMemory.add(userMessage("你好,我是小可"));
AiMessage ans = model.chat(chatMemory.messages()).aiMessage();
logger.info("ans:{}",ans.text());
chatMemory.add(ans);
chatMemory.add(userMessage("我是谁"));
AiMessage ansWithName = model.chat(chatMemory.messages()).aiMessage();
logger.info("ans:{}",ans.text());
chatMemory.add(ans);
}
使用AiService
如果使用AiService需要langchain4j版本大于等于1.0.0-beta2
且需要在pom.xml中导入依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>${langchain4j.version}</version>
</dependency>
代码如下:
interface Assistant1 {
String chat(String message);
}
public void memoryAiService1(){
ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
ChatLanguageModel model = QwenChatModel.builder()
.modelName(modelName)
.build();
Assistant1 assistant = AiServices.builder(Assistant1.class)
.chatLanguageModel(model)
.chatMemory(chatMemory)
.build();
String answer = assistant.chat("你好,我是小可");
System.out.println(answer); // Hello Klaus! How can I assist you today?
String answerWithName = assistant.chat("我是谁");
System.out.println(answerWithName); // Your name is Klaus.
}
使用AiService , 每一位用户聊天记录单独存储
注意:Assistant2 中 UserMessage 的导包为 import dev.langchain4j.service.UserMessage;
interface Assistant2 {
String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}
public void memoryAiService2ForEachUser(){
ChatLanguageModel model = QwenChatModel.builder()
.modelName(modelName)
.build();
Assistant2 assistant = AiServices.builder(Assistant2.class)
.chatLanguageModel(model)
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
.build();
System.out.println(assistant.chat(1, "你好,我是小可"));
// 你好小可!
System.out.println(assistant.chat(2, "你好,我是月"));
// 你好,月!
System.out.println(assistant.chat(1, "我是谁"));
// 你的名字是小可
System.out.println(assistant.chat(2, "我是谁"));
// 你的名字是月
}
使用AiService ,使用MapDB存储聊天内容
注意:db中的STRING导包为 import static org.mapdb.Serializer.STRING;·
public void memoryAiServicePersistent1(){
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.maxMessages(10)
.chatMemoryStore(new PersistentChatMemoryStore1())
.build();
ChatLanguageModel model = QwenChatModel.builder()
.modelName(modelName)
.build();
Assistant1 assistant = AiServices.builder(Assistant1.class)
.chatLanguageModel(model)
.chatMemory(chatMemory)
.build();
String answer = assistant.chat("你好,我是小可");
System.out.println(answer);
//第二次询问时,只需要执行下面两行代码,上面两行无须执行
// String answerWithName = assistant.chat("我是谁?");
// System.out.println(answerWithName); // 你是小可
}
static class PersistentChatMemoryStore1 implements ChatMemoryStore {
private final DB db = DBMaker.fileDB("chat-memory.db").transactionEnable().make();
private final Map<String, String> map = db.hashMap("messages", STRING, STRING).createOrOpen();
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String json = map.get((String) memoryId);
return messagesFromJson(json);
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
String json = messagesToJson(messages);
map.put((String) memoryId, json);
db.commit();
}
@Override
public void deleteMessages(Object memoryId) {
map.remove((String) memoryId);
db.commit();
}
}
使用AiService ,使用MapDB存储聊天内容,每一位用户聊天记录单独存储
public void memoryAiServicePersistentForEach2(){
PersistentChatMemoryStore2 store = new PersistentChatMemoryStore2();
ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(10)
.chatMemoryStore(store)
.build();
ChatLanguageModel model = QwenChatModel.builder()
.modelName(modelName)
.build();
Assistant2 assistant = AiServices.builder(Assistant2.class)
.chatLanguageModel(model)
.chatMemoryProvider(chatMemoryProvider)
.build();
System.out.println(assistant.chat(1, "Hello, my name is Klaus"));
System.out.println(assistant.chat(2, "Hi, my name is Francine"));
// Now, comment out the two lines above, uncomment the two lines below, and run again.
// System.out.println(assistant.chat(1, "What is my name?"));
// System.out.println(assistant.chat(2, "What is my name?"));
}
static class PersistentChatMemoryStore2 implements ChatMemoryStore {
private final DB db = DBMaker.fileDB("multi-user-chat-memory.db").transactionEnable().make();
private final Map<Integer, String> map = db.hashMap("messages", INTEGER, STRING).createOrOpen();
@Override
public List<ChatMessage> getMessages(Object memoryId) {
String json = map.get((int) memoryId);
return messagesFromJson(json);
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
String json = messagesToJson(messages);
map.put((int) memoryId, json);
db.commit();
}
@Override
public void deleteMessages(Object memoryId) {
map.remove((int) memoryId);
db.commit();
}
}
流式输出
流式输出也分为低级LLM API 和 高级AI Service的API使用方式
使用低级LLM API
代码中重点为onPartialResponse,流式输出为多次响应而不是等待生成整个文本,每次得到响应时执行onPartialResponse。
public static void main(String[] args) {
StreamingChatLanguageModel model = QwenStreamingChatModel.builder()
.apiKey("xxxxxxxxxxx")
.modelName(modelName)
.build();
List<ChatMessage> messages = asList(
systemMessage("你是个幽默的人"),
userMessage("讲个笑话")
);
model.chat(messages, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
System.out.println("流式输出新的响应: '" + partialResponse + "'");
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
System.out.println("流式输出结束: " + completeResponse);
}
@Override
public void onError(Throwable error) {
error.printStackTrace();
}
});
}
更简洁
流式传输响应的一种更简洁的方法是使用 class,自定义一个static方法,用于如何处理响应
LambdaStreamingResponseHandlerStreamingChatResponseHandleronPartialResponse()
然后通过静态方法来创建 using lambda 表达式,使用 lambda 表达式调用 static 方法
import static dev.langchain4j.model.LambdaStreamingResponseHandler.onPartialResponse;
model.chat("讲个笑话", onPartialResponse(System.out::print));
使用高级AI Service
处理方式万变不离其宗
这里将所有的处理都设为pintln
interface Assistant3 {
TokenStream chat(String message);
}
public static void main(String[] args) {
StreamingChatLanguageModel model = QwenStreamingChatModel.builder()
.modelName(modelName)
.build();
Assistant3 assistant = AiServices.create(Assistant3.class, model);
TokenStream tokenStream = assistant.chat("讲个笑话");
tokenStream.onPartialResponse(System.out::println)
.onCompleteResponse(System.out::println)
.onError(Throwable::printStackTrace)
.start();
}