媒介
我在前一段时间突发奇想,就使用java来调用chatgpt的接口,然后写了一个简单小程序,也上了热榜第一,java调用chatgpt接口,实现专属于本身的人工智能助手,事实上,这个程序毛病挺多的,最不能让人接受的一点就是返回速度非常缓慢(即使使用非常好的外网服务器)。
如今,我改进了一下程序,使用异步哀求的方式,基本可以实现秒回复。而且还基于webSocket编写了一个微信小程序来进行交互,可以直接使用微信小程序来进行体验。
如今我将所有代码都上传了github(链接在文章结尾),各人可以clone下来,部署到服务器上,真正实现本身的聊天呆板人!!!
结果展示
部分截图如下
原理说明
在 java调用chatgpt接口,实现专属于本身的人工智能助手 我说明白java调用chatgpt的基本原理,这里的代码就是对这个代码的改进,使用异步哀求的方式来进行。
注意看官方文档,我们在哀求时可以提供一个参数stream,然后就可以实现按照流的形式进行返回,这种方式基本可以做到没有延迟就给出答案。
由于这次改进的思路主要就是将哀求改为了异步,其他的基本一样,以是就不做解释,直接给出代码了,代码上面都有注释
- /**
- * 这个方法用于测试的,可以在控制台打印输出结果
- *
- * @param chatGptRequestParameter 请求的参数
- * @param question 问题
- */
- public void printAnswer(ChatRequestParameter chatGptRequestParameter, String question) {
- asyncClient.start();
- // 创建一个post请求
- AsyncRequestBuilder asyncRequest = AsyncRequestBuilder.post(url);
- // 设置请求参数
- chatGptRequestParameter.addMessages(new ChatMessage("user", question));
- // 请求的参数转换为字符串
- String valueAsString = null;
- try {
- valueAsString = objectMapper.writeValueAsString(chatGptRequestParameter);
- } catch (JsonProcessingException e) {
- e.printStackTrace();
- }
- // 设置编码和请求参数
- ContentType contentType = ContentType.create("text/plain", charset);
- asyncRequest.setEntity(valueAsString, contentType);
- asyncRequest.setCharset(charset);
- // 设置请求头
- asyncRequest.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
- // 设置登录凭证
- asyncRequest.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey);
- // 下面就是生产者消费者模型
- CountDownLatch latch = new CountDownLatch(1);
- // 用于记录返回的答案
- StringBuilder sb = new StringBuilder();
- // 消费者
- AbstractCharResponseConsumer<HttpResponse> consumer = new AbstractCharResponseConsumer<HttpResponse>() {
- HttpResponse response;
- @Override
- protected void start(HttpResponse response, ContentType contentType) throws HttpException, IOException {
- setCharset(charset);
- this.response = response;
- }
- @Override
- protected int capacityIncrement() {
- return Integer.MAX_VALUE;
- }
- @Override
- protected void data(CharBuffer src, boolean endOfStream) throws IOException {
- // 收到一个请求就进行处理
- String ss = src.toString();
- // 通过data:进行分割,如果不进行此步,可能返回的答案会少一些内容
- for (String s : ss.split("data:")) {
- // 去除掉data:
- if (s.startsWith("data:")) {
- s = s.substring(5);
- }
- // 返回的数据可能是(DONE)
- if (s.length() > 8) {
- // 转换为对象
- ChatResponseParameter responseParameter = objectMapper.readValue(s, ChatResponseParameter.class);
- // 处理结果
- for (Choice choice : responseParameter.getChoices()) {
- String content = choice.getDelta().getContent();
- if (content != null && !"".equals(content)) {
- // 保存结果
- sb.append(content);
- // 将结果使用webSocket传送过去
- System.out.print(content);
- }
- }
- }
- }
- }
- @Override
- protected HttpResponse buildResult() throws IOException {
- return response;
- }
- @Override
- public void releaseResources() {
- }
- };
- // 执行请求
- asyncClient.execute(asyncRequest.build(), consumer, new FutureCallback<HttpResponse>() {
- @Override
- public void completed(HttpResponse response) {
- latch.countDown();
- chatGptRequestParameter.addMessages(new ChatMessage("assistant", sb.toString()));
- System.out.println("回答结束!!!");
- }
- @Override
- public void failed(Exception ex) {
- latch.countDown();
- System.out.println("failed");
- ex.printStackTrace();
- }
- @Override
- public void cancelled() {
- latch.countDown();
- System.out.println("cancelled");
- }
- });
- try {
- latch.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
复制代码 各人代码可以直接不看,反正终极的结果就是可以实现问了题目就返回结果。运行结果如下
可以发现,输出就雷同于官方的那种结果,一个字一个字的输出
服务器端代码说明
我使用java搭建了一个简单的服务器端程序,提供最底子的用户登录校验功能,以及提供了WebSocket通信。
用户校验的代码
- package com.ttpfx.controller;
- import com.ttpfx.entity.User;
- import com.ttpfx.service.UserService;
- import com.ttpfx.utils.R;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- import javax.annotation.Resource;
- import java.util.Objects;
- import java.util.concurrent.ConcurrentHashMap;
- /**
- * @author ttpfx
- * @date 2023/3/29
- */
- @RestController
- @RequestMapping("/user")
- public class UserController {
- @Resource
- private UserService userService;
- public static ConcurrentHashMap<String, User> loginUser = new ConcurrentHashMap<>();
- public static ConcurrentHashMap<String, Long> loginUserKey = new ConcurrentHashMap<>();
- @RequestMapping("/login")
- public R login(String username, String password) {
- if (username == null) return R.fail("必须填写用户名");
- User user = userService.queryByName(username);
- if (user == null) return R.fail("用户名不存在");
- String targetPassword = user.getPassword();
- if (targetPassword == null) return R.fail("用户密码异常");
- if (!targetPassword.equals(password)) return R.fail("密码错误");
- loginUser.put(username, user);
- loginUserKey.put(username, System.currentTimeMillis());
- return R.ok(String.valueOf(loginUserKey.get(username)));
- }
- @RequestMapping("/logout")
- public R logout(String username) {
- loginUser.remove(username);
- loginUserKey.remove(username);
- return R.ok();
- }
- @RequestMapping("/checkUserKey")
- public R checkUserKey(String username, Long key){
- if (username==null || key == null)return R.fail("用户校验异常");
- if (!Objects.equals(loginUserKey.get(username), key)){
- return R.fail("用户在其他地方登录!!!");
- }
- return R.ok();
- }
- @RequestMapping("/loginUser")
- public R loginUser(){
- return R.ok("success",loginUser.keySet());
- }
- }
复制代码 基于webSocket通信的代码
我们只需要编写简单的前端代码,就可以实现和后端的socket通信。对于后端,我们只需要改一下apiKey和数据库配置就可以直接运行了。
微信小程序代码说明
我写了一个简单微信小程序来和后端进行通信,界面如下
各人只需要下载源代码,然将程序中的ip改为本身服务器的ip即可
代码链接
github的所在为 https://github.com/c-ttpfx/chatgpt-java-wx
可以直接使用 git clone https://github.com/c-ttpfx/chatgpt-java-wx.git 下载代码到本地
我在github里面说明白安装使用的基本步调,各人按照步调使用即可
总结
上面聊天小程序就是我花2天写出来的,可能会有一些bug,我本身测试的时间倒是没有怎么遇到bug,聊天和登录功能都能正常使用。
对于微信小程序,由于我不是专业搞前端的,就只东拼西凑实现了最基本的功能(登录、聊天),各人可以本身写一个,反正后端接口都提供好了嘛,也不是很难,不想写也可以迁就使用我的。
最后,也是最重要的,各人帮我的代码star一下!!! 感谢各人了(≥▽≤)/(≥▽≤)/
更新日记
2023/5/13 14:42更新
对代码进行了重构,最新的代码已经支持代理,通过在application.yaml里面进行简单配置即可使用
- gpt:
- proxy:
- host: 127.0.0.1
- port: 7890
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |