JavaAI: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的 存储方式有所不同,以下介绍五种

  1. 低级:使用ConversationalChain
  2. 高级:使用AiService
  3. 高级:使用AiService ,每一位用户聊天记录单独存储
  4. 高级:使用AiService ,使用MapDB存储聊天内容
  5. 高级:使用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();
}
### LangChain4J 流式处理概述 LangChain4J 设计之初就考虑到了流式处理的需求,这使得应用程序能够更高效地处理大规模数据。通过实现同步和异步两种模式下的流式API调用,开发者可以根据实际需求选择最合适的方案来优化性能[^2]。 对于希望利用这些特性构建应用的人来说,在Spring Boot环境中集成langchain4j并启用其流式功能是一个不错的选择。下面将展示如何在一个基于Java 17及Spring Boot 3.2+版本的新项目里配置langchain4j来进行流式操作的具体步骤。 ### 实现示例 为了更好地理解这一过程,这里给出一段简单的代码片段作为参考: ```java import dev.langchain4j.data.message.StreamingChatHistory; import dev.langchain4j.model.chat.StreamingChatResponse; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ChatController { @GetMapping("/chat/stream") public void chatStream() { StreamingChatHistory history = new StreamingChatHistory(); // 假设这里的model已经被正确初始化 model.streamChat(history, response -> { System.out.println(response.getMessage().text()); }); } } ``` 上述例子展示了如何设置一个HTTP GET请求处理器`/chat/stream`,当接收到客户端请求时会触发聊天记录的流式传输。每当有新的消息块到达服务器端时,就会被即时打印出来。 值得注意的是,除了基本的消息传递外,还可以进一步扩展此逻辑以适应更多复杂的业务场景,比如实时更新UI界面或将部分计算卸载到边缘设备上执行等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值