手动传递历史记忆

  • 先来思考一个问题,我们向AI提问,不能一直是一次性的吧?
    AI回答我们之后,是不是我们要基于这个问题继续讨论?
    所以我们需要把之前的对话传给AI
  • 代码使用最基础的openAI
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
        <version>1.0.0-beta3</version>
    </dependency>
    
  • java
    import com.qi.code.ApiKey;
    import dev.langchain4j.data.message.AiMessage;
    import dev.langchain4j.data.message.ChatMessage;
    import dev.langchain4j.data.message.UserMessage;
    import dev.langchain4j.model.chat.ChatLanguageModel;
    import dev.langchain4j.model.chat.response.ChatResponse;
    import dev.langchain4j.model.openai.OpenAiChatModel;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Main {
    
        public static void main(String[] args) {
    
            ChatLanguageModel model = OpenAiChatModel
                    .builder()
                    .baseUrl("http://langchain4j.dev/demo/openai/v1")
                    .apiKey(ApiKey.DEMO)
                    .modelName("gpt-4o-mini")
                    .build();
    
            UserMessage userMessage1 = UserMessage.userMessage("你好,我是qi");
            ChatResponse response1 = model.chat(userMessage1);
            AiMessage aiMessage1 = response1.aiMessage(); // 大模型的第一次响应
            System.out.println(aiMessage1.text());
            System.out.println("----");
    
            // 下面一行代码是重点
            ChatResponse response2 = model.chat(userMessage1, aiMessage1, UserMessage.userMessage("我叫什么"));
            AiMessage aiMessage2 = response2.aiMessage(); // 大模型的第二次响应
            System.out.println(aiMessage2.text());
        }
    }
    
    • 这里的ApiKey.DEMO = “demo”;
  • 运行,AI就会基于上次的对话进行思考并回答
    在这里插入图片描述
  • 看一下刚才调用的 chat 方法,它位于ChatLanguageModel接口中
    在这里插入图片描述
    • 这个方法接受的是ChatResponse,ChatResponse可以简单理解为每次说话的内容。

    • 这个方法可以传入无数次的对话,可以看到下方有另一个接收形式的方法:
      在这里插入图片描述

    • 所以我们可以把全部历史对话都放到一个集合里,再调用方法即可:

      import com.qi.code.ApiKey;
      import dev.langchain4j.data.message.AiMessage;
      import dev.langchain4j.data.message.ChatMessage;
      import dev.langchain4j.data.message.UserMessage;
      import dev.langchain4j.model.chat.ChatLanguageModel;
      import dev.langchain4j.model.chat.response.ChatResponse;
      import dev.langchain4j.model.openai.OpenAiChatModel;
      import java.util.ArrayList;
      import java.util.List;
      
      public class Main {
      
          public static void main(String[] args) {
      
              ChatLanguageModel model = OpenAiChatModel
                      .builder()
                      .baseUrl("http://langchain4j.dev/demo/openai/v1")
                      .apiKey(ApiKey.DEMO)
                      .modelName("gpt-4o-mini")
                      .build();
      
              UserMessage userMessage1 = UserMessage.userMessage("你好,我是qi");
              ChatResponse response1 = model.chat(userMessage1);
              AiMessage aiMessage1 = response1.aiMessage(); // 大模型的第一次响应
              System.out.println(aiMessage1.text());
              System.out.println("----");
      
              // 下面一行代码是重点
      //        ChatResponse response2 = model.chat(userMessage1, aiMessage1, UserMessage.userMessage("我叫什么"));
      //        AiMessage aiMessage2 = response2.aiMessage(); // 大模型的第二次响应
      //        System.out.println(aiMessage2.text());
      
              List<ChatMessage> history = new ArrayList<>();
              history.add(userMessage1);
              history.add(aiMessage1);
              history.add(UserMessage.userMessage("我叫什么"));
              ChatResponse response3 = model.chat(history);
              AiMessage aiMessage3 = response3.aiMessage(); // 大模型的第二次响应
              System.out.println(aiMessage3.text());
          }
      }
      
      
  • 现在想想,是不是可以实现AI的对话记忆了?
    确实是,但是好像还是很麻烦,需要手动维护历史对话。
    没事,LangChain4j提供了简单的方法。

ChatMemory记忆对话

  • 需要引入langchain4j的核心依赖包,以及之前的SpringBoot相关的依赖
    	<dependencies>
    		<!-- langchain4j核心 -->
    	<dependency>
    		<groupId>dev.langchain4j</groupId>
    		<artifactId>langchain4j</artifactId>
    		<version>1.0.0-beta3</version>
    	</dependency>
        
        <!-- SpringBoot 核心包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- SpringBoot Web容器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 阿里云百炼 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
            <version>1.0.0-beta3</version>
        </dependency>
        <!-- webflux -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
    </dependencies>
    
  • application.yml
    langchain4j:
      community:
        dashscope:
          chatModel:
            api-key: "sk-xxxxxxxxxx"
            model-name: qwen-plus
          streaming-chat-model:
            api-key: "sk-xxxxxxxxx"
            model-name: qwen-plus
    
  • 创建config配置类:AiConfig
    import dev.langchain4j.memory.ChatMemory;
    import dev.langchain4j.memory.chat.MessageWindowChatMemory;
    import dev.langchain4j.model.chat.ChatLanguageModel;
    import dev.langchain4j.model.chat.StreamingChatLanguageModel;
    import dev.langchain4j.service.AiServices;
    import dev.langchain4j.service.TokenStream;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class AiConfig {
    
        public interface Assistant {
            String chat(String message);
            // 流式响应
            TokenStream stream(String message);
        }
    
        @Bean
        public Assistant assistant(ChatLanguageModel qwenChatModel,
                                   StreamingChatLanguageModel qwenStreamingChatModel) {
            ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
    
    
            Assistant assistant = AiServices.builder(Assistant.class)
                    .chatLanguageModel(qwenChatModel)
                    .streamingChatLanguageModel(qwenStreamingChatModel)
                    .chatMemory(chatMemory)
                    .build();
    
            return  assistant;
        }
    }
    
    • 其实就是通过AiServices创建了代理,将原本调用的方法进行了代理。
    • 在对话开始前以及结束后,帮我们把对话内容保存起来
  • 编写接口:MemoryController
    @RequestMapping("/Memory")
    @RestController
    public class MemoryController {
    
        @Autowired
        AiConfig.Assistant assistant;
    
        @RequestMapping(value = "/chat_test")
        public String test1(@RequestParam("message") String message) {
            return assistant.chat(message);
        }
    
        @RequestMapping(value = "/stream_test",produces ="text/stream;charset=UTF-8")
        public Flux<String> memoryStreamChat(@RequestParam("message") String message, HttpServletResponse response) {
            TokenStream stream = assistant.stream(message);
    
            return Flux.create(sink -> {
                stream.onPartialResponse(s -> sink.next(s))
                        .onCompleteResponse(c -> sink.complete())
                        .onError(sink::error)
                        .start();
    
            });
        }
    
    }
    
  • 此时调用接口,先调用一个,告诉ai自己的名字
    在这里插入图片描述
  • 此时调用另一个方法,问AI自己的名字,测试它有没有记忆
    在这里插入图片描述

对话隔离

  • 有些时候,我们不希望将之前的对话给到AI,想让他从0开始个思考。

  • 重新创建一下Config

    @Configuration
    public class AiIsolationConfig {
    
        public interface AssistantUnique {
            String chat(@MemoryId int memoryId, @UserMessage String userMessage);
            // 流式响应
            TokenStream stream(@MemoryId int memoryId, @UserMessage String userMessage);
        }
    
        @Bean
        public AssistantUnique assistantUnique(ChatLanguageModel qwenChatModel,
                                               StreamingChatLanguageModel qwenStreamingChatModel) {
    
            AssistantUnique assistant = AiServices.builder(AssistantUnique.class)
                    .chatLanguageModel(qwenChatModel)
                    .streamingChatLanguageModel(qwenStreamingChatModel)
                    .chatMemoryProvider(memoryId ->
                            MessageWindowChatMemory.builder().maxMessages(10)
                                    .id(memoryId).build()
                    )
                    .build();
    
            return assistant;
        }
    
    }
    
  • 编写接口代码

     @Autowired
        AiIsolationConfig.AssistantUnique assistantUnique;
    
        @RequestMapping(value = "/unique_chat_test")
        public String test1(@RequestParam("message") String message, @RequestParam("sessionId") Integer sessionId) {
            return assistantUnique.chat(sessionId, message);
        }
    
  • 此时可以根据不同的记忆(会话)ID,去灵活对话了
    在这里插入图片描述
    在这里插入图片描述

  • 连续对话
    在这里插入图片描述
    在这里插入图片描述

记忆持久化

  • 看一下对话存储的位置
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    • 存放在一个map中的,说明是在内存中存储,后续可以替换到数据库中。
    • 可以看到这是ChatMemoryStore的实现类,说明我们只要自定义一个实现类,实现接口的方法,即可自定义记忆存储。
  • 定义一个ChatMemoryStore的实现类:DBChatMemoryStore
    import dev.langchain4j.data.message.ChatMessage;
    import dev.langchain4j.store.memory.chat.ChatMemoryStore;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class DBChatMemoryStore implements ChatMemoryStore {
    
    	//模拟一个数据库
        private final Map<Integer, List<ChatMessage>> db =new HashMap<>();
    
    
        @Override
        public List<ChatMessage> getMessages(Object o) {
            System.out.println("根据memoryId,从数据库中查询");
            return null != db.get(o) ? db.get(o) : new ArrayList<>();
        }
    
        @Override
        public void updateMessages(Object o, List<ChatMessage> list) {
            db.put((Integer) o, list);
            System.out.println("根据memoryId,向数据库中新增/修改记忆对话");
        }
    
        @Override
        public void deleteMessages(Object o) {
            db.remove(o);
            System.out.println("根据memoryId,删除对话");
        }
    }
    
  • 创建config配置类:AiAssistantDBConfig
    import dev.langchain4j.memory.chat.ChatMemoryProvider;
    import dev.langchain4j.memory.chat.MessageWindowChatMemory;
    import dev.langchain4j.model.chat.ChatLanguageModel;
    import dev.langchain4j.model.chat.StreamingChatLanguageModel;
    import dev.langchain4j.service.AiServices;
    import dev.langchain4j.service.MemoryId;
    import dev.langchain4j.service.TokenStream;
    import dev.langchain4j.service.UserMessage;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class AiAssistantDBConfig {
    
        public interface AiAssistantDB {
            String chat(@MemoryId int memoryId, @UserMessage String userMessage);
            // 流式响应
            TokenStream stream(@MemoryId int memoryId, @UserMessage String userMessage);
    
        }
        @Bean
        public AiAssistantDB aiAssistantDB(ChatLanguageModel qwenChatModel,
                                                                      StreamingChatLanguageModel qwenStreamingChatModel) {
    
            DBChatMemoryStore store = new DBChatMemoryStore();
    
            ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
                    .id(memoryId)
                    .maxMessages(10) //最多储存记忆数
                    .chatMemoryStore(store)
                    .build();
    
            AiAssistantDB assistant = AiServices.builder(AiAssistantDB.class)
                    .chatLanguageModel(qwenChatModel)
                    .streamingChatLanguageModel(qwenStreamingChatModel)
                    .chatMemoryProvider(memoryId ->
                            MessageWindowChatMemory.builder().maxMessages(10)
                                    .id(memoryId).build()
                    )
                    .chatMemoryProvider(chatMemoryProvider)
                    .build();
            return assistant;
        }
    
    }
    
  • 编写接口
    import com.qi.config.AiAssistantDBConfig;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    @RequestMapping("/DB")
    @RestController
    public class DbController {
    
        @Autowired
        AiAssistantDBConfig.AiAssistantDB aiAssistantDB;
    
        @RequestMapping(value = "/test1")
        public String test1(@RequestParam("message") String message, @RequestParam("sessionId") Integer sessionId) {
            return aiAssistantDB.chat(sessionId, message);
        }
        
    }
    
  • 第一次调用接口:http://127.0.0.1:8080/DB/test1?message=你是谁&sessionId=1
    • 可以看到他会先查询历史记忆,但此时数据库是空的,所以返回了一个空的集合;
      在这里插入图片描述
    • 然后会把我们的提问对话作为第一条记忆,写入数据库
      在这里插入图片描述
    • 插入后,会再从数据中读取一遍,然后交给AI
      在这里插入图片描述
    • AI思考完成后,会去再一次调用数据库查询
      在这里插入图片描述
    • 然后,会把这次AI的回答写入数据库
      在这里插入图片描述
    • 控制台的打印记录
      在这里插入图片描述
  • 第二次调用接口,他会依次重复上面的步骤
    在这里插入图片描述
  • 当达到我们设置的最大记忆数(10)之后
    在这里插入图片描述
    • 接下来每新增一条,最会移除最早的一条记录
      在这里插入图片描述
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐