一、基于ChatGPT API的PC端软件开发过程遇到的问题的分析

最近这个OpenAI公司推出的GPT-4.0模型真是太火了。当然由于OpenAI目前还没有正式全面对外开放GPT-4.0 API,所以本次使用的是GPT-3.5 API。

首先来看一下效果图吧!

image-20230403175127354

本客户端使用的是 JavaFX 开发的。JavaFX 相比于 Swing 来说,JavaFX 支持 CSS 样式,如果使用 Java 来开发 GUI 软件的话,还是推荐使用 JavaFX 的。JavaFX 是 2008 年由 Oracle 公司推出的项目。需要说明的是在高版本的 JDK 中不含有 JavaFX 相关的 API,所以你需要自己安装 JavaFX。因为我开发使用的是 JDK 8 所以无需自己另外安装 JavaFX,直接就可以调用 JavaFX API。

上面的图中我们可以看到使用 JavaFX 编写的 UI 界面不是太好看,没办法 Java 是我的主力编程语言,所以只能用 Java 来编写 UI 界面了。

主要有四个功能,分别是:发送,保存,查看,删除。其中发送是最核心的功能。在发送时会间接调用 GPT-3.5 API,这里为什么说是间接调用而不是直接调用呢,想必大家都知道,这个 OpenAI 公司是不对我们中国地区开放的,虽然我们可以直接访问OpenAI 的官方网站,但是不能访问 OpenAI 的产品ChatGPT。所以这里我们必须要自己使用一个国外的服务器作为中转服务器。很容易理解为啥使用中转服务器就可以访问GPT API,比如你是A,你可以访问B,但是你无法访问C,然而B是可以访问C的,那么你就可以告诉B,让B把信息传递给C。

这里我只讲开发客户端软件遇到的问题,不会讲解如何编写接口。

二、遇到的第一个问题,用户点击发送按钮后,线程阻塞

这个问题主要是因为用户点击了发送按钮后会调用 Hutool 工具类中的 HttpRequest.post() 方法将数据发送到自己定义的接口上。代码如下:

sendButton.setOnAction(e -> sendMessage());
private void sendMessage() {
    // 获取用户输入的消息并将其添加到聊天区域
    String prompt = inputArea.getText();

    // 获取当前时间
    String nowTime = getNowTime();
    chatArea.appendText(nowTime + "\n");
    chatArea.appendText("我说:" + prompt + "\n\n");

    // 清空输入框
    inputArea.setText("");

    // 存储上下文语境
    messages.add(new Message("user", prompt));

    // 获取 ChatGPT 的回复
    String reply = httpRequest(messages);

    // 机器人回复时间
    String replyTime = getNowTime();
    chatArea.appendText(replyTime + "\n");
    // 把内容显示到 UI 界面上
    chatArea.appendText("机器人说:" + reply + "\n\n");

    messages.add(new Message("assistant", reply));
}

在上面的代码中,运行的时候给用户的感觉是不好的,因为在调用 httpRequest(messages) 时会造成线程阻塞。因为在当前线程在进行网络请求时是非常耗时的操作,所以整个 main 线程会阻塞,导致应用卡顿,如果 ChatGPT 一直没有响应结果,那么会一直卡在那里。

或许你们想到的是创建一个新的线程来发送 http 请求就解决了,其实不是的,问题的根源在我们点击“发送”按钮,我们应该在点击发送按钮的时候创建新的线程,当然这里我在发送 http 的时候也是创建了新的线程。代码如下:

sendButton.setOnAction(e -> {
    Task<Void> task = new Task<Void>() {
        @Override
        protected Void call() throws Exception {
            // 执行耗时操作,例如发送网络请求或执行计算密集型任务
            sendMessage();
            // 返回null
            return null;
        }
    };

    // 在后台线程上执行Task
    new Thread(task).start();

    // 将操作提交到JavaFX应用程序线程队列中
    Platform.runLater(() -> {
        // 在此更新UI或执行其他需要在JavaFX应用程序线程上执行的操作
    });
});
private void sendMessage() {
    // 获取用户输入的消息并将其添加到聊天区域
    String prompt = inputArea.getText();

    // 获取当前时间
    String nowTime = getNowTime();
    chatArea.appendText(nowTime + "\n");
    chatArea.appendText("我说:" + prompt + "\n\n");

    // 清空输入框
    inputArea.setText("");

    // 存储上下文语境
    messages.add(new Message("user", prompt));

    // 获取 ChatGPT 的回复
    // String reply = httpRequest(messages);

    // 创建新的线程去发送 ChatGPT 请求
    FutureTask<String> task = new FutureTask<String>(new Callable<String>() {
        @Override
        public String call() throws Exception {
            return httpRequest(messages);
        }
    });
    new Thread(task).start();
    try {
        String reply = task.get();

        // 机器人回复时间
        String replyTime = getNowTime();
        chatArea.appendText(replyTime + "\n");
        // 把内容显示到 UI 界面上
        chatArea.appendText("机器人说:" + reply + "\n\n");

        messages.add(new Message("assistant", reply));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

注意:如果想要更新 UI 界面的内容,那么可以使用Platform.runLater()

三、第二个问题是ChatGPT无法进行连续对话,也就是没有上下文语境

这个问题折腾了好久。官方开发文档好像也没有特地说明这一点。我是研究了 GitHub 的代码,并且网上搜索了别人的想法,然后知道必须要把聊天记录再次发送给 ChatGPT API。但是这样就会消耗更多的资金。因为 OpenAI 并非真的是 Open。

我们每次把聊天记录发送给 ChatGPT 就行。

这里我们使用一个集合存放聊天记录,每次把聊天记录追加到集合里面即可。然后把 List 集合发送到 ChatGPT API。

// 存放上下文语境
private List<Message> messages = new ArrayList<>();
// 存储上下文语境
messages.add(new Message("system", "你是一个助手"));
messages.add(new Message("user", prompt));
messages.add(new Message("assistant", reply));

其中 Message 类代码如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Message {
    private String role;
    private String content;
}

ChatGPT 开发文档中说明了 role 有三种值,一个是 system,表示让 ChatGPT 充当什么角色,第二种取值是 user,表示用户,第三种是 assistant,表示 ChatGPT。而角色对应的内容存储到 content 变量中。这类似于 map 集合,也就是 role 是 key,content 是 value。

Q.E.D.


热爱生活,热爱程序