摘要
ChatGPT和元宇宙都是以后数字化畛域中十分热门的技术和利用。联合两者的劣势和特点,能够摸索出更多的利用场景和商业模式。例如,在元宇宙中应用ChatGPT进行自然语言交互,能够为用户提供更加智能化、个性化的服务和反对;在ChatGPT中应用元宇宙进行虚拟现实体验,能够为用户提供更加实在、丰盛、多样化的交互体验。
上面我将联合元宇宙和ChatGPT的劣势,实战开发一个GPT虚构直播的Demo并推流到抖音平台,
NodeJS接入ChatGPT与即构ZIM
上一篇文章《人人都能用ChatGPT4.0做Avatar虚拟人直播》,次要介绍了如何应用ChatGPT+即构Avatar做虚拟人直播。因为篇幅起因,对代码具体实现局部形容的不够具体。收到不少读者询问代码相干问题,接下来笔者将代码实现局部拆分2局部来详细描述:
- NodeJS接入ChatGPT与即构ZIM
- ChatGPT与即构Avatar虚拟人对接直播
本文次要解说如何接入ChatGPT并实现前期能与Avatar对接能力。
在开始讲具体流程之前,咱们先来回顾一下整个GPT虚构直播Demo的实现流程图,本文要分享的内容是下图的左边局部的实现逻辑。
1 基本原理
ChatGPT是纯文本互动,那么如何让它跟Avatar虚拟人分割呢?
首先咱们已知一个先验:
- 即构Avatar有文本驱动能力,即给Avatar输出一段文本,Avatar依据文本渲染口型+播报语音
- 将观众在直播间发送的弹幕音讯抓取后,发送给OpenAI的ChatGPT服务器
- 失去ChatGPT回复后将回复内容通过Avatar语音播报
在观众看来,这就是在跟领有ChatGPT一样智商的虚拟人直播互动了。
2 本文应用的工具
- GPT虚构直播弹幕:即构ZIM语聊房群聊弹幕
- GPT4.0:New bing
- GPT3.5:ChatGPT
3 对接ChatGPT
这里次要举荐2个库:
- chatgpt-api
- chatgpt
chatgpt-api封装了基于bing的chatgpt4.0,chatgpt基于openAI官网的chatgpt3.5。具体如何创立bing账号以及如何获取Cookie值以及如何获取apiKey,能够参考我另一篇文章《人人都能用ChatGPT4.0做Avatar虚拟人直播》。
3.1 chatgpt-api
装置:
npm i @waylaidwanderer/chatgpt-api
bing还没有对中国大陆凋谢chatgpt,因而须要一个代理,因而须要把代理地址也一起封装。代码如下:
import { BingAIClient } from '@waylaidwanderer/chatgpt-api';export class BingGPT { /* * http_proxy, apiKey **/ constructor(http_proxy, userCookie) { this.api = this.init(http_proxy, userCookie); this.conversationSignature = ""; this.conversationId = ""; this.clientId = ""; this.invocationId = ""; } init(http_proxy, userCookie) { console.log(http_proxy, userCookie) const options = { host: 'https://www.bing.com', userToken: userCookie, // If the above doesn't work, provide all your cookies as a string instead cookies: '', // A proxy string like "http://<ip>:<port>" proxy: http_proxy, // (Optional) Set to true to enable `console.debug()` logging debug: false, }; return new BingAIClient(options); } // //此处省略chat函数...... //}
下面代码实现了VPN和BingAIClient的封装,还短少聊天接口,因而增加chat函数实现聊天性能:
//调用chatpgt chat(text, cb) { var res="" var that = this; console.log("正在向bing发送发问", text ) this.api.sendMessage(text, { toneStyle: 'balanced', onProgress: (token) => { if(token.length==2 && token.charCodeAt(0)==55357&&token.charCodeAt(1)==56842){ cb(true, res); } res+=token; } }).then(function(response){ that.conversationSignature = response.conversationSignature; that.conversationId = response.conversationId; that.clientId = response.clientId; that.invocationId = response.invocationId; }) ; }
在应用的时候只需如下调用:
var bing = new BingGPT(HTTP_PROXY, BING_USER_COOKIE);bing.chat("这里传入发问内容XXXX?", function(succ, response){ if(succ) console.log("回复内容:", response)})
须要留神的是,基于bing的chatgpt4.0次要是通过模仿浏览器形式封住。在浏览器端有很多防机器人检测,因而容易被卡断。这里笔者倡议仅限本人体验,不适宜作为产品接口应用。如果须要封装成产品,倡议应用下一节2.2内容。
3.2 chatgpt
装置:
npm install chatgpt
跟上一大节2.1相似,基于openAI的chatgpt3.5仍旧须要梯子能力应用。chatgpt库没有内置代理能力,因而咱们能够本人装置代理库:
npm install https-proxy-agent node-fetch
接下来将代理和chatgpt库一起集成封装成一个类:
import { ChatGPTAPI } from "chatgpt";import proxy from "https-proxy-agent";import nodeFetch from "node-fetch";export class ChatGPT { constructor(http_proxy, apiKey) { this.api = this.init(http_proxy, apiKey); this.conversationId = null; this.ParentMessageId = null; } init(http_proxy, apiKey) { console.log(http_proxy, apiKey) return new ChatGPTAPI({ apiKey: apiKey, fetch: (url, options = {}) => { const defaultOptions = { agent: proxy(http_proxy), }; const mergedOptions = { ...defaultOptions, ...options, }; return nodeFetch(url, mergedOptions); }, }); } //... //此处省略chat函数 //...}
实现ChatGPTAPI的封装后,接下来增加聊天接口:
//调用chatpgt chat(text, cb) { let that = this console.log("正在向ChatGPT发送发问:", text) that.api.sendMessage(text, { conversationId: that.ConversationId, parentMessageId: that.ParentMessageId }).then( function (res) { that.ConversationId = res.conversationId that.ParentMessageId = res.id cb && cb(true, res.text) } ).catch(function (err) { console.log(err) cb && cb(false, err); });}
应用时就非常简单:
var chatgpt = new ChatGPT(HTTP_PROXY, API_KEY);chatgpt.chat("这里传入发问内容XXXX?", function(succ, response){ if(succ) console.log("回复内容:", response)})
chatgpt库次要基于openAI的官网接口,相对来说比较稳定,举荐这种形式应用。
3.3 两库一起封装
为了更加灵便方便使用,随便切换chatgpt3.5和chatgpt4.0。将以上两个库封装到一个接口中。
首先创立一个文件保留各种配置, KeyCenter.js:
const HTTP_PROXY = "http://127.0.0.1:xxxx";//本地vpn代理端口//openAI的key, const API_KEY = "sk-xxxxxxxxxxxxxxxxxxxxxxxxx";//bing cookieconst BING_USER_COOKIE = 'xxxxxxxxxxxxxxxxxxxxxxxx--BA';module.exports = { HTTP_PROXY: HTTP_PROXY, API_KEY: API_KEY, BING_USER_COOKIE:BING_USER_COOKIE}
留神,以上相干配置内容须要读者替换。
接下来封装两个不同版本的chatGPT:
const KEY_CENTER = require("../KeyCenter.js");var ChatGPTObj = null, BingGPTObj = null;//初始化chatgptfunction getChatGPT(onInitedCb) { if (ChatGPTObj != null) { onInitedCb(true, ChatGPTObj); return; } (async () => { let { ChatGPT } = await import("./chatgpt.mjs"); return new ChatGPT(KEY_CENTER.HTTP_PROXY, KEY_CENTER.API_KEY); })().then(function (obj) { ChatGPTObj = obj; onInitedCb(true, obj); }).catch(function (err) { onInitedCb(false, err); });}function getBingGPT(onInitedCb){ if(BingGPTObj!=null) { onInitedCb(true, BingGPTObj); return; } (async () => { let { BingGPT } = await import("./binggpt.mjs"); return new BingGPT(KEY_CENTER.HTTP_PROXY, KEY_CENTER.BING_USER_COOKIE); })().then(function (obj) { BingGPTObj = obj; onInitedCb(true, obj); }).catch(function (err) { console.log(err) onInitedCb(false, err); });}
下面两个函数getBingGPT
和getChatGPT
别离对应2.1节
和2.2节
封装的版本。在切换版本的时候间接调用对应的函数即可,但笔者认为,还不够优雅!应用起来还是不够难受,因为须要保护不同的对象。最好能进一步封装,调用的时候一行代码来应用是最好的。那进一步封装,补充以下代码:
//调用chatgpt聊天function chatGPT(text, cb) { getChatGPT(function (succ, obj) { if (succ) { obj.chat(text, cb); } else { cb && cb(false, "chatgpt not inited!!!"); } })}function chatBing(text, cb){ getBingGPT(function (succ, obj) { if (succ) { obj.chat(text, cb); } else { cb && cb(false, "chatgpt not inited!!!"); } })}module.exports = { chatGPT: chatGPT, chatBing:chatBing}
加了以上代码后,就难受多了:想要应用bing的chatgpt4.0,那就调用chatBing函数好了;想要应用openAI官网的chatgpt3.5,那就调用chatGPT函数就好!
4 对接Avatar
4.1 基本思路
好了,第2节介绍了对chatgpt的封装,不同的版本只需调用不同函数即可实现与chatgpt对话。接下来怎么将chatGPT的文本对话内容传递给Avatar呢?即构Avatar是即构推出的一款虚构形象产品,它能够跟即构内的其余产品对接,比方即时通讯ZIM和音视频通话RTC。这就好办了,咱们只需利用ZIM或RTC即可。
这里咱们次要利用即构ZIM实现,因为即构ZIM十分不便实时文本内容。即构ZIM群聊音讯稳固牢靠,提早低,寰球任何一个地区都有接入服务的节点保障达到。
尤其是ZIM群聊有弹幕性能,相比发送聊天音讯,发送弹幕音讯不会被存储,更适宜直播间评论性能。
4.2 代码实现
即构官网提供的js版本库次要是基于浏览器,须要应用到浏览器的个性如DOM、localStorage等。而这里咱们次要基于NodeJS,没有浏览器环境。因而咱们须要装置一些必要的库, 相干库曾经在package.json有记录,间接执行如下命令即可:
npm install
4.2.1 创立模仿浏览器环境
首先执行浏览器环境模拟,通过fake-indexeddb、jsdom、node-localstorage库模仿浏览器环境以及本地存储环境。创立WebSocket、XMLHttpRequest等全局对象。
var fs = require('fs');//先清理缓存fs.readdirSync('./local_storage').forEach(function (fileName) { fs.unlinkSync('./local_storage/' + fileName);});const KEY_CENTER = require("../KeyCenter.js");const APPID = KEY_CENTER.APPID, SERVER_SECRET = KEY_CENTER.SERVER_SECRET;const generateToken04 = require('./TokenUtils.js').generateToken04;var LocalStorage = require('node-localstorage').LocalStorage;localStorage = new LocalStorage('./local_storage');var indexedDB = require("fake-indexeddb/auto").indexedDB;const jsdom = require("jsdom");const { JSDOM } = jsdom;const dom = new JSDOM(``, { url: "http://localhost/", referrer: "http://localhost/", contentType: "text/html", includeNodeLocations: true, storageQuota: 10000000});window = dom.window;document = window.document;navigator = window.navigator;location = window.location;WebSocket = window.WebSocket;XMLHttpRequest = window.XMLHttpRequest;
4.2.2 创立ZIM对象
将即构官网下载的index.js引入,获取ZIM类并实例化,这个过程封装到createZIM函数中。须要留神的是登录须要Token,为了平安思考,Token倡议在服务器端生成。接下来把整个初始化过程封装到initZego函数中,蕴含注册监听接管音讯,监控Token过期并重置。
const ZIM = require('./index.js').ZIM; function newToken(userId) { const token = generateToken04(APPID, userId, SERVER_SECRET, 60 * 60 * 24, ''); return token;}/** * 创立ZIM对象*/function createZIM(onError, onRcvMsg, onTokenWillExpire) { var zim = ZIM.create(APPID); zim.on('error', onError); zim.on('receivePeerMessage', function (zim, msgObj) { console.log("收到P2P音讯") onRcvMsg(false, zim, msgObj) }); // 收到群组音讯的回调 zim.on('receiveRoomMessage', function (zim, msgObj) { console.log("收到群组音讯") onRcvMsg(true, zim, msgObj) }); zim.on('tokenWillExpire', onTokenWillExpire); return zim;}/**初始化即构ZIM*/function initZego(onError, onRcvMsg, myUID) { var token = newToken(myUID); var startTimestamp = new Date().getTime(); function _onError(zim, err) { onError(err); } function _onRcvMsg(isFromGroup, zim, msgObj) { var msgList = msgObj.messageList; var fromConversationID = msgObj.fromConversationID; msgList.forEach(function (msg) { if (msg.timestamp - startTimestamp >= 0) { //过滤掉离线音讯 var out = parseMsg(zim, isFromGroup, msg.message, fromConversationID) if (out) onRcvMsg(out); } }) } function onTokenWillExpire(zim, second) { token = newToken(userId); zim.renewToken(token); } var zim = createZIM(_onError, _onRcvMsg, onTokenWillExpire); login(zim, myUID, token, function (succ, data) { if (succ) { console.log("登录胜利!") } else { console.log("登录失败!", data) } }) return zim;}
4.2.3 登录、创立房间、退出房间、来到房间
调用zim对象的login函数实现登录,封装到login函数中;调用zim对象的joinRoom实现退出房间,封装到joinRoom函数中;调用zim的leaveRoom函数实现退出房间,封装到leaveRoom函数中。
/** * 登录即构ZIM*/function login(zim, userId, token, cb) { var userInfo = { userID: userId, userName: userId }; zim.login(userInfo, token) .then(function () { cb(true, null); }) .catch(function (err) { cb(false, err); });}/** * 退出房间*/function joinRoom(zim, roomId, cb = null) { zim.joinRoom(roomId) .then(function ({ roomInfo }) { cb && cb(true, roomInfo); }) .catch(function (err) { cb && cb(false, err); });}/** * 来到房间*/function leaveRoom(zim, roomId) { zim.leaveRoom(roomId) .then(function ({ roomID }) { // 操作胜利 console.log("已来到房间", roomID) }) .catch(function (err) { // 操作失败 console.log("来到房间失败", err) });}
4.2.4 发送音讯、解析音讯
发送音讯分为一对一发送和发送到房间,这里通过isGroup参数来管制,如下sendMsg函数所示。将接管音讯UID和发送内容作为sendMsg参数,最终封装并调用ZIM的sendMessage函数实现音讯发送。
接管到音讯后,在咱们的利用中设置了发送的音讯内容是个json对象,因而须要对内容进行解析,具体的json格局能够参考残缺源码,这里不做具体解说。
/** * 发送音讯*/function sendMsg(zim, isGroup, msg, toUID, cb) { var type = isGroup ? 1 : 0; // 会话类型,取值为 单聊:0,房间:1,群组:2 var config = { priority: 1, // 设置音讯优先级,取值为 低:1(默认),中:2,高:3 }; var messageTextObj = { type: 20, message: msg, extendedData: '' }; var notification = { onMessageAttached: function (message) { console.log("已发送", message) } } zim.sendMessage(messageTextObj, toUID, type, config, notification) .then(function ({ message }) { // 发送胜利 cb(true, null); }) .catch(function (err) { // 发送失败 cb(false, err) }); }/** * 解析收到的音讯*/function parseMsg(zim, isFromGroup, msg, fromUid) { //具体实现略}
4.2.5 导出接口
有了以上的实现后,把要害函数导出裸露给其余业务调用:
module.exports = { initZego: initZego, sendMsg: sendMsg, joinRoom: joinRoom}
以上代码次要封装:
- 即构ZIM初始化
- 发送音讯
- 退出房间
至此,咱们就具备了将chatgpt音讯群发到一个房间的能力、退出房间、接管到房间的弹幕音讯能力。
更多对于即构ZIM接口与官网Demo能够点击参考这里,对即构ZIM理解更多能够点击这里
对于Avatar如何播报chatgpt内容,咱们在下一篇文章实现。
5 相干代码
- nodejs接入chatgpt与即构zim