使用chatgpt實現(xiàn)微信聊天小程序的代碼示例
前言
前一段時間使用java來調(diào)用chatgpt的接口,然后寫了一個簡單小程序,java調(diào)用chatgpt接口,實現(xiàn)專屬于自己的人工智能助手,事實上,這個程序毛病挺多的,最不能讓人接受的一點就是返回速度非常緩慢(即使使用非常好的外網(wǎng)服務(wù)器)。
現(xiàn)在,我改進(jìn)了一下程序,使用異步請求的方式,基本可以實現(xiàn)秒回復(fù)。并且還基于webSocket編寫了一個微信小程序來進(jìn)行交互,可以直接使用微信小程序來進(jìn)行體驗。
效果展示
部分截圖如下
原理說明
在 java調(diào)用chatgpt接口,實現(xiàn)專屬于自己的人工智能助手 我說明了java調(diào)用chatgpt的基本原理,這里的代碼就是對這個代碼的改進(jìn),使用異步請求的方式來進(jìn)行。
注意看官方文檔,我們在請求時可以提供一個參數(shù)stream,然后就可以實現(xiàn)按照流的形式進(jìn)行返回,這種方式基本可以做到?jīng)]有延遲就給出答案。
由于這次改進(jìn)的思路主要就是將請求改為了異步,其他的基本一樣,所以就不做解釋,直接給出代碼了,代碼上面都有注釋
/** * 這個方法用于測試的,可以在控制臺打印輸出結(jié)果 * * @param chatGptRequestParameter 請求的參數(shù) * @param question 問題 */ public void printAnswer(ChatRequestParameter chatGptRequestParameter, String question) { asyncClient.start(); // 創(chuàng)建一個post請求 AsyncRequestBuilder asyncRequest = AsyncRequestBuilder.post(url); // 設(shè)置請求參數(shù) chatGptRequestParameter.addMessages(new ChatMessage("user", question)); // 請求的參數(shù)轉(zhuǎn)換為字符串 String valueAsString = null; try { valueAsString = objectMapper.writeValueAsString(chatGptRequestParameter); } catch (JsonProcessingException e) { e.printStackTrace(); } // 設(shè)置編碼和請求參數(shù) ContentType contentType = ContentType.create("text/plain", charset); asyncRequest.setEntity(valueAsString, contentType); asyncRequest.setCharset(charset); // 設(shè)置請求頭 asyncRequest.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); // 設(shè)置登錄憑證 asyncRequest.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey); // 下面就是生產(chǎn)者消費者模型 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 { // 收到一個請求就進(jìn)行處理 String ss = src.toString(); // 通過data:進(jìn)行分割,如果不進(jìn)行此步,可能返回的答案會少一些內(nèi)容 for (String s : ss.split("data:")) { // 去除掉data: if (s.startsWith("data:")) { s = s.substring(5); } // 返回的數(shù)據(jù)可能是(DONE) if (s.length() > 8) { // 轉(zhuǎn)換為對象 ChatResponseParameter responseParameter = objectMapper.readValue(s, ChatResponseParameter.class); // 處理結(jié)果 for (Choice choice : responseParameter.getChoices()) { String content = choice.getDelta().getContent(); if (content != null && !"".equals(content)) { // 保存結(jié)果 sb.append(content); // 將結(jié)果使用webSocket傳送過去 System.out.print(content); } } } } } @Override protected HttpResponse buildResult() throws IOException { return response; } @Override public void releaseResources() { } }; // 執(zhí)行請求 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("回答結(jié)束?。?!"); } @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(); } }
大家代碼可以直接不看,反正最終的效果就是可以實現(xiàn)問了問題就返回結(jié)果。運行效果如下
可以發(fā)現(xiàn),輸出就類似于官方的那種效果,一個字一個字的輸出
服務(wù)器端代碼說明
我使用java搭建了一個簡單的服務(wù)器端程序,提供最基礎(chǔ)的用戶登錄校驗功能,以及提供了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通信的代碼
package com.ttpfx.server; import com.fasterxml.jackson.databind.ObjectMapper; import com.ttpfx.entity.UserLog; import com.ttpfx.model.ChatModel; import com.ttpfx.service.UserLogService; import com.ttpfx.service.UserService; import com.ttpfx.vo.chat.ChatRequestParameter; import org.springframework.stereotype.Component; import javax.annotation.Resource; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.time.LocalDateTime; import java.util.concurrent.ConcurrentHashMap; /** * @author ttpfx * @date 2023/3/28 */ @Component @ServerEndpoint("/chatWebSocket/{username}") public class ChatWebSocketServer { /** * 靜態(tài)變量,用來記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計成線程安全的。 */ private static int onlineCount = 0; /** * concurrent包的線程安全Map,用來存放每個客戶端對應(yīng)的MyWebSocket對象。 */ private static ConcurrentHashMap<String, ChatWebSocketServer> chatWebSocketMap = new ConcurrentHashMap<>(); /** * 與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù) */ private Session session; /** * 接收的username */ private String username = ""; private UserLog userLog; private static UserService userService; private static UserLogService userLogService; @Resource public void setUserService(UserService userService) { ChatWebSocketServer.userService = userService; } @Resource public void setUserLogService(UserLogService userLogService) { ChatWebSocketServer.userLogService = userLogService; } private ObjectMapper objectMapper = new ObjectMapper(); private static ChatModel chatModel; @Resource public void setChatModel(ChatModel chatModel) { ChatWebSocketServer.chatModel = chatModel; } ChatRequestParameter chatRequestParameter = new ChatRequestParameter(); /** * 建立連接 * @param session 會話 * @param username 連接用戶名稱 */ @OnOpen public void onOpen(Session session, @PathParam("username") String username) { this.session = session; this.username = username; this.userLog = new UserLog(); // 這里的用戶id不可能為null,出現(xiàn)null,那么就是非法請求 try { this.userLog.setUserId(userService.queryByName(username).getId()); } catch (Exception e) { e.printStackTrace(); try { session.close(); } catch (IOException ex) { ex.printStackTrace(); } } this.userLog.setUsername(username); chatWebSocketMap.put(username, this); onlineCount++; System.out.println(username + "--open"); } @OnClose public void onClose() { chatWebSocketMap.remove(username); System.out.println(username + "--close"); } @OnMessage public void onMessage(String message, Session session) { System.out.println(username + "--" + message); // 記錄日志 this.userLog.setDateTime(LocalDateTime.now()); this.userLog.setPreLogId(this.userLog.getLogId() == null ? -1 : this.userLog.getLogId()); this.userLog.setLogId(null); this.userLog.setQuestion(message); long start = System.currentTimeMillis(); // 這里就會返回結(jié)果 String answer = chatModel.getAnswer(session, chatRequestParameter, message); long end = System.currentTimeMillis(); this.userLog.setConsumeTime(end - start); this.userLog.setAnswer(answer); userLogService.save(userLog); } @OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } public static void sendInfo(String message, String toUserId) throws IOException { chatWebSocketMap.get(toUserId).sendMessage(message); } }
我們只需要編寫簡單的前端代碼,就可以實現(xiàn)和后端的socket通信。對于后端,我們只需要改一下apiKey和數(shù)據(jù)庫配置就可以直接運行了。
微信小程序代碼說明
我寫了一個簡單微信小程序來和后端進(jìn)行通信,界面如下
大家只需要下載源代碼,然將程序中的ip改為自己服務(wù)器的ip即可
代碼鏈接
github的地址為 https://github.com/c-ttpfx/chatgpt-java-wx
可以直接使用 git clone https://github.com/c-ttpfx/chatgpt-java-wx.git 下載代碼到本地
我在github里面說明了安裝使用的基本步驟,大家按照步驟使用即可
總結(jié)
上面聊天小程序就是我花2天寫出來的,可能會有一些bug,我自己測試的時候倒是沒有怎么遇到bug,聊天和登錄功能都能正常使用。
對于微信小程序,由于我不是專業(yè)搞前端的,就只東拼西湊實現(xiàn)了最基本的功能(登錄、聊天),大家可以自己寫一個,反正后端接口都提供好了嘛,也不是很難,不想寫也可以將就使用我的。
更新日志
對代碼進(jìn)行了重構(gòu),最新的代碼已經(jīng)支持代理,通過在application.yaml里面進(jìn)行簡單配置即可使用
gpt: proxy: host: 127.0.0.1 port: 7890
以上就是使用chatgpt實現(xiàn)微信聊天小程序的代碼示例的詳細(xì)內(nèi)容,更多關(guān)于chatgpt微信聊天小程序的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java之Spring認(rèn)證使用Profile配置運行環(huán)境講解
這篇文章主要介紹了Java之Spring認(rèn)證使用Profile配置運行環(huán)境講解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07Java實現(xiàn)監(jiān)控多個線程狀態(tài)的簡單實例
下面小編就為大家?guī)硪黄狫ava實現(xiàn)監(jiān)控多個線程狀態(tài)的簡單實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-03-03SpringMVC數(shù)據(jù)頁響應(yīng)ModelAndView實現(xiàn)頁面跳轉(zhuǎn)
本文主要介紹了SpringMVC數(shù)據(jù)頁響應(yīng)ModelAndView實現(xiàn)頁面跳轉(zhuǎn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07微信公眾號開發(fā)之設(shè)置自定義菜單實例代碼【java版】
這篇文章主要介紹了微信公眾號開發(fā)之設(shè)置自定義菜單實例代碼,本實例是為了實現(xiàn)在管理后臺實現(xiàn)微信菜單的添加刪除管理。需要的朋友可以參考下2018-06-06