如果喜欢本文章,记得收藏哦!
关注我,一起学Java。
最近这个OpenAI公司推出的GPT-4.0模型真是太火了。当然由于OpenAI目前还没有正式全面对外开放GPT-4.0 API,所以本次使用的是GPT-3.5 API。
首先来看一下效果图吧!
本客户端使用的是 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()
。
这个问题折腾了好久。官方开发文档好像也没有特地说明这一点。我是研究了 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。
有更多问题欢迎访问博客。