diff --git a/cores/qqbot/core.py b/cores/qqbot/core.py index ff30d9c21..543eba657 100644 --- a/cores/qqbot/core.py +++ b/cores/qqbot/core.py @@ -12,7 +12,6 @@ import json import threading import asyncio import time -from cores.database.conn import dbConn import requests import util.unfit_words as uw import os @@ -21,7 +20,6 @@ from cores.qqbot.personality import personalities from addons.baidu_aip_judge import BaiduJudge -history_dump_interval = 10 # QQBotClient实例 client = '' # ChatGPT实例 @@ -54,9 +52,6 @@ frequency_count = 2 # 公告(可自定义): announcement = "" -# 人格信息 -now_personality = {} - # 机器人私聊模式 direct_message_mode = True @@ -127,30 +122,6 @@ def toggle_count(at: bool, message): except BaseException: pass -# 转储历史记录的定时器~ Soulter -def dump_history(): - time.sleep(10) - global session_dict, history_dump_interval - db = dbConn() - while True: - try: - # print("转储历史记录...") - for key in session_dict: - # print("TEST: "+str(db.get_session(key))) - data = session_dict[key] - data_json = { - 'data': data - } - if db.check_session(key): - db.update_session(key, json.dumps(data_json)) - else: - db.insert_session(key, json.dumps(data_json)) - # print("转储历史记录完毕") - except BaseException as e: - print(e) - # 每隔10分钟转储一次 - time.sleep(10*history_dump_interval) - # 上传统计信息并检查更新 def upload(): global object_id @@ -192,7 +163,8 @@ def upload(): ''' def initBot(cfg, prov): global chatgpt, provider, rev_chatgpt, baidu_judge, rev_ernie, rev_edgegpt - global reply_prefix, now_personality, gpt_config, config, uniqueSession, history_dump_interval, frequency_count, frequency_time,announcement, direct_message_mode, version + global reply_prefix, now_personality, gpt_config, config, uniqueSession, frequency_count, frequency_time,announcement, direct_message_mode, version + global command_openai_official, command_rev_chatgpt, command_rev_edgegpt provider = prov config = cfg @@ -203,71 +175,42 @@ def initBot(cfg, prov): # 语言模型提供商 if prov == REV_CHATGPT: if 'account' in cfg['rev_ChatGPT']: - from addons.revChatGPT.revchatgpt import revChatGPT + from model.provider.provider_rev_chatgpt import ProviderRevChatGPT + from model.command.command_rev_chatgpt import CommandRevChatGPT for i in range(0, len(cfg['rev_ChatGPT']['account'])): try: print(f"[System] 创建rev_ChatGPT负载{str(i)}: " + str(cfg['rev_ChatGPT']['account'][i])) revstat = { - 'obj': revChatGPT(cfg['rev_ChatGPT']['account'][i]), + 'obj': ProviderRevChatGPT(cfg['rev_ChatGPT']['account'][i]), 'busy': False } rev_chatgpt.append(revstat) - except: print("[System] 创建rev_ChatGPT负载失败") + command_rev_chatgpt = CommandRevChatGPT(rev_chatgpt) + if REV_CHATGPT in reply_prefix_config: reply_prefix = reply_prefix_config[REV_CHATGPT] else: input("[System-err] 请退出本程序, 然后在配置文件中填写rev_ChatGPT相关配置") elif prov == OPENAI_OFFICIAL: - from cores.openai.core import ChatGPT - chatgpt = ChatGPT(cfg['openai']) - global max_tokens - max_tokens = int(chatgpt.getConfigs()['total_tokens_limit']) - - # 读取历史记录 Soulter - try: - db1 = dbConn() - for session in db1.get_all_session(): - session_dict[session[0]] = json.loads(session[1])['data'] - print("[System] 历史记录读取成功喵") - except BaseException as e: - print("[System] 历史记录读取失败: " + str(e)) - - # 读统计信息 - global stat_file - if not os.path.exists(abs_path+"configs/stat"): - with open(abs_path+"configs/stat", 'w', encoding='utf-8') as f: - json.dump({}, f) - stat_file = open(abs_path+"configs/stat", 'r', encoding='utf-8') - global count - res = stat_file.read() - if res == '': - count = {} - else: - try: - count = json.loads(res) - except BaseException: - pass - # 创建转储定时器线程 - threading.Thread(target=dump_history, daemon=True).start() - - # 得到GPT配置信息 - if 'openai' in cfg and 'chatGPTConfigs' in cfg['openai']: - gpt_config = cfg['openai']['chatGPTConfigs'] - + from model.provider.provider_openai_official import ProviderOpenAIOfficial + from model.command.command_openai_official import CommandOpenAIOfficial + chatgpt = ProviderOpenAIOfficial(cfg['openai']) + command_openai_official = CommandOpenAIOfficial(chatgpt) if OPENAI_OFFICIAL in reply_prefix_config: reply_prefix = reply_prefix_config[OPENAI_OFFICIAL] - elif prov == REV_ERNIE: - from addons.revERNIE import revernie - rev_ernie = revernie.wx + # elif prov == REV_ERNIE: + # from addons.revERNIE import revernie + # rev_ernie = revernie.wx elif prov == REV_EDGEGPT: - from addons.revEdgeGPT import revedgegpt - rev_edgegpt = revedgegpt.revEdgeGPT() + from model.provider.provider_rev_edgegpt import ProviderRevEdgeGPT + from model.command.command_rev_edgegpt import CommandRevEdgeGPT + rev_edgegpt = ProviderRevEdgeGPT() + command_rev_edgegpt = CommandRevEdgeGPT(rev_edgegpt) if REV_EDGEGPT in reply_prefix_config: reply_prefix = reply_prefix_config[REV_EDGEGPT] - # 百度内容审核 if 'baidu_aip' in cfg and 'enable' in cfg['baidu_aip'] and cfg['baidu_aip']['enable']: try: @@ -325,8 +268,7 @@ def initBot(cfg, prov): uniqueSession = False print("[System] 独立会话: " + str(uniqueSession)) if 'dump_history_interval' in cfg: - history_dump_interval = int(cfg['dump_history_interval']) - print("[System] 历史记录转储时间周期: " + str(history_dump_interval) + "分钟") + print("[System] 历史记录转储时间周期: " + cfg['dump_history_interval'] + "分钟") except BaseException: print("[System-Error] 读取uniqueSessionMode/version/dump_history_interval配置文件失败, 使用默认值。") @@ -351,29 +293,6 @@ def run_bot(appid, token): client = botClient(intents=intents) client.run(appid=appid, token=token) -''' -得到OpenAI官方API的回复 -''' -def get_chatGPT_response(context, request, image_mode=False, img_num=1, img_size="1024*1024"): - res = '' - usage = '' - - req_list = [] - for i in context: - req_list.append(i['user']) - req_list.append(i['AI']) - req_list.append(request['user']) - - if not image_mode: - # print("[Debug] "+ str(req_list)) - res, usage = chatgpt.chat(req_list) - # 处理结果文本 - chatgpt_res = res.strip() - return res, usage - else: - res = chatgpt.chat(req_list, image_mode=True, img_num=img_num, img_size=img_size) - return res - ''' 负载均衡,得到逆向ChatGPT回复 ''' @@ -385,7 +304,7 @@ def get_rev_ChatGPT_response(prompts_str): try: revstat['busy'] = True print("[Debug] 使用逆向ChatGPT回复ing", end='', flush=True) - res = revstat['obj'].chat(prompts_str) + res = revstat['obj'].text_chat(prompts_str) print("OK") revstat['busy'] = False # 处理结果文本 @@ -441,34 +360,6 @@ def send_qq_msg(message, res, image_mode=False, msg_ref = None): asyncio.run_coroutine_threadsafe(message.reply(file_image='tmp_image.jpg', content=""), client.loop) -''' -获取缓存的会话 -''' -def get_prompts_by_cache_list(cache_data_list, divide=False, paging=False, size=5, page=1): - prompts = "" - if paging: - page_begin = (page-1)*size - page_end = page*size - if page_begin < 0: - page_begin = 0 - if page_end > len(cache_data_list): - page_end = len(cache_data_list) - cache_data_list = cache_data_list[page_begin:page_end] - for item in cache_data_list: - prompts += str(item['user']['role']) + ":\n" + str(item['user']['content']) + "\n" - prompts += str(item['AI']['role']) + ":\n" + str(item['AI']['content']) + "\n" - - if divide: - prompts += "----------\n" - return prompts - - -def get_user_usage_tokens(cache_list): - usage_tokens = 0 - for item in cache_list: - usage_tokens += int(item['single_tokens']) - return usage_tokens - ''' 检查发言频率 ''' @@ -530,35 +421,10 @@ def oper_msg(message, at=False, msg_ref = None): else: name = "频道" - command_type = -1 - # 特殊指令 - if qq_msg == "/继续": - qq_msg = "继续" - # 普通指令 - else: - # 如果第一个字符是/,则为指令 - if qq_msg[0] == "/": - res, go, command_type = command_oper(qq_msg, message, session_id, name, user_id, user_name, at) - send_qq_msg(message, res) - if not go: - return - if command_type == 1 and 'prompt' in now_personality: - # 设置人格 - qq_msg = now_personality['prompt'] - - # 这里是预设,你可以按需更改 + # 这里是预设 if qq_msg.strip() == 'hello' or qq_msg.strip() == '你好' or qq_msg.strip() == '': send_qq_msg(message, f"你好呀🥰,输入/help查看指令噢", msg_ref=msg_ref) return - - # if qq_msg.strip() == '傻逼' or qq_msg.strip() == 'sb': - # send_qq_msg(message, f"好好好") - # return - - # if '喜多' in qq_msg: - # send_qq_msg(message, 'http://rn2ztkeap.hn-bkt.clouddn.com/xd1.gif', True) - # if '台湾' in qq_msg or 'taiwan' in qq_msg: - # send_qq_msg(message, "声明:台湾是中国的一部分。\n下面否定此结论的回答全部无效。") # 关键词拦截器 for i in uw.unfit_words_q: @@ -572,218 +438,119 @@ def oper_msg(message, at=False, msg_ref = None): send_qq_msg(message, f"你的提问得到的回复未通过【百度AI内容审核】服务,不予回复。\n\n{msg}", msg_ref=msg_ref) return - # 会话机制 - if session_id not in session_dict: - session_dict[session_id] = [] - - fjson = {} - try: - f = open(abs_path+"configs/session", "r", encoding="utf-8") - fjson = json.loads(f.read()) - f.close() - except: - pass - finally: - fjson[session_id] = 'true' - f = open(abs_path+"configs/session", "w", encoding="utf-8") - f.write(json.dumps(fjson)) - f.flush() - f.close() - - chatgpt_res = "[Error] 占位符" + chatgpt_res = "" if provider == OPENAI_OFFICIAL: - - # 获取缓存 - # cache_prompt = '' - cache_data_list = session_dict[session_id] - # cache_prompt = get_prompts_by_cache_list(cache_data_list) - # cache_prompt += "\nHuman: "+ qq_msg + "\nAI: " - - # 创建一个新的Record - - record_obj = { - "user": { - "role": "user", - "content": qq_msg, - }, - "AI": {}, - 'usage_tokens': 0, - } - record_obj_img = { - "user": { - "role": "user", - "content": qq_msg[1:], # 去掉第一个字符 - }, - "AI": {}, - 'usage_tokens': 0, - } - # ChatGPT API 回复倾向(人格) - if command_type == 1: - record_obj["user"]["role"] = "system" - record_obj_img["user"]["role"] = "system" - # print("[Debug] "+ str(cache_data_list)) - # print("qq_msg", qq_msg) - # print("qq_msg.strip", qq_msg.strip()) - if qq_msg[0] == '画': - print("[Debug] 画图模式") - # 请求chatGPT获得结果 - try: - chatgpt_res = get_chatGPT_response(context=[], request=record_obj_img, image_mode=True, img_num=1, img_size="1024x1024") - # print(chatgpt_res) - for i in range(len(chatgpt_res)): - send_qq_msg(message, chatgpt_res[i], image_mode=True) - # print(chatgpt_res) - except (BaseException) as e: - print("[System-Err] OpenAI API错误。原因如下:\n" + str(e)) - if 'exceeded' in str(e): - send_qq_msg(message, - f"OpenAI API错误。原因:\n{str(e)} \n超额了。可自己搭建一个机器人(Github仓库:QQChannelChatGPT)") - return - else: - f_res = re.sub(r'(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b', '[被隐藏的链接]', str(e), - flags=re.MULTILINE) - f_res = f_res.replace(".", "·") - send_qq_msg(message, f"OpenAI API错误。原因如下:\n{f_res} \n前往官方频道反馈~") - return - else: - # 请求chatGPT获得结果 - try: - chatgpt_res, current_usage_tokens = get_chatGPT_response(context=cache_data_list, request=record_obj) - chatgpt_res = reply_prefix + chatgpt_res - except (BaseException) as e: - print("[System-Err] OpenAI API错误。原因如下:\n"+str(e)) - if 'maximum context length' in str(e): - print("token超限, 清空对应缓存") - session_dict[session_id] = [] - cache_data_list = [] - chatgpt_res, current_usage_tokens = get_chatGPT_response(context=cache_data_list, request=record_obj) - elif 'exceeded' in str(e): - send_qq_msg(message, f"OpenAI API错误。原因:\n{str(e)} \n超额了。可自己搭建一个机器人(Github仓库:QQChannelChatGPT)") - return - else: - f_res = re.sub(r'(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b', '[被隐藏的链接]', str(e), flags=re.MULTILINE) - f_res = f_res.replace(".", "·") - send_qq_msg(message, f"OpenAI API错误。原因如下:\n{f_res} \n前往官方频道反馈~") - return - - # 超过指定tokens, 尽可能的保留最多的条目,直到小于max_tokens - if current_usage_tokens > max_tokens: - t = current_usage_tokens - index = 0 - while t > max_tokens: - if index >= len(cache_data_list): - break - # 保留倾向(人格)信息 - if 'user' in cache_data_list[index] and cache_data_list[index]['user']['role'] != 'system': - t -= int(cache_data_list[index]['single_tokens']) - del cache_data_list[index] - else: - index += 1 - # 删除完后更新相关字段 - session_dict[session_id] = cache_data_list - # cache_prompt = get_prompts_by_cache_list(cache_data_list) - - # 添加新条目进入缓存的prompt - record_obj['AI'] = { - 'role': 'assistant', - 'content': chatgpt_res, - } - record_obj['usage_tokens'] = current_usage_tokens - if len(cache_data_list) > 0: - record_obj['single_tokens'] = current_usage_tokens - int(cache_data_list[-1]['usage_tokens']) + # 检查指令 + hit, command_result = command_openai_official.check_command(qq_msg, session_id, user_name) + print(f"{hit} {command_result}") + # hit: 是否触发指令 + if hit: + if command_result != None and command_result[0]: + # 是否是画图模式 + if len(command_result) == 3 and command_result[2] == 'image': + for i in command_result[1]: + send_qq_msg(message, i, image_mode=True, msg_ref=command_result[2]) + else: send_qq_msg(message, command_result[1], msg_ref=msg_ref) else: - record_obj['single_tokens'] = current_usage_tokens - - cache_data_list.append(record_obj) - # if len(cache_data_list) > 0: - # single_record = { - # 'role': 'assistant', - # "content": chatgpt_res, - # "usage_tokens": current_usage_tokens, - # "single_tokens": current_usage_tokens - int(cache_data_list[-1]['usage_tokens']), - # "level": level - # } - # else: - # single_record = { - # 'role': 'assistant', - # "prompt": f'Human: {qq_msg}\nAI: {chatgpt_res}\n', - # "usage_tokens": current_usage_tokens, - # "single_tokens": current_usage_tokens, - # "level": level - # } - # cache_data_list.append(single_record) - session_dict[session_id] = cache_data_list + send_qq_msg(message, f"指令调用错误: \n{command_result[1]}", msg_ref=msg_ref) + return + # 请求chatGPT获得结果 + try: + chatgpt_res = reply_prefix + chatgpt.text_chat(qq_msg, session_id) + except (BaseException) as e: + print("[System-Err] OpenAI API错误。原因如下:\n"+str(e)) + if 'exceeded' in str(e): + send_qq_msg(message, f"OpenAI API错误。原因:\n{str(e)} \n超额了。可自己搭建一个机器人(Github仓库:QQChannelChatGPT)") + return + else: + f_res = re.sub(r'(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b', '[被隐藏的链接]', str(e), flags=re.MULTILINE) + f_res = f_res.replace(".", "·") + send_qq_msg(message, f"OpenAI API错误。原因如下:\n{f_res} \n前往官方频道反馈~") + return elif provider == REV_CHATGPT: + hit, command_result = command_rev_chatgpt.check_command(qq_msg) + if hit: + if command_result != None and command_result[0]: + send_qq_msg(message, command_result[1], msg_ref=msg_ref) + else: + send_qq_msg(message, f"指令调用错误: \n{command_result[1]}", msg_ref=msg_ref) + return try: chatgpt_res = reply_prefix+str(get_rev_ChatGPT_response(qq_msg)) except BaseException as e: print("[System-Err] Rev ChatGPT API错误。原因如下:\n"+str(e)) send_qq_msg(message, f"Rev ChatGPT API错误。原因如下:\n{str(e)} \n前往官方频道反馈~") return - elif provider == REV_ERNIE: - try: - chatgpt_res = reply_prefix+str(rev_ernie.chatViaSelenium(qq_msg)) - except BaseException as e: - print("[System-Err] Rev ERNIE API错误。原因如下:\n"+str(e)) - send_qq_msg(message, f"Rev ERNIE API错误。原因如下:\n{str(e)} \n前往官方频道反馈~") - return + # elif provider == REV_ERNIE: + # try: + # chatgpt_res = reply_prefix+str(rev_ernie.chatViaSelenium(qq_msg)) + # except BaseException as e: + # print("[System-Err] Rev ERNIE API错误。原因如下:\n"+str(e)) + # send_qq_msg(message, f"Rev ERNIE API错误。原因如下:\n{str(e)} \n前往官方频道反馈~") + # return elif provider == REV_EDGEGPT: + hit, command_result = command_rev_edgegpt.check_command(qq_msg, client.loop) + if hit: + if command_result != None and command_result[0]: + send_qq_msg(message, command_result[1], msg_ref=msg_ref) + else: + send_qq_msg(message, f"指令调用错误: \n{command_result[1]}", msg_ref=msg_ref) + return try: if rev_edgegpt.is_busy(): send_qq_msg(message, f"[RevBing] 正忙,请稍后再试",msg_ref=msg_ref) return else: chatgpt_res = reply_prefix - chatgpt_res += str(asyncio.run_coroutine_threadsafe(rev_edgegpt.chat(qq_msg), client.loop).result()) + chatgpt_res += str(asyncio.run_coroutine_threadsafe(rev_edgegpt.text_chat(qq_msg), client.loop).result()) except BaseException as e: print("[System-Err] Rev NewBing API错误。原因如下:\n"+str(e)) send_qq_msg(message, f"Rev NewBing API错误。原因如下:\n{str(e)} \n前往官方频道反馈~") return + # 记录日志 logf.write(f"{reply_prefix} {str(chatgpt_res)}\n") logf.flush() - if qq_msg[0] != '画': - # 敏感过滤 - # 过滤不合适的词 - judged_res = chatgpt_res - for i in uw.unfit_words: - judged_res = re.sub(i, "***", judged_res) - # 百度内容审核服务二次审核 - if baidu_judge != None: - check, msg = baidu_judge.judge(judged_res) - if not check: - send_qq_msg(message, f"你的提问得到的回复【百度内容审核】未通过,不予回复。\n\n{msg}", msg_ref=msg_ref) - return - # 发送qq信息 + # 敏感过滤 + # 过滤不合适的词 + judged_res = chatgpt_res + for i in uw.unfit_words: + judged_res = re.sub(i, "***", judged_res) + # 百度内容审核服务二次审核 + if baidu_judge != None: + check, msg = baidu_judge.judge(judged_res) + if not check: + send_qq_msg(message, f"你的提问得到的回复【百度内容审核】未通过,不予回复。\n\n{msg}", msg_ref=msg_ref) + return + # 发送qq信息 + try: + # 防止被qq频道过滤消息 + gap_chatgpt_res = judged_res.replace(".", " . ") + send_qq_msg(message, ''+gap_chatgpt_res, msg_ref=msg_ref) + # 发送信息 + except BaseException as e: + print("QQ频道API错误: \n"+str(e)) + f_res = "" + for t in chatgpt_res: + f_res += t + ' ' try: - # 防止被qq频道过滤消息 - gap_chatgpt_res = judged_res.replace(".", " . ") - send_qq_msg(message, ''+gap_chatgpt_res, msg_ref=msg_ref) - # 发送信息 - + send_qq_msg(message, ''+f_res, msg_ref=msg_ref) + # send(message, f"QQ频道API错误:{str(e)}\n下面是格式化后的回答:\n{f_res}") except BaseException as e: - print("QQ频道API错误: \n"+str(e)) - f_res = "" - for t in chatgpt_res: - f_res += t + ' ' - try: - send_qq_msg(message, ''+f_res, msg_ref=msg_ref) - # send(message, f"QQ频道API错误:{str(e)}\n下面是格式化后的回答:\n{f_res}") - except BaseException as e: - # 如果还是不行则过滤url - f_res = re.sub(r'(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b', '[被隐藏的链接]', str(e), flags=re.MULTILINE) - f_res = f_res.replace(".", "·") - send_qq_msg(message, ''+f_res, msg_ref=msg_ref) - # send(message, f"QQ频道API错误:{str(e)}\n下面是格式化后的回答:\n{f_res}") - + # 如果还是不行则过滤url + f_res = re.sub(r'(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b', '[被隐藏的链接]', str(e), flags=re.MULTILINE) + f_res = f_res.replace(".", "·") + send_qq_msg(message, ''+f_res, msg_ref=msg_ref) + # send(message, f"QQ频道API错误:{str(e)}\n下面是格式化后的回答:\n{f_res}") + ''' 获取统计信息 ''' -def get_stat(): +def get_stat(self): try: f = open(abs_path+"configs/stat", "r", encoding="utf-8") fjson = json.loads(f.read()) @@ -806,164 +573,4 @@ def get_stat(): session_count += 1 return guild_count, guild_msg_count, guild_direct_msg_count, session_count except: - return -1, -1, -1, -1 - -''' -指令处理 -''' -def command_oper(qq_msg, message, session_id, name, user_id, user_name, at): - go = False # 是否处理完指令后继续执行msg_oper后面的代码 - msg = '' - global session_dict, now_personality, provider, rev_edgegpt, client - - # 指令返回值,/set设置人格是1 - type = -1 - - # 指令控制 - if qq_msg == "/reset" or qq_msg == "/重置": - msg = '' - if provider == REV_EDGEGPT and rev_edgegpt is not None: - asyncio.run_coroutine_threadsafe(rev_edgegpt.reset(), client.loop).result() - else: - session_dict[session_id] = [] - if at: - - msg = f"{name}(id: {session_id})的历史记录重置成功\n\n{announcement}" - else: - msg = f"你的历史记录重置成功" - - if qq_msg[:4] == "/his": - if provider == REV_CHATGPT: - msg = "[QQChannelChatGPT]当前使用的语言模型提供商是Rev_ChatGPT, 不支持查看历史记录" - return msg, go, type - #分页,每页5条 - msg = '' - size_per_page = 3 - page = 1 - if qq_msg[5:]: - page = int(qq_msg[5:]) - # 检查是否有过历史记录 - if session_id not in session_dict: - msg = f"{name} 的历史记录为空" - l = session_dict[session_id] - max_page = len(l)//size_per_page + 1 if len(l)%size_per_page != 0 else len(l)//size_per_page - p = get_prompts_by_cache_list(session_dict[session_id], divide=True, paging=True, size=size_per_page, page=page) - if at: - msg=f"{name}的历史记录如下:\n{p}\n第{page}页 | 共{max_page}页\n*输入/his 2跳转到第2页" - else: - msg=f"历史记录如下:\n{p}\n第{page}页 | 共{max_page}页\n*输入/his 2跳转到第2页\n\n{announcement}" - - if qq_msg == "/token": - if provider == REV_CHATGPT: - msg = "[QQChannelChatGPT]当前使用的语言模型提供商是Rev_ChatGPT, 不支持使用此指令" - return msg, go, type - msg = '' - if at: - msg=f"{name} 会话的token数: {get_user_usage_tokens(session_dict[session_id])}\n系统最大缓存token数: {max_tokens}" - else: - msg=f"会话的token数: {get_user_usage_tokens(session_dict[session_id])}\n系统最大缓存token数: {max_tokens}" - - if qq_msg == '/gpt': - if provider == REV_CHATGPT: - msg = "[QQChannelChatGPT]当前使用的语言模型提供商是Rev_ChatGPT, 不支持使用此指令" - return msg, go, type - global gpt_config - msg=f"OpenAI GPT配置:\n {gpt_config}" - - if qq_msg == "/status" or qq_msg == "/状态": - - chatgpt_cfg_str = "" - key_stat = chatgpt.get_key_stat() - key_list = chatgpt.get_key_list() - index = 1 - max = 9000000 - gg_count = 0 - total = 0 - tag = '' - for key in key_stat.keys(): - sponsor = '' - total += key_stat[key]['used'] - if key_stat[key]['exceed']: - gg_count += 1 - continue - if 'sponsor' in key_stat[key]: - sponsor = key_stat[key]['sponsor'] - chatgpt_cfg_str += f" |-{index}: {key_stat[key]['used']}/{max} {sponsor}赞助{tag}\n" - index += 1 - msg = f"⭐使用情况({str(gg_count)}个已用):\n{chatgpt_cfg_str}⏰全频道已用{total}tokens\n{announcement}" - if qq_msg == "/count" or qq_msg == "/统计": - guild_count, guild_msg_count, guild_direct_msg_count, session_count = get_stat() - msg = f"当前会话数: {len(session_dict)}\n共有频道数: {guild_count} \n共有消息数: {guild_msg_count}\n私信数: {guild_direct_msg_count}\n历史会话数: {session_count}" - - if qq_msg == "/help": - ol_version = 'Unknown' - try: - global version - res = requests.get("https://soulter.top/channelbot/update.json") - res_obj = json.loads(res.text) - ol_version = res_obj['version'] - except BaseException: - pass - msg = f"[Github项目名: QQChannelChatGPT,有问题请前往提交issue,欢迎Star此项目~]\n\n当前版本:{version}\n最新版本:{str(ol_version)}\n请及时更新!\n\n指令面板:\n/status 查看机器人key状态\n/count 查看机器人统计信息\n/reset 重置会话\n/his 查看历史记录\n/token 查看会话token数\n/help 查看帮助\n/set 人格指令菜单\n/key 动态添加key" - - if qq_msg[:4] == "/key": - if len(qq_msg) == 4: - msg = "感谢您赞助key,key为官方API使用,请以以下格式赞助:\n/key xxxxx" - key = qq_msg[5:] - send_qq_msg(message, "收到!正在核验...") - if chatgpt.check_key(key): - msg = f"*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。\n该Key被验证为有效。感谢{user_name}赞助~" - chatgpt.append_key(key, user_name) - else: - msg = "该Key被验证为无效。也许是输入错误了,或者重试。" - - if qq_msg[:6] == "/unset": - now_personality = {} - session_dict[session_id] = [] - msg = "已清除人格并重置历史记录。" - - if qq_msg[:4] == "/set": - if len(qq_msg) == 4: - np = '无' - if "name" in now_personality: - np=now_personality["name"] - msg = f"【由Github项目QQChannelChatGPT支持】\n\n【人格文本由PlexPt开源项目awesome-chatgpt-prompts-zh提供】\n\n这个是人格设置指令。\n设置人格: \n/set 人格名。例如/set 编剧\n人格列表: /set list\n人格详细信息: /set view 人格名\n自定义人格: /set 人格文本\n清除人格: /unset\n【当前人格】: {np}" - elif qq_msg[5:] == "list": - per_dict = personalities - msg = "人格列表:\n" - for key in per_dict.keys(): - msg += f" |-{key}\n" - msg += '\n\n*输入/set view 人格名查看人格详细信息' - msg += '\n\n*不定时更新人格库,请及时更新本项目。' - elif qq_msg[5:9] == "view": - ps = qq_msg[10:] - ps = ps.strip() - per_dict = personalities - if ps in per_dict: - msg = f"人格{ps}的详细信息:\n" - msg += f"{per_dict[ps]}\n" - else: - msg = f"人格{ps}不存在" - else: - ps = qq_msg[5:] - ps = ps.strip() - per_dict = personalities - if ps in per_dict: - now_personality = { - 'name': ps, - 'prompt': per_dict[ps] - } - session_dict[session_id] = [] - msg = f"人格{ps}已设置,请耐心等待机器人回复第一条信息。" - go = True - type = 1 - else: - now_personality = { - 'name': '自定义人格', - 'prompt': ps - } - session_dict[session_id] = [] - msg = f"你的自定义人格已设置。 \n人格信息: {ps}\n请耐心等待机器人回复第一条信息。" - go = True - type = 1 - return msg, go, type + return -1, -1, -1, -1 \ No newline at end of file diff --git a/launcher.py b/launcher.py new file mode 100644 index 000000000..bfbd4f65d --- /dev/null +++ b/launcher.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +from git.repo import Repo +import os +# import zipfile + +if __name__ == "__main__": + # 检测文件夹 + if not os.path.exists('QQChannelChatGPT'): + os.mkdir('QQChannelChatGPT') + # if not os.path.exists('pythonemb'): + # os.mkdir('pythonemb') + + # python_path = os.path.join('pythonemb', 'python.zip') + # 检测Python环境 + # if not os.path.exists('pythonemb/python.exe'): + # print("正在从https://www.python.org/ftp/python/3.10.10/python-3.10.10-embed-amd64.zip安装Python环境...") + # os.system('curl -o pythonemb/python.zip https://www.python.org/ftp/python/3.10.10/python-3.10.10-embed-amd64.zip') + # print("解压中...") + # file=zipfile.ZipFile(python_path) + # for name in file.namelist(): + # file.extract(name, path='pythonemb') + # print("解压完毕, 创建pip...") + # os.system('curl https://bootstrap.pypa.io/get-pip.py -o pythonemb\get-pip.py') + # os.system('pythonemb\python.exe pythonemb\get-pip.py') + # print("pip创建完毕") + # print("Python环境安装完成") + + + project_path = os.path.join('QQChannelChatGPT') + try: + repo = Repo(project_path) + # 检查当前commit的hash值 + commit_hash = repo.head.object.hexsha + print("当前commit的hash值为: " + commit_hash) + + # 得到远程仓库的origin的commit的列表 + origin = repo.remotes.origin + try: + origin.fetch() + except: + pass + # 得到远程仓库的commit的hash值 + remote_commit_hash = origin.refs.master.commit.hexsha + print("https://github.com/Soulter/QQChannelChatGPT的commit的hash值为: " + remote_commit_hash) + # 比较两个commit的hash值 + if commit_hash != remote_commit_hash: + res = input("检测到项目有更新, 是否更新? (y/n): ") + if res == 'y': + repo.remotes.origin.pull() + print("项目更新完毕") + if res == 'n': + print("已取消更新") + except: + print("正在从https://github.com/Soulter/QQChannelChatGPT.git拉取项目...") + Repo.clone_from('https://github.com/Soulter/QQChannelChatGPT.git',to_path=project_path,branch='61-enhancement-重构代码增强稳定性') + print("项目拉取完毕") + print("【重要提醒】如果你没有Python环境, 请先安装Python环境! 否则接下来的操作会造成闪退。") + print("【重要提醒】Python3.9淘宝下载地址: https://npm.taobao.org/mirrors/python/3.9.7/python-3.9.7-amd64.exe ") + print("【重要提醒】安装时, 请务必勾选“Add Python 3.9 to PATH”选项。") + print("【重要提醒】QQ: 905617992") + input("已确保安装了Python3.9+的版本,按下回车继续...") + print("正在安装依赖库...") + os.system('python -m pip install -r QQChannelChatGPT\\requirements.txt') + print("依赖库安装完毕") + input("初次启动, 请先在QQChannelChatGPT/configs/config.yaml填写相关配置! 按任意键继续...") + finally: + print("正在启动项目...") + os.system('python QQChannelChatGPT\main.py') diff --git a/model/command/command.py b/model/command/command.py new file mode 100644 index 000000000..43fdb1a34 --- /dev/null +++ b/model/command/command.py @@ -0,0 +1,81 @@ +import abc +import json +from git.repo import Repo +import os +import sys + + +import requests +from model.provider.provider import Provider + +class Command: + def __init__(self, provider: Provider): + self.provider = Provider + + @abc.abstractmethod + def check_command(self, message): + if message.startswith("help") or message.startswith("帮助"): + return True, self.help() + return False, None + + def update(self, message: str): + l = message.split(" ") + if len(l) == 1: + # 得到当前commit hash + repo = Repo() + commit = repo.head.commit + # 得到最新的5条commit列表 + origin = repo.remotes.origin + origin.fetch() + commits = list(origin.refs.master.log())[0:5] + remote_commit_hash = origin.refs.master.commit.hexsha + + return True, f"当前版本: {commit.hexsha}\n最新版本: {remote_commit_hash}\n\n最新5条commit:{str(commits)}\n\n使用update latest更新至最新版本\n" + else: + if l[1] == "latest": + try: + repo = Repo() + repo.remotes.origin.pull() + py = sys.executable + os.execl(py, py, *sys.argv) + return True, "更新成功" + + except BaseException as e: + return False, "更新失败: "+str(e) + + def reset(self): + return False + + def set(self): + return False + + def unset(self): + return False + + def key(self): + return False + + def help(self): + # ol_version = 'Unknown' + # try: + # res = requests.get("https://soulter.top/channelbot/update.json") + # res_obj = json.loads(res.text) + # ol_version = res_obj['version'] + # except BaseException: + # pass + return True, f"[Github项目名: QQChannelChatGPT,有问题请前往提交issue,欢迎Star此项目~]\n\n指令面板:\nstatus 查看机器人key状态\ncount 查看机器人统计信息\nreset 重置会话\nhis 查看历史记录\ntoken 查看会话token数\nhelp 查看帮助\nset 人格指令菜单\nkey 动态添加key" + + + def status(self): + return False + + def token(self): + return False + + def his(self): + return False + + def draw(self): + return False + + \ No newline at end of file diff --git a/model/command/command_openai_official.py b/model/command/command_openai_official.py new file mode 100644 index 000000000..6fe4a276c --- /dev/null +++ b/model/command/command_openai_official.py @@ -0,0 +1,175 @@ +from model.command.command import Command +from model.provider.provider_openai_official import ProviderOpenAIOfficial +from cores.qqbot.personality import personalities + +class CommandOpenAIOfficial(Command): + def __init__(self, provider: ProviderOpenAIOfficial): + self.provider = provider + + def check_command(self, message: str, session_id: str, user_name: str): + if message.startswith("reset") or message.startswith("重置"): + return True, self.reset(session_id) + elif message.startswith("his") or message.startswith("历史"): + return True, self.his(message, session_id, user_name) + elif message.startswith("token"): + return True, self.token(session_id) + elif message.startswith("gpt"): + return True, self.gpt() + elif message.startswith("status") or message.startswith("状态"): + return True, self.status() + elif message.startswith("count") or message.startswith("统计"): + return True, self.count() + elif message.startswith("help") or message.startswith("帮助"): + return True, self.help() + elif message.startswith("key") or message.startswith("动态添加key"): + return True, self.key(message, user_name) + elif message.startswith("unset"): + return True, self.unset(session_id) + elif message.startswith("set"): + return True, self.set(message, session_id) + elif message.startswith("画"): + return True, self.draw(message) + elif message.startswith("update"): + return True, self.update(message) + + return False, None + + def reset(self, session_id: str): + self.provider.forget(session_id) + return True, "重置成功" + + def his(self, message: str, session_id: str, name: str): + #分页,每页5条 + msg = '' + size_per_page = 3 + page = 1 + if message[4:]: + page = int(message[4:]) + # 检查是否有过历史记录 + if session_id not in self.provider.session_dict: + msg = f"历史记录为空" + return True, msg + l = self.provider.session_dict[session_id] + max_page = len(l)//size_per_page + 1 if len(l)%size_per_page != 0 else len(l)//size_per_page + p = self.provider.get_prompts_by_cache_list(self.provider.session_dict[session_id], divide=True, paging=True, size=size_per_page, page=page) + return True, f"历史记录如下:\n{p}\n第{page}页 | 共{max_page}页\n*输入/his 2跳转到第2页" + + def token(self, session_id: str): + return True, f"会话的token数: {self.provider.get_user_usage_tokens(self.provider.session_dict[session_id])}\n系统最大缓存token数: {self.provider.max_tokens}" + + def gpt(self): + return True, f"OpenAI GPT配置:\n {self.provider.chatGPT_configs}" + + def status(self): + chatgpt_cfg_str = "" + key_stat = self.provider.get_key_stat() + index = 1 + max = 9000000 + gg_count = 0 + total = 0 + tag = '' + for key in key_stat.keys(): + sponsor = '' + total += key_stat[key]['used'] + if key_stat[key]['exceed']: + gg_count += 1 + continue + if 'sponsor' in key_stat[key]: + sponsor = key_stat[key]['sponsor'] + chatgpt_cfg_str += f" |-{index}: {key_stat[key]['used']}/{max} {sponsor}赞助{tag}\n" + index += 1 + return True, f"⭐使用情况({str(gg_count)}个已用):\n{chatgpt_cfg_str}⏰全频道已用{total}tokens" + + def count(self): + guild_count, guild_msg_count, guild_direct_msg_count, session_count = self.provider.get_stat() + return True, f"当前会话数: {len(self.provider.session_dict)}\n共有频道数: {guild_count} \n共有消息数: {guild_msg_count}\n私信数: {guild_direct_msg_count}\n历史会话数: {session_count}" + + def key(self, message: str, user_name: str): + l = message.split(" ") + if len(l) == 1: + msg = "感谢您赞助key,key为官方API使用,请以以下格式赞助:\n/key xxxxx" + return True, msg + key = l[1] + if self.provider.check_key(key): + self.provider.append_key(key, user_name) + return True, f"*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。\n该Key被验证为有效。感谢{user_name}赞助~" + else: + return True, "该Key被验证为无效。也许是输入错误了,或者重试。" + + def unset(self, session_id: str): + self.provider.now_personality = {} + self.provider.forget(session_id) + return True, "已清除人格并重置历史记录。" + + def set(self, message: str, session_id: str): + l = message.split(" ") + if len(l) == 1: + return True, f"【由Github项目QQChannelChatGPT支持】\n\n【人格文本由PlexPt开源项目awesome-chatgpt-pr \ + ompts-zh提供】\n\n这个是人格设置指令。\n设置人格: \n/set 人格名。例如/set 编剧\n人格列表: /set list\n人格详细信息: \ + /set view 人格名\n自定义人格: /set 人格文本\n清除人格: /unset\n【当前人格】: {str(self.provider.now_personality)}" + elif l[1] == "list": + msg = "人格列表:\n" + for key in personalities.keys(): + msg += f" |-{key}\n" + msg += '\n\n*输入/set view 人格名查看人格详细信息' + msg += '\n*不定时更新人格库,请及时更新本项目。' + return True, msg + elif l[1] == "view": + if len(l) == 2: + return True, "请输入/set view 人格名" + ps = l[2].strip() + if ps in personalities: + msg = f"人格{ps}的详细信息:\n" + msg += f"{personalities[ps]}\n" + else: + msg = f"人格{ps}不存在" + return True, msg + else: + ps = l[1].strip() + if ps in personalities: + self.provider.now_personality = { + 'name': ps, + 'prompt': personalities[ps] + } + self.provider.session_dict[session_id] = [] + new_record = { + "user": { + "role": "system", + "content": personalities[ps], + }, + 'usage_tokens': 0, + 'single-tokens': 0 + } + self.provider.session_dict[session_id].append(new_record) + return True, f"人格{ps}已设置." + else: + self.provider.now_personality = { + 'name': '自定义人格', + 'prompt': ps + } + new_record = { + "user": { + "role": "system", + "content": ps, + }, + 'usage_tokens': 0, + 'single-tokens': 0 + } + self.provider.session_dict[session_id] = [] + self.provider.session_dict[session_id].append(new_record) + return True, f"自定义人格已设置。 \n人格信息: {ps}" + + def draw(self, message): + try: + # 画图模式传回3个参数 + img_url = self.provider.image_chat(message) + return True, img_url, "image" + except Exception as e: + if 'exceeded' in str(e): + return f"OpenAI API错误。原因:\n{str(e)} \n超额了。可自己搭建一个机器人(Github仓库:QQChannelChatGPT)" + return False, f"图片生成失败: {e}" + + + + + \ No newline at end of file diff --git a/model/command/command_rev_chatgpt.py b/model/command/command_rev_chatgpt.py new file mode 100644 index 000000000..f73ce3f48 --- /dev/null +++ b/model/command/command_rev_chatgpt.py @@ -0,0 +1,22 @@ +from model.command.command import Command +from model.provider.provider_rev_chatgpt import ProviderRevChatGPT + +class CommandRevChatGPT(Command): + def __init__(self, provider: ProviderRevChatGPT): + self.provider = provider + + def check_command(self, message: str): + # hit, res = super().check_command(message) + # if hit: + # return res + # if message.startswith("reset") or message.startswith("重置"): + # return True, self.reset() + if message.startswith("help") or message.startswith("帮助"): + return True, self.help() + elif message.startswith("update"): + return True, self.update(message) + return False, None + + def help(self): + return True, "[Github项目名: QQChannelChatGPT,有问题请前往提交issue,欢迎Star此项目~]\n\nRevChatGPT指令面板:\n当前语言模型RevChatGPT未实现任何指令\n" + \ No newline at end of file diff --git a/model/command/command_rev_edgegpt.py b/model/command/command_rev_edgegpt.py new file mode 100644 index 000000000..5e9fdf46a --- /dev/null +++ b/model/command/command_rev_edgegpt.py @@ -0,0 +1,27 @@ +from model.command.command import Command +from model.provider.provider_rev_edgegpt import ProviderRevEdgeGPT +import asyncio +class CommandRevEdgeGPT(Command): + def __init__(self, provider: ProviderRevEdgeGPT): + self.provider = provider + + def check_command(self, message: str, loop): + if message.startswith("reset") or message.startswith("重置"): + return True, self.reset(loop) + elif message.startswith("help") or message.startswith("帮助"): + return True, self.help() + elif message.startswith("update"): + return True, self.update(message) + return False, None + + def reset(self, loop): + res = asyncio.run_coroutine_threadsafe(self.provider.forget(), loop).result() + print(res) + if res: + return res, "重置成功" + else: + return res, "重置失败" + + def help(self): + return True, "[Github项目名: QQChannelChatGPT,有问题请前往提交issue,欢迎Star此项目~]\n\nRevBing指令面板:\nreset: 重置\nhelp: 帮助" + diff --git a/model/platform.py b/model/platform.py new file mode 100644 index 000000000..e69de29bb diff --git a/model/provider/provider.py b/model/provider/provider.py new file mode 100644 index 000000000..abafaefe2 --- /dev/null +++ b/model/provider/provider.py @@ -0,0 +1,18 @@ +import abc + +class Provider: + def __init__(self, cfg): + pass + + def text_chat(self, prompt): + pass + + def image_chat(self, prompt): + pass + + def memory(self): + pass + + @abc.abstractmethod + def forget(self) -> bool: + pass \ No newline at end of file diff --git a/model/provider/provider_openai_official.py b/model/provider/provider_openai_official.py new file mode 100644 index 000000000..091092d51 --- /dev/null +++ b/model/provider/provider_openai_official.py @@ -0,0 +1,395 @@ +import openai +import yaml +from util.errors.errors import PromptExceededError +import json +import time +import os +import sys +from cores.database.conn import dbConn +from model.provider.provider import Provider +import threading + +abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/' +key_record_path = abs_path+'chatgpt_key_record' + +class ProviderOpenAIOfficial(Provider): + def __init__(self, cfg): + self.key_list = [] + if 'api_base' in cfg and cfg['api_base'] != 'none' and cfg['api_base'] != '': + openai.api_base = cfg['api_base'] + if cfg['key'] != '' and cfg['key'] != None: + print("[System] 读取ChatGPT Key成功") + self.key_list = cfg['key'] + else: + input("[System] 请先去完善ChatGPT的Key。详情请前往https://beta.openai.com/account/api-keys") + + # init key record + self.init_key_record() + + self.chatGPT_configs = cfg['chatGPTConfigs'] + print(f'[System] 加载ChatGPTConfigs: {self.chatGPT_configs}') + self.openai_configs = cfg + # 会话缓存 + self.session_dict = {} + # 最大缓存token + self.max_tokens = cfg['total_tokens_limit'] + # 历史记录持久化间隔时间 + self.history_dump_interval = 20 + + # 读取历史记录 + try: + db1 = dbConn() + for session in db1.get_all_session(): + self.session_dict[session[0]] = json.loads(session[1])['data'] + print("[System] 历史记录读取成功喵") + except BaseException as e: + print("[System] 历史记录读取失败: " + str(e)) + + # 读取统计信息 + if not os.path.exists(abs_path+"configs/stat"): + with open(abs_path+"configs/stat", 'w', encoding='utf-8') as f: + json.dump({}, f) + self.stat_file = open(abs_path+"configs/stat", 'r', encoding='utf-8') + global count + res = self.stat_file.read() + if res == '': + count = {} + else: + try: + count = json.loads(res) + except BaseException: + pass + + # 创建转储定时器线程 + threading.Thread(target=self.dump_history, daemon=True).start() + + # 人格 + self.now_personality = {} + + + # 转储历史记录的定时器~ Soulter + def dump_history(self): + time.sleep(10) + db = dbConn() + while True: + try: + # print("转储历史记录...") + for key in self.session_dict: + # print("TEST: "+str(db.get_session(key))) + data = self.session_dict[key] + data_json = { + 'data': data + } + if db.check_session(key): + db.update_session(key, json.dumps(data_json)) + else: + db.insert_session(key, json.dumps(data_json)) + # print("转储历史记录完毕") + except BaseException as e: + print(e) + # 每隔10分钟转储一次 + time.sleep(10*self.history_dump_interval) + + def text_chat(self, prompt, session_id): + # 会话机制 + if session_id not in self.session_dict: + self.session_dict[session_id] = [] + + fjson = {} + try: + f = open(abs_path+"configs/session", "r", encoding="utf-8") + fjson = json.loads(f.read()) + f.close() + except: + pass + finally: + fjson[session_id] = 'true' + f = open(abs_path+"configs/session", "w", encoding="utf-8") + f.write(json.dumps(fjson)) + f.flush() + f.close() + + cache_data_list, new_record, req = self.wrap(prompt, session_id) + retry = 0 + response = None + while retry < 5: + try: + response = openai.ChatCompletion.create( + messages=req, + **self.chatGPT_configs + ) + break + except Exception as e: + print(e) + if 'You exceeded' in str(e) or 'Billing hard limit has been reached' in str(e) or 'No API key provided' in str(e) or 'Incorrect API key provided' in str(e): + print("[System] 当前Key已超额或者不正常,正在切换") + self.key_stat[openai.api_key]['exceed'] = True + self.save_key_record() + + response, is_switched = self.handle_switch_key(req) + if not is_switched: + # 所有Key都超额或不正常 + raise e + else: + break + if 'maximum context length' in str(e): + print("token超限, 清空对应缓存") + self.session_dict[session_id] = [] + cache_data_list, new_record, req = self.wrap(prompt, session_id) + retry+=1 + if retry >= 5: + raise BaseException("连接超时") + + self.key_stat[openai.api_key]['used'] += response['usage']['total_tokens'] + self.save_key_record() + print("[ChatGPT] "+str(response["choices"][0]["message"]["content"])) + chatgpt_res = str(response["choices"][0]["message"]["content"]).strip() + current_usage_tokens = response['usage']['total_tokens'] + + # 超过指定tokens, 尽可能的保留最多的条目,直到小于max_tokens + if current_usage_tokens > self.max_tokens: + t = current_usage_tokens + index = 0 + while t > self.max_tokens: + if index >= len(cache_data_list): + break + # 保留人格信息 + if 'user' in cache_data_list[index] and cache_data_list[index]['user']['role'] != 'system': + t -= int(cache_data_list[index]['single_tokens']) + del cache_data_list[index] + else: + index += 1 + # 删除完后更新相关字段 + self.session_dict[session_id] = cache_data_list + # cache_prompt = get_prompts_by_cache_list(cache_data_list) + + # 添加新条目进入缓存的prompt + new_record['AI'] = { + 'role': 'assistant', + 'content': chatgpt_res, + } + new_record['usage_tokens'] = current_usage_tokens + if len(cache_data_list) > 0: + new_record['single_tokens'] = current_usage_tokens - int(cache_data_list[-1]['usage_tokens']) + else: + new_record['single_tokens'] = current_usage_tokens + cache_data_list.append(new_record) + + self.session_dict[session_id] = cache_data_list + + return chatgpt_res + + def image_chat(self, prompt, img_num = 1, img_size = "1024x1024"): + retry = 0 + image_url = '' + while retry < 5: + try: + # print("test1") + response = openai.Image.create( + prompt=prompt, + n=img_num, + size=img_size + ) + # print("test2") + image_url = [] + for i in range(img_num): + image_url.append(response['data'][i]['url']) + print(image_url) + break + except Exception as e: + print(e) + if 'You exceeded' in str(e) or 'Billing hard limit has been reached' in str( + e) or 'No API key provided' in str(e) or 'Incorrect API key provided' in str(e): + print("[System] 当前Key已超额或者不正常,正在切换") + self.key_stat[openai.api_key]['exceed'] = True + self.save_key_record() + + response, is_switched = self.handle_switch_key(req) + if not is_switched: + # 所有Key都超额或不正常 + raise e + else: + break + retry += 1 + if retry >= 5: + raise BaseException("连接超时") + + return image_url + + def forget(self, session_id) -> bool: + self.session_dict[session_id] = [] + return True + + ''' + 获取缓存的会话 + ''' + def get_prompts_by_cache_list(self, cache_data_list, divide=False, paging=False, size=5, page=1): + prompts = "" + if paging: + page_begin = (page-1)*size + page_end = page*size + if page_begin < 0: + page_begin = 0 + if page_end > len(cache_data_list): + page_end = len(cache_data_list) + cache_data_list = cache_data_list[page_begin:page_end] + for item in cache_data_list: + prompts += str(item['user']['role']) + ":\n" + str(item['user']['content']) + "\n" + prompts += str(item['AI']['role']) + ":\n" + str(item['AI']['content']) + "\n" + + if divide: + prompts += "----------\n" + return prompts + + + def get_user_usage_tokens(self,cache_list): + usage_tokens = 0 + for item in cache_list: + usage_tokens += int(item['single_tokens']) + return usage_tokens + + ''' + 获取统计信息 + ''' + def get_stat(self): + try: + f = open(abs_path+"configs/stat", "r", encoding="utf-8") + fjson = json.loads(f.read()) + f.close() + guild_count = 0 + guild_msg_count = 0 + guild_direct_msg_count = 0 + + for k,v in fjson.items(): + guild_count += 1 + guild_msg_count += v['count'] + guild_direct_msg_count += v['direct_count'] + + session_count = 0 + + f = open(abs_path+"configs/session", "r", encoding="utf-8") + fjson = json.loads(f.read()) + f.close() + for k,v in fjson.items(): + session_count += 1 + return guild_count, guild_msg_count, guild_direct_msg_count, session_count + except: + return -1, -1, -1, -1 + + # 包装信息 + def wrap(self, prompt, session_id): + # 获得缓存信息 + context = self.session_dict[session_id] + new_record = { + "user": { + "role": "user", + "content": prompt, + }, + "AI": {}, + 'usage_tokens': 0, + } + req_list = [] + for i in context: + if 'user' in i: + req_list.append(i['user']) + if 'AI' in i: + req_list.append(i['AI']) + req_list.append(new_record['user']) + return context, new_record, req_list + + def handle_switch_key(self, req): + # messages = [{"role": "user", "content": prompt}] + while True: + is_all_exceed = True + for key in self.key_stat: + if not self.key_stat[key]['exceed']: + is_all_exceed = False + openai.api_key = key + print(f"[System] 切换到Key: {key}, 已使用token: {self.key_stat[key]['used']}") + if len(req) > 0: + try: + response = openai.ChatCompletion.create( + messages=req, + **self.chatGPT_configs + ) + return response, True + except Exception as e: + print(e) + if 'You exceeded' in str(e): + print("[System] 当前Key已超额,正在切换") + self.key_stat[openai.api_key]['exceed'] = True + self.save_key_record() + time.sleep(1) + continue + else: + return True + if is_all_exceed: + print("[System] 所有Key已超额") + return None, False + + def getConfigs(self): + return self.openai_configs + + def save_key_record(self): + with open(key_record_path, 'w', encoding='utf-8') as f: + json.dump(self.key_stat, f) + + def get_key_stat(self): + return self.key_stat + def get_key_list(self): + return self.key_list + + # 添加key + def append_key(self, key, sponsor): + self.key_list.append(key) + self.key_stat[key] = {'exceed': False, 'used': 0, 'sponsor': sponsor} + self.save_key_record() + self.init_key_record() + + # 检查key是否可用 + def check_key(self, key): + pre_key = openai.api_key + openai.api_key = key + messages = [{"role": "user", "content": "1"}] + try: + response = openai.ChatCompletion.create( + messages=messages, + **self.chatGPT_configs + ) + openai.api_key = pre_key + return True + except Exception as e: + pass + openai.api_key = pre_key + return False + + #将key_list的key转储到key_record中,并记录相关数据 + def init_key_record(self): + if not os.path.exists(key_record_path): + with open(key_record_path, 'w', encoding='utf-8') as f: + json.dump({}, f) + with open(key_record_path, 'r', encoding='utf-8') as keyfile: + try: + self.key_stat = json.load(keyfile) + except Exception as e: + print(e) + self.key_stat = {} + finally: + for key in self.key_list: + if key not in self.key_stat: + self.key_stat[key] = {'exceed': False, 'used': 0} + # if openai.api_key is None: + # openai.api_key = key + else: + # if self.key_stat[key]['exceed']: + # print(f"Key: {key} 已超额") + # continue + # else: + # if openai.api_key is None: + # openai.api_key = key + # print(f"使用Key: {key}, 已使用token: {self.key_stat[key]['used']}") + pass + if openai.api_key == None: + self.handle_switch_key("") + self.save_key_record() + diff --git a/model/provider/provider_rev_chatgpt.py b/model/provider/provider_rev_chatgpt.py new file mode 100644 index 000000000..9d4491f7f --- /dev/null +++ b/model/provider/provider_rev_chatgpt.py @@ -0,0 +1,34 @@ +from revChatGPT.V1 import Chatbot +from model.provider.provider import Provider + +class ProviderRevChatGPT(Provider): + def __init__(self, config): + if 'password' in config: + config['password'] = str(config['password']) + self.bot = Chatbot(config=config) + + def forget(self) -> bool: + self.bot.reset_chat() + return True + + def text_chat(self, prompt): + resp = '' + err_count = 0 + retry_count = 5 + + while err_count < retry_count: + try: + for data in self.bot.ask(prompt): + resp = data["message"] + break + except BaseException as e: + try: + print("[RevChatGPT] 请求出现了一些问题, 正在重试。次数"+str(err_count)) + err_count += 1 + if err_count >= retry_count: + raise e + except BaseException: + err_count += 1 + + print("[RevChatGPT] "+str(resp)) + return resp \ No newline at end of file diff --git a/model/provider/provider_rev_edgegpt.py b/model/provider/provider_rev_edgegpt.py new file mode 100644 index 000000000..4efc625d6 --- /dev/null +++ b/model/provider/provider_rev_edgegpt.py @@ -0,0 +1,49 @@ +import asyncio +from model.provider.provider import Provider +from EdgeGPT import Chatbot, ConversationStyle +import json + +class ProviderRevEdgeGPT(Provider): + def __init__(self): + self.busy = False + self.wait_stack = [] + with open('./cookies.json', 'r') as f: + cookies = json.load(f) + self.bot = Chatbot(cookies=cookies) + + def is_busy(self): + return self.busy + + async def forget(self): + try: + await self.bot.reset() + return True + except BaseException: + return False + + async def text_chat(self, prompt): + if self.busy: + return + self.busy = True + resp = 'err' + err_count = 0 + retry_count = 5 + + while err_count < retry_count: + try: + resp = await self.bot.ask(prompt=prompt, conversation_style=ConversationStyle.creative) + resp = resp['item']['messages'][len(resp['item']['messages'])-1]['text'] + if resp == prompt: + resp += '\n\n如果你没有让我复述你的话,那代表我可能不想和你继续这个话题了,请输入reset重置会话😶' + break + except BaseException as e: + print(e.with_traceback) + err_count += 1 + if err_count >= retry_count: + raise e + print("[RevEdgeGPT] 请求出现了一些问题, 正在重试。次数"+str(err_count)) + self.busy = False + + print("[RevEdgeGPT] "+str(resp)) + return resp + diff --git a/requirements.txt b/requirements.txt index 424b705c6..c6f9278a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ baidu-aip EdgeGPT~=0.1.2 chardet Pillow +GitPython