From 94d5691e8a4cbf833767d57ff9d40e7130af7e77 Mon Sep 17 00:00:00 2001 From: ikechan8370 Date: Mon, 20 Mar 2023 00:26:04 +0800 Subject: [PATCH] feat: add support for chatglm --- apps/chat.js | 46 ++++++++++++++++++-- apps/help.js | 4 +- apps/management.js | 20 +++++---- utils/chatglm.js | 106 +++++++++++++++++++++++++++++++++++++++++++++ utils/config.js | 3 +- 5 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 utils/chatglm.js diff --git a/apps/chat.js b/apps/chat.js index 193332a..bf7101a 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -22,6 +22,7 @@ import fetch from 'node-fetch' import { deleteConversation, getConversations, getLatestMessageIdByConversationId } from '../utils/conversation.js' import { convertSpeaker, generateAudio, speakers } from '../utils/tts.js' import { segment } from 'oicq' +import ChatGLMClient from "../utils/chatglm.js"; try { await import('keyv') } catch (err) { @@ -194,6 +195,21 @@ export class chatgpt extends plugin { logger.info(`SydneyUser_${e.sender.user_id}`, await conversationsCache.get(`SydneyUser_${e.sender.user_id}`)) await conversationsCache.delete(`SydneyUser_${e.sender.user_id}`) await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true) + } else if (use === 'chatglm') { + const conversation = { + store: new KeyvFile({ filename: 'cache.json' }), + namespace: 'chatglm' + } + let Keyv + try { + Keyv = (await import('keyv')).default + } catch (err) { + await this.reply('依赖keyv未安装,请执行pnpm install keyv', true) + } + const conversationsCache = new Keyv(conversation) + logger.info(`ChatGLMUser_${e.sender.user_id}`, await conversationsCache.get(`SydneyUser_${e.sender.user_id}`)) + await conversationsCache.delete(`SydneyUser_${e.sender.user_id}`) + await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true) } else { let c = await redis.get(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`) if (!c) { @@ -224,6 +240,21 @@ export class chatgpt extends plugin { const conversationsCache = new Keyv(conversation) await conversationsCache.delete(`SydneyUser_${qq}`) await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true) + } else if (use === 'chatglm') { + const conversation = { + store: new KeyvFile({ filename: 'cache.json' }), + namespace: 'chatglm' + } + let Keyv + try { + Keyv = (await import('keyv')).default + } catch (err) { + await this.reply('依赖keyv未安装,请执行pnpm install keyv', true) + } + const conversationsCache = new Keyv(conversation) + logger.info(`SydneyUser_${e.sender.user_id}`, await conversationsCache.get(`SydneyUser_${e.sender.user_id}`)) + await conversationsCache.delete(`ChatGLMUser_${qq}`) + await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true) } else { let c = await redis.get(`CHATGPT:CONVERSATIONS:${qq}`) if (!c) { @@ -527,9 +558,6 @@ export class chatgpt extends plugin { if (!lastMessageId) { lastMessageId = await getLatestMessageIdByConversationId(conversationId, newFetch) } - // let lastMessagePrompt = await redis.get(`CHATGPT:CONVERSATION_LAST_MESSAGE_PROMPT:${conversationId}`) - // let conversationCreateTime = await redis.get(`CHATGPT:CONVERSATION_CREATE_TIME:${conversationId}`) - // let conversationLength = await redis.get(`CHATGPT:CONVERSATION_LENGTH:${conversationId}`) conversation = { conversationId, parentMessageId: lastMessageId @@ -855,6 +883,18 @@ export class chatgpt extends plugin { } return sendMessageResult } + case 'chatglm': { + const cacheOptions = { + namespace: 'chatglm_6b', + store: new KeyvFile({ filename: 'cache.json' }) + } + this.chatGPTApi = new ChatGLMClient({ + user: e.sender.user_id, + cache: cacheOptions + }) + let sendMessageResult = await this.chatGPTApi.sendMessage(prompt, conversation) + return sendMessageResult + } default: { let completionParams = {} if (Config.model) { diff --git a/apps/help.js b/apps/help.js index a044372..525eb6a 100644 --- a/apps/help.js +++ b/apps/help.js @@ -128,8 +128,8 @@ let helpData = [ }, { icon: 'switch', - title: '#chatgpt切换浏览器/API/API2/API3/Bing', - desc: '切换使用的后端为浏览器或OpenAI API/第三方API/反代官网API/Bing' + title: '#chatgpt切换浏览器/API/API3/Bing/ChatGLM', + desc: '切换使用的后端为浏览器或OpenAI API/反代官网API/Bing/自建ChatGLM' }, { icon: 'confirm', diff --git a/apps/management.js b/apps/management.js index 84c0c43..efd22f1 100644 --- a/apps/management.js +++ b/apps/management.js @@ -43,8 +43,8 @@ export class ChatgptManagement extends plugin { permission: 'master' }, { - reg: '^#chatgpt切换API2$', - fnc: 'useReversedAPIBasedSolution', + reg: '^#chatgpt切换(ChatGLM|chatglm)$', + fnc: 'useChatGLMSolution', permission: 'master' }, { @@ -207,10 +207,9 @@ export class ChatgptManagement extends plugin { } } - async useReversedAPIBasedSolution (e) { - await this.reply('API2已废弃,处于不可用状态,不会为你切换') - // await redis.set('CHATGPT:USE', 'apiReverse') - // await this.reply('【暂时不可用,请关注仓库更新和群公告】已切换到基于第三方Reversed CompletionAPI的解决方案,如果已经对话过建议执行`#结束对话`避免引起404错误') + async useChatGLMSolution (e) { + await redis.set('CHATGPT:USE', 'chatglm') + await this.reply('已切换到ChatGLM-6B解决方案,如果已经对话过建议执行`#结束对话`避免引起404错误') } async useReversedAPIBasedSolution2 (e) { @@ -340,10 +339,11 @@ export class ChatgptManagement extends plugin { let mode = await redis.get('CHATGPT:USE') const modeMap = { browser: '浏览器', - apiReverse: 'API2', + // apiReverse: 'API2', api: 'API', bing: '必应', - api3: 'API3' + api3: 'API3', + chatglm: 'ChatGLM-6B' } let modeText = modeMap[mode || 'api'] let message = ` API模式和浏览器模式如何选择? @@ -356,8 +356,10 @@ export class ChatgptManagement extends plugin { 浏览器模式通过在本地启动Chrome等浏览器模拟用户访问ChatGPT网站,使得获得和官方以及API2模式一模一样的回复质量,同时保证安全性。缺点是本方法对环境要求较高,需要提供桌面环境和一个可用的代理(能够访问ChatGPT的IP地址),且响应速度不如API,而且高峰期容易无法使用。 必应(Bing)将调用微软新必应接口进行对话。需要在必应网页能够正常使用新必应且设置有效的Bing 登录Cookie方可使用。#chatgpt设置必应token + + 自建ChatGLM模式会调用自建的ChatGLM-6B服务器API进行对话,需要自建。参考https://github.com/ikechan8370/SimpleChatGLM6BAPI - 您可以使用‘#chatgpt切换浏览器/API/API2/API3/Bing’来切换到指定模式。 + 您可以使用‘#chatgpt切换浏览器/API/API3/Bing/ChatGLM’来切换到指定模式。 当前为${modeText}模式。 ` diff --git a/utils/chatglm.js b/utils/chatglm.js new file mode 100644 index 0000000..29979df --- /dev/null +++ b/utils/chatglm.js @@ -0,0 +1,106 @@ +import { Config } from './config.js' +import fetch from 'node-fetch' +import { v4 as uuidv4 } from 'uuid' +async function getKeyv () { + let Keyv + try { + Keyv = (await import('keyv')).default + } catch (error) { + throw new Error('keyv依赖未安装,请使用pnpm install keyv安装') + } + return Keyv +} + +export default class ChatGLMClient { + constructor (opts) { + // user: qq号 + this.opts = opts + } + + async initCache () { + if (!this.conversationsCache) { + const cacheOptions = this.opts.cache || {} + cacheOptions.namespace = cacheOptions.namespace || 'chatglm' + let Keyv = await getKeyv() + this.conversationsCache = new Keyv(cacheOptions) + } + } + + async sendMessage (prompt, opts) { + const { + conversationId = uuidv4(), + messageId = uuidv4(), + parentMessageId, + temperature = Config.temperature + } = opts + await this.initCache() + let url = Config.chatglmBaseUrl + '/api/chat' + if (Config.debug) { + logger.info('use chatglm api server endpoint: ' + url) + } + const conversationKey = `ChatGLMUser_${this.opts.user}` + const conversation = (await this.conversationsCache.get(conversationKey)) || { + messages: [], + createdAt: Date.now() + } + let history = getMessagesForConversation(conversation.messages, parentMessageId) + if (Config.debug) { + logger.info(history) + } + console.log(history) + let option = { + method: 'POST', + body: JSON.stringify({ + prompt, + temperature, + history + }), + headers: { + 'content-type': 'application/json', + library: 'chatgpt-plugin' + } + } + let response = await fetch(url, option) + let result = await response.text() + try { + result = JSON.parse(result) + conversation.messages.push({ + id: messageId, + role: 'user', + content: prompt, + parentMessageId + }) + let responseId = uuidv4() + conversation.messages.push({ + id: responseId, + role: 'AI', + content: result.data, + parentMessageId: messageId + }) + await this.conversationsCache.set(conversationKey, conversation) + return { + conversationId, + id: responseId, + text: result.data + } + } catch (e) { + console.error(result) + throw new Error(result) + } + } +} + +function getMessagesForConversation (messages, parentMessageId) { + const orderedMessages = [] + let currentMessageId = parentMessageId + while (currentMessageId) { + const message = messages.find((m) => m.id === currentMessageId) + if (!message) { + break + } + orderedMessages.unshift(message) + currentMessageId = message.parentMessageId + } + + return orderedMessages +} diff --git a/utils/config.js b/utils/config.js index 8177b59..a84dc7e 100644 --- a/utils/config.js +++ b/utils/config.js @@ -66,7 +66,8 @@ const defaultConfig = { initiativeChatGroups: [], enableDraw: true, helloPrompt: '写一段话让大家来找我聊天。类似于“有人找我聊天吗?"这种风格,轻松随意一点控制在20个字以内', - version: 'v2.2.3' + chatglmBaseUrl: 'http://localhost:8080', + version: 'v2.2.4' } const _path = process.cwd() let config = {}