摘要

ChatGPT和元世界都是当时数字化领域中十分热门的技术和运用。结合两者的优势和特色,能够探究出更多的运用场景和商业模式。例如,在元世界中运用ChatGPT进行自然语言交互,可认为用户供给愈加智能化、个性化的服务和支持;在ChatGPT中运用元世界进行虚拟现实体验,可认为用户供给愈加真实、丰厚、多样化的交互体验。 下面我将结合元世界和ChatGPT的优势,实战开发一个GPT虚拟直播的Demo并推流到抖音平台,

NodeJS接入ChatGPT与即构ZIM

上一篇文章《人人都能用ChatGPT4.0做Avatar虚拟人直播》,首要介绍了怎么运用ChatGPT+即构Avatar做虚拟人直播。由于篇幅原因,对代码具体完结部分描绘的不行具体。收到不少读者询问代码相关问题,接下来笔者将代码完结部分拆分2部分来具体描绘:

  1. NodeJS接入ChatGPT与即构ZIM
  2. ChatGPT与即构Avatar虚拟人对接直播

本文首要讲解怎么接入ChatGPT并完结后期能与Avatar对接才能。

在开端讲具体流程之前,咱们先来回忆一下整个GPT虚拟直播Demo的完结流程图,本文要共享的内容是下图的右边部分的完结逻辑。

「GPT实战」GPT接入直播间实现虚拟人弹幕回复

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 cookie
const 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;
//初始化chatgpt
function 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);
    });
}

上面两个函数getBingGPTgetChatGPT别离对应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
}

以上代码首要封装:

  1. 即构ZIM初始化
  2. 发送音讯
  3. 参加房间

至此,咱们就具备了将chatgpt音讯群发到一个房间的才能、参加房间、接纳到房间的弹幕音讯才能。

更多关于即构ZIM接口与官方Demo能够点击参阅这儿,对即构ZIM了解更多能够点击这儿

关于Avatar怎么播报chatgpt内容,咱们在下一篇文章完结。

5 相关代码

  1. nodejs接入chatgpt与即构zim