Java开发ChatGPT应用入门(二)

ChatGPT对话

 首先,ChatGPT对话功能分为流式与非流式,一般来说,流式更为常见。流式就是接收ChatGPT回答的信息时,是一段一段的,有体验过ChatGPT的同学应该深有体会,流式对话响应到前端的对话框时,吐字是一个个吐。

 非流式对话,则是一次性回答,直接返回它的文本。

 两者中,流式对话的响应及体验更佳,当然,这也取决于你的应用场景,若是交互式的,选择流式,若是类似于机器人业务的,选择非流式即可。

 这两种对话模式分别对应了两种不同的OpenAI客户端。OpenAiClientOpenAiStreamClient,顾名思义,分别对应非流式和流式的。

 我们之前的准备工作中,创建的是非流式对话的客户端,流式对话的客户端不支持绘图和语音转文字的功能。因此,我们先以非流式的为例。

非流式对话

 我们使用的SDK对话模式支持复杂的与简单的提问方式,复杂的就是可以对问题描述进行调参,这里面就涉及到问题类Completion和ChatCompletion。两者相似,相关常用属性和方法如下:

  • model:问题模型,text-davinci-003、text-davinci-002等
  • prompt:问题描述,一般是用户输入的内容
  • tokens():获取当前问题描述所需的tokens值,这个tokens值是OpenAI收费的依据。

 当我们需要调参进行对话时,就需要创建一个问题类的对象,例如:

// 问题内容
String prompt = "你好";
// 转换为Message对象
Message message = Message.builder().role(Message.Role.USER).content(prompt).build();
// 创建问题类
ChatCompletion chatCompletion = ChatCompletion.builder().messages(Arrays.asList(message)).model(ChatCompletion.Model.GPT_3_5_TURBO.getName()).build();

 发起对话时,代码如下:

// 创建对话并获取答案类对象
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
// 获取消息
List<ChatChoice> choices = chatCompletionResponse.getChoices();

 依据上述,我们就能写出发起非流式对话与接收返回的消息的代码了,这里编写一个测试方法进行测试

@Test
public void chatTest(){
    
    
        // 问题内容
        String prompt = "你好";
        // 转换为Message对象
        Message message = Message.builder().role(Message.Role.USER).content(prompt).build();
        // 创建问题类
        ChatCompletion chatCompletion = ChatCompletion.builder().messages(Arrays.asList(message)).model(ChatCompletion.Model.GPT_3_5_TURBO.getName()).build();
        // 创建对话并获取答案类对象
        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
        // 获取会话内容
        List<ChatChoice> choices = chatCompletionResponse.getChoices();
        System.out.println(choices.toString());
}

 测试结果如下:

 若出现超时错误,请检查是否设置了代理等相关信息。使用我提供的接口,无需代理;访问openai的接口,需要代理。当本地开启了代理,需要设置Proxy对象。

 上述实现非流式对话的过程可能较为复杂,我们使用的SDK为我们提供了更为便捷的实现方式,传入Messages对象即可。

    @Test
    public void chatTest1(){
    
    
        // 创建消息列表
        List<Message> messages = new ArrayList<>();
        Message currentMessage = Message.builder().content("你好").role(Message.Role.USER).build();
        messages.add(currentMessage);
        // 直接将消息列表发送过去
        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(messages);
        // 获取响应
        List<ChatChoice> choices = chatCompletionResponse.getChoices();
        System.out.println(choices.toString());
    }

 上述代码中直接使用Message列表对象作为参数,至于为什么使用列表而不是使用单独的Message对象,这涉及到ChatGPT上下文的实现。

ChatGPT上下文的实现是通过将用户与ChatGPT的对话记录发送给ChatGPT,让它明白上下文从而给出接下来的回答。因此,Message列表里的Message对象会被划分为ChatGPT的message和用户的message,当然,还有一个系统角色设定的message。对于每个角色,我们使用Message.Role枚举类的参数进行选择,上文的Message.Role.USER就是表示当前message是用户的。

流式对话

 再介绍完非流式对话的实现后,就是流式对话了。首先,我们所使用的SDK向我们提供了两种OpenAI客户端,分别对应流式与非流式:

  • OpenAiClient: 非流式
  • OpenAiStreamClient: 流式

 除了客户端有所差异外,调用方式大多是一致的。示例代码如下:

  • 获取OpenAiStreamClient实例对象:
// 创建openAi客户端
openAiStreamClient = OpenAiStreamClient.builder()
        // 获取key
        .apiKey(ChatGPTConfig.getApiKey())
        // 获取接口
        .apiHost(ChatGPTConfig.getApiHost())
        .okHttpClient(okHttpClient)
        .build();

 编写流式输出代码

 @Test
public void chatStreamTest(){
    
    
    List<Message> messages = new ArrayList<>();
    Message currentMessage = Message.builder().content("Hello").role(Message.Role.USER).build();
    messages.add(currentMessage);
    // 接收服务器端发送的事件流的内置对象
    ConsoleEventSourceListener consoleEventSourceListener = new ConsoleEventSourceListener();
    openAiStreamClient.streamChatCompletion(messages,consoleEventSourceListener);
    // 协调多个线程之间的同步操作
    CountDownLatch countDownLatch = new CountDownLatch(1);
    try{
    
    
        countDownLatch.await();
    }catch (Exception e){
    
    
        e.printStackTrace();
    }
}

 其中,在创建对话连接时,必须传入监听回调函数接口ConsoleEventSourceListener的示例对象。

 运行结果如下:

实现上下文对话

 ChatGPT的上下文对话并不是我们想象得那么简单,我们需要传入之前的对话记录给ChatGPT,然后ChatGPT会依据对话记录进行回复,这就是ChatGPT的上下文记忆功能的实现逻辑。

 实际应用中,这种上下文对话是我们主要的应用场景,若不实现上下文对话,可能就会出现这种情况:

 示例代码:

@Test
public void contextTest(){
    
    
    ArrayList<Message> messages = new ArrayList<>();
    // 用户需要问的内容
    String[] context = new String[]{
    
    "你好!","请问我第一句是什么?","请问你第一句是什么?"};
    for(int i=0;i<3;i++){
    
    
        String content = context[i];
        Message message = Message.builder().content(content).role(Message.Role.USER).build();
        // 将用户的问题放入messages对象里
        messages.add(message);
        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(messages);
        List<ChatChoice> choices = chatCompletionResponse.getChoices();
        for(ChatChoice e:choices){
    
    
            System.out.println(e.getMessage().getContent());
        }
        // 将刚刚放进去的内容移出来 使得每次提问都不保存上下文信息
        messages.remove(0);
    }
}

我在这里设定了用户三次发起的对话,分别是:“你好”,“请问我第一句是什么?”,“请问你第一句是什么”。在不存储对话内容的情况下,ChatGPT的回答如下

 运行结果:

 可以很明显的感受到,ChatGPT是不知道之前的对话内容的,因此,实现上下文对话,就需要将之前的对话对话内容也加进去,让ChatGPT依据上下文内容进行回复。

注意:之前的对话需要传入用户的对话和ChatGPT的对话,不能单独只传用户或ChatGPT的对话,否则,ChatGPT依据上下文进行的回复仍然会有很大的缺陷和漏洞。

 实现上下文对话的示例代码:

@Test
public void contextTest(){
    
    
    ArrayList<Message> messages = new ArrayList<>();
    String[] context = new String[]{
    
    "你好!","请问我第一句是什么?","请问你对我第一句的回复是什么?"};
    for(int i=0;i<3;i++){
    
    
        String content = context[i];
        Message message = Message.builder().content(content).role(Message.Role.USER).build();
        // 将用户的问题放入messages对象里
        messages.add(message);
        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(messages);
        List<ChatChoice> choices = chatCompletionResponse.getChoices();
        for(ChatChoice e:choices){
    
    
            System.out.println(e.getMessage().getContent());

        }
        System.out.println(choices.toString());
        // 取出ChatGPT对话内容
        String assistant = choices.get(0).getMessage().getContent();
        //                                                       将这条消息设置为ChatGPT的 --- Message.Role.ASSISTANT
        Message build = Message.builder().content(assistant).role(Message.Role.ASSISTANT).build();
        messages.add(build);
    }
}

 运行结果如下:

 可以看到,ChatGPT已经具有根据上下文进行回复的功能了。但是,我们向messages序列中不断放消息记录,依据ChatGPT的收费规则,我们的费用是依据传入给ChatGPT和ChatGPT回复给我们的总的字数进行计算的。倘若不进行限制,那么会随着我们询问的增多,每次询问所需费用也会增多。因此,我们需要对上下文的记录条数进行限制,一般是10条。

 示例代码如下:

@Test
public void contextTest(){
    
    
    List<Message> messages = new ArrayList<>();
    String[] context = new String[]{
    
    "你好!","请问我第一句是什么?","请问你对我第一句的回复是什么?"};
    for(int i=0;i<3;i++){
    
    
        String content = context[i];
        Message message = Message.builder().content(content).role(Message.Role.USER).build();
        // 将用户的问题放入messages对象里
        messages.add(message);
        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(messages);
        List<ChatChoice> choices = chatCompletionResponse.getChoices();
        for(ChatChoice e:choices){
    
    
            System.out.println(e.getMessage().getContent());

        }
        System.out.println(choices.toString());
        // 取出ChatGPT对话内容
        String assistant = choices.get(0).getMessage().getContent();
        Message build = Message.builder().content(assistant).role(Message.Role.ASSISTANT).build();
        messages.add(build);
        // 当消息列表记录10条以上时,我们截选最后十条
        if (messages.size() > 10){
    
    
            messages = messages.subList(messages.size()-10,messages.size());
        }
    }
}

实操案例

 上述就是一个上下文逻辑的实现,我们可以据此写一个交互型的ChatGPT小程序

准备工作

 首先创建一个App类,写一个start方法,该方法用来初始化一些必要的信息:

    public static void start() throws Exception {
    
    
        // 设置本地代理:如果你需要通过代理进行访问,就需要通过相关参数创建这个对象
        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 10809));
        // 设置日志级别:对于控制台输出的日志信息,我们有必要通过设置级别进行过滤,这里推荐设置HEADERS
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
        // 创建http客户端
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .addInterceptor(httpLoggingInterceptor)
                .addInterceptor(new OpenAiResponseInterceptor())
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .build();
        // 创建openAi客户端
        openAiClient = OpenAiClient.builder()
                // 获取key
                .apiKey(ChatGPTConfig.getApiKey())
                // 获取接口
                .apiHost(ChatGPTConfig.getApiHost())
                .okHttpClient(okHttpClient)
                .build();
    }

具体实现

 我们需要实现的ChatGPT小程序的需求是:用户通过控制台输入问题,然后将ChatGPT回复的内容输出出来,并且要保证ChatGPT具有上下文对话的功能。

 代码如下:

public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        try{
    
    
            start();
            List<Message> messages = new ArrayList<>();
            while (true){
    
    
                String next = scanner.next();
                // 用户输入 exit ,就退出,否则一直进行对话
                if(next.equals("exit")){
    
    
                    break;
                }
                // 将用户输入的字符串信息转化为Message对象并添加到messages序列中。
                Message message_user = Message.builder().content(next).role(Message.Role.USER).build();
                messages.add(message_user);
                // 发起ChatGPT对话
                ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(messages);
                List<ChatChoice> choices = chatCompletionResponse.getChoices();
                // 获取ChatGPT的回复
                String message_assistant = choices.get(0).getMessage().getContent();
                // 输出
                System.out.println(message_assistant);
                Message assistant = Message.builder().content(message_assistant).role(Message.Role.ASSISTANT).build();
                messages.add(assistant);
                // 记录进行限制 最多记录5条信息
                if(messages.size() > 5){
    
    
                    messages = messages.subList(messages.size()-5,messages.size());
                    // 输出存储的messages序列中的内容
                    printInfoOfList(messages);
                }
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }

 输出messages序列中的内容的方法如下:

    public static void printInfoOfList(List<Message> messages){
    
    
        for(Message e:messages){
    
    
            System.out.println(e.getRole() + "-->" + e.getContent());
        }
    }

 启动,运行,结果如下:

 至于流式输出,并不建议通过上述方法来实现用户交互,通常是通过WebSocket协议建立双向通信通道使得数据能够实时的进行交互,这一点后续会带着读者一起实现。

 至此,利用Java实现ChatGPT对话的大部分内容已经介绍完毕,后面的内容将介绍调用ChatGPT绘图功能和音频转文字功能。

猜你喜欢

转载自blog.csdn.net/qq_41481367/article/details/132418887