From 043b2f0198a43729f638d00b1186507c070a6b76 Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Mon, 26 Dec 2022 19:37:25 +0800 Subject: [PATCH 01/12] =?UTF-8?q?refactor:=20=E7=8B=AC=E7=AB=8B=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=A4=84=E7=90=86=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/qqbot/manager.py | 175 ++---------------------------------------- pkg/qqbot/process.py | 176 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 168 deletions(-) create mode 100644 pkg/qqbot/process.py diff --git a/pkg/qqbot/manager.py b/pkg/qqbot/manager.py index b1fdcd14..ed362705 100644 --- a/pkg/qqbot/manager.py +++ b/pkg/qqbot/manager.py @@ -10,18 +10,14 @@ from mirai import At, GroupMessage, MessageEvent, Mirai, Plain, StrangerMessage, import config import pkg.openai.session import pkg.openai.manager -from func_timeout import func_set_timeout, FunctionTimedOut -import datetime +from func_timeout import FunctionTimedOut import logging import pkg.qqbot.filter - -help_text = config.help_message +import pkg.qqbot.process as processor inst = None -processing = [] - # 并行运行 def go(func, args=()): @@ -54,7 +50,6 @@ def check_response_rule(text: str) -> (bool, str): # 控制QQ消息输入输出的类 class QQBotManager: - timeout = 60 retry = 3 bot = None @@ -93,6 +88,9 @@ class QQBotManager: ) ) + else: + raise Exception("未知的适配器类型") + @bot.on(FriendMessage) async def on_friend_message(event: FriendMessage): go(self.on_person_message, (event,)) @@ -110,163 +108,6 @@ class QQBotManager: global inst inst = self - # 统一的消息处理函数 - @func_set_timeout(timeout) - def process_message(self, launcher_type: str, launcher_id: int, text_message: str) -> str: - global processing - reply = '' - session_name = "{}_{}".format(launcher_type, launcher_id) - - pkg.openai.session.get_session(session_name).acquire_response_lock() - - try: - if session_name in processing: - pkg.openai.session.get_session(session_name).release_response_lock() - return "[bot]err:正在处理中,请稍后再试" - - processing.append(session_name) - - try: - - if text_message.startswith('!') or text_message.startswith("!"): # 指令 - try: - logging.info( - "[{}]发起指令:{}".format(session_name, text_message[:min(20, len(text_message))] + ( - "..." if len(text_message) > 20 else ""))) - - cmd = text_message[1:].strip().split(' ')[0] - - params = text_message[1:].strip().split(' ')[1:] - if cmd == 'help': - reply = "[bot]" + help_text - elif cmd == 'reset': - pkg.openai.session.get_session(session_name).reset(explicit=True) - reply = "[bot]会话已重置" - elif cmd == 'last': - result = pkg.openai.session.get_session(session_name).last_session() - if result is None: - reply = "[bot]没有前一次的对话" - else: - datetime_str = datetime.datetime.fromtimestamp(result.create_timestamp).strftime( - '%Y-%m-%d %H:%M:%S') - reply = "[bot]已切换到前一次的对话:\n创建时间:{}\n".format( - datetime_str) + result.prompt[ - :min(100, - len(result.prompt))] + \ - ("..." if len(result.prompt) > 100 else "#END#") - elif cmd == 'next': - result = pkg.openai.session.get_session(session_name).next_session() - if result is None: - reply = "[bot]没有后一次的对话" - else: - datetime_str = datetime.datetime.fromtimestamp(result.create_timestamp).strftime( - '%Y-%m-%d %H:%M:%S') - reply = "[bot]已切换到后一次的对话:\n创建时间:{}\n".format( - datetime_str) + result.prompt[ - :min(100, - len(result.prompt))] + \ - ("..." if len(result.prompt) > 100 else "#END#") - elif cmd == 'prompt': - reply = "[bot]当前对话所有内容:\n" + pkg.openai.session.get_session(session_name).prompt - elif cmd == 'list': - pkg.openai.session.get_session(session_name).persistence() - page = 0 - - if len(params) > 0: - try: - page = int(params[0]) - except ValueError: - pass - - results = pkg.openai.session.get_session(session_name).list_history(page=page) - if len(results) == 0: - reply = "[bot]第{}页没有历史会话".format(page) - else: - reply = "[bot]历史会话 第{}页:\n".format(page) - current = -1 - for i in range(len(results)): - # 时间(使用create_timestamp转换) 序号 部分内容 - datetime_obj = datetime.datetime.fromtimestamp(results[i]['create_timestamp']) - reply += "#{} 创建:{} {}\n".format(i + page * 10, - datetime_obj.strftime("%Y-%m-%d %H:%M:%S"), - results[i]['prompt'][ - :min(20, len(results[i]['prompt']))]) - if results[i]['create_timestamp'] == pkg.openai.session.get_session( - session_name).create_timestamp: - current = i + page * 10 - - reply += "\n以上信息倒序排列" - if current != -1: - reply += ",当前会话是 #{}\n".format(current) - else: - reply += ",当前处于全新会话或不在此页" - elif cmd == 'usage': - api_keys = pkg.openai.manager.get_inst().key_mgr.api_key - reply = "[bot]api-key使用情况:(阈值:{})\n\n".format( - pkg.openai.manager.get_inst().key_mgr.api_key_usage_threshold) - - using_key_name = "" - for api_key in api_keys: - reply += "{}:\n - {}字 {}%\n".format(api_key, - pkg.openai.manager.get_inst().key_mgr.get_usage( - api_keys[api_key]), - round( - pkg.openai.manager.get_inst().key_mgr.get_usage( - api_keys[ - api_key]) / pkg.openai.manager.get_inst().key_mgr.api_key_usage_threshold * 100, - 3)) - if api_keys[api_key] == pkg.openai.manager.get_inst().key_mgr.using_key: - using_key_name = api_key - reply += "\n当前使用:{}".format(using_key_name) - except Exception as e: - self.notify_admin("{}指令执行失败:{}".format(session_name, e)) - logging.exception(e) - reply = "[bot]err:{}".format(e) - else: # 消息 - logging.info("[{}]发送消息:{}".format(session_name, text_message[:min(20, len(text_message))] + ( - "..." if len(text_message) > 20 else ""))) - - session = pkg.openai.session.get_session(session_name) - try: - prefix = "[GPT]" if hasattr(config, "show_prefix") and config.show_prefix else "" - reply = prefix + session.append(text_message) - except openai.error.APIConnectionError as e: - self.notify_admin("{}会话调用API失败:{}".format(session_name, e)) - reply = "[bot]err:调用API失败,请重试或联系作者,或等待修复" - except openai.error.RateLimitError as e: - # 尝试切换api-key - current_tokens_amt = pkg.openai.manager.get_inst().key_mgr.get_usage(pkg.openai.manager.get_inst().key_mgr.get_using_key()) - pkg.openai.manager.get_inst().key_mgr.set_current_exceeded() - switched, name = pkg.openai.manager.get_inst().key_mgr.auto_switch() - - if not switched: - self.notify_admin("API调用额度超限({}),请向OpenAI账户充值或在config.py中更换api_key".format(current_tokens_amt)) - reply = "[bot]err:API调用额度超额,请联系作者,或等待修复" - else: - openai.api_key = pkg.openai.manager.get_inst().key_mgr.get_using_key() - self.notify_admin("API调用额度超限({}),已切换到{}".format(current_tokens_amt, name)) - reply = "[bot]err:API调用额度超额,已自动切换,请重新发送消息" - except openai.error.InvalidRequestError as e: - self.notify_admin("{}API调用参数错误:{}\n\n这可能是由于config.py中的prompt_submit_length参数或" - "completion_api_params中的max_tokens参数数值过大导致的,请尝试将其降低".format( - session_name, e)) - reply = "[bot]err:API调用参数错误,请联系作者,或等待修复" - except Exception as e: - logging.exception(e) - reply = "[bot]err:{}".format(e) - - logging.info( - "回复[{}]消息:{}".format(session_name, - reply[:min(100, len(reply))] + ("..." if len(reply) > 100 else ""))) - reply = self.reply_filter.process(reply) - - finally: - processing.remove(session_name) - finally: - pkg.openai.session.get_session(session_name).release_response_lock() - - return reply - def send(self, event, msg): asyncio.run(self.bot.send(event, msg)) @@ -286,7 +127,7 @@ class QQBotManager: failed = 0 for i in range(self.retry): try: - reply = self.process_message('person', event.sender.id, str(event.message_chain)) + reply = processor.process_message('person', event.sender.id, str(event.message_chain)) break except FunctionTimedOut: pkg.openai.session.get_session('person_{}'.format(event.sender.id)).release_response_lock() @@ -312,13 +153,11 @@ class QQBotManager: if At(self.bot.qq) in event.message_chain: event.message_chain.remove(At(self.bot.qq)) - processing.append("group_{}".format(event.sender.id)) - # 超时则重试,重试超过次数则放弃 failed = 0 for i in range(self.retry): try: - replys = self.process_message('group', event.group.id, + replys = processor.process_message('group', event.group.id, str(event.message_chain).strip() if text is None else text) break except FunctionTimedOut: diff --git a/pkg/qqbot/process.py b/pkg/qqbot/process.py new file mode 100644 index 00000000..82cf0f5c --- /dev/null +++ b/pkg/qqbot/process.py @@ -0,0 +1,176 @@ +# 此模块提供了消息处理的具体逻辑的接口 +import datetime + +import pkg.qqbot.manager as manager +from func_timeout import func_set_timeout +import logging +import openai + +import config + +import pkg.openai.session +import pkg.openai.manager + +processing = [] + + +@func_set_timeout(config.process_message_timeout) +def process_message(launcher_type: str, launcher_id: int, text_message: str) -> str: + global processing + + mgr = pkg.openai.manager.get_inst() + + reply = '' + session_name = "{}_{}".format(launcher_type, launcher_id) + + pkg.openai.session.get_session(session_name).acquire_response_lock() + + try: + if session_name in processing: + pkg.openai.session.get_session(session_name).release_response_lock() + return "[bot]err:正在处理中,请稍后再试" + + processing.append(session_name) + + try: + + if text_message.startswith('!') or text_message.startswith("!"): # 指令 + try: + logging.info( + "[{}]发起指令:{}".format(session_name, text_message[:min(20, len(text_message))] + ( + "..." if len(text_message) > 20 else ""))) + + cmd = text_message[1:].strip().split(' ')[0] + + params = text_message[1:].strip().split(' ')[1:] + if cmd == 'help': + reply = "[bot]" + config.help_message + elif cmd == 'reset': + pkg.openai.session.get_session(session_name).reset(explicit=True) + reply = "[bot]会话已重置" + elif cmd == 'last': + result = pkg.openai.session.get_session(session_name).last_session() + if result is None: + reply = "[bot]没有前一次的对话" + else: + datetime_str = datetime.datetime.fromtimestamp(result.create_timestamp).strftime( + '%Y-%m-%d %H:%M:%S') + reply = "[bot]已切换到前一次的对话:\n创建时间:{}\n".format( + datetime_str) + result.prompt[ + :min(100, + len(result.prompt))] + \ + ("..." if len(result.prompt) > 100 else "#END#") + elif cmd == 'next': + result = pkg.openai.session.get_session(session_name).next_session() + if result is None: + reply = "[bot]没有后一次的对话" + else: + datetime_str = datetime.datetime.fromtimestamp(result.create_timestamp).strftime( + '%Y-%m-%d %H:%M:%S') + reply = "[bot]已切换到后一次的对话:\n创建时间:{}\n".format( + datetime_str) + result.prompt[ + :min(100, + len(result.prompt))] + \ + ("..." if len(result.prompt) > 100 else "#END#") + elif cmd == 'prompt': + reply = "[bot]当前对话所有内容:\n" + pkg.openai.session.get_session(session_name).prompt + elif cmd == 'list': + pkg.openai.session.get_session(session_name).persistence() + page = 0 + + if len(params) > 0: + try: + page = int(params[0]) + except ValueError: + pass + + results = pkg.openai.session.get_session(session_name).list_history(page=page) + if len(results) == 0: + reply = "[bot]第{}页没有历史会话".format(page) + else: + reply = "[bot]历史会话 第{}页:\n".format(page) + current = -1 + for i in range(len(results)): + # 时间(使用create_timestamp转换) 序号 部分内容 + datetime_obj = datetime.datetime.fromtimestamp(results[i]['create_timestamp']) + reply += "#{} 创建:{} {}\n".format(i + page * 10, + datetime_obj.strftime("%Y-%m-%d %H:%M:%S"), + results[i]['prompt'][ + :min(20, len(results[i]['prompt']))]) + if results[i]['create_timestamp'] == pkg.openai.session.get_session( + session_name).create_timestamp: + current = i + page * 10 + + reply += "\n以上信息倒序排列" + if current != -1: + reply += ",当前会话是 #{}\n".format(current) + else: + reply += ",当前处于全新会话或不在此页" + elif cmd == 'usage': + api_keys = pkg.openai.manager.get_inst().key_mgr.api_key + reply = "[bot]api-key使用情况:(阈值:{})\n\n".format( + pkg.openai.manager.get_inst().key_mgr.api_key_usage_threshold) + + using_key_name = "" + for api_key in api_keys: + reply += "{}:\n - {}字 {}%\n".format(api_key, + pkg.openai.manager.get_inst().key_mgr.get_usage( + api_keys[api_key]), + round( + pkg.openai.manager.get_inst().key_mgr.get_usage( + api_keys[ + api_key]) / pkg.openai.manager.get_inst().key_mgr.api_key_usage_threshold * 100, + 3)) + if api_keys[api_key] == pkg.openai.manager.get_inst().key_mgr.using_key: + using_key_name = api_key + reply += "\n当前使用:{}".format(using_key_name) + except Exception as e: + mgr.notify_admin("{}指令执行失败:{}".format(session_name, e)) + logging.exception(e) + reply = "[bot]err:{}".format(e) + else: # 消息 + logging.info("[{}]发送消息:{}".format(session_name, text_message[:min(20, len(text_message))] + ( + "..." if len(text_message) > 20 else ""))) + + session = pkg.openai.session.get_session(session_name) + try: + prefix = "[GPT]" if hasattr(config, "show_prefix") and config.show_prefix else "" + reply = prefix + session.append(text_message) + except openai.error.APIConnectionError as e: + mgr.notify_admin("{}会话调用API失败:{}".format(session_name, e)) + reply = "[bot]err:调用API失败,请重试或联系作者,或等待修复" + except openai.error.RateLimitError as e: + # 尝试切换api-key + current_tokens_amt = pkg.openai.manager.get_inst().key_mgr.get_usage( + pkg.openai.manager.get_inst().key_mgr.get_using_key()) + pkg.openai.manager.get_inst().key_mgr.set_current_exceeded() + switched, name = pkg.openai.manager.get_inst().key_mgr.auto_switch() + + if not switched: + mgr.notify_admin("API调用额度超限({}),请向OpenAI账户充值或在config.py中更换api_key".format( + current_tokens_amt)) + reply = "[bot]err:API调用额度超额,请联系作者,或等待修复" + else: + openai.api_key = pkg.openai.manager.get_inst().key_mgr.get_using_key() + mgr.notify_admin("API调用额度超限({}),已切换到{}".format(current_tokens_amt, name)) + reply = "[bot]err:API调用额度超额,已自动切换,请重新发送消息" + except openai.error.InvalidRequestError as e: + mgr.notify_admin("{}API调用参数错误:{}\n\n这可能是由于config.py中的prompt_submit_length参数或" + "completion_api_params中的max_tokens参数数值过大导致的,请尝试将其降低".format( + session_name, e)) + reply = "[bot]err:API调用参数错误,请联系作者,或等待修复" + except Exception as e: + logging.exception(e) + reply = "[bot]err:{}".format(e) + + logging.info( + "回复[{}]消息:{}".format(session_name, + reply[:min(100, len(reply))] + ("..." if len(reply) > 100 else ""))) + reply = mgr.reply_filter.process(reply) + + finally: + processing.remove(session_name) + finally: + pkg.openai.session.get_session(session_name).release_response_lock() + + return reply From 502082aa156a64089f3935ef4844013246922c21 Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Mon, 26 Dec 2022 20:56:52 +0800 Subject: [PATCH 02/12] =?UTF-8?q?fix:=20=E5=8F=AA=E6=9C=89=E4=B8=80?= =?UTF-8?q?=E4=B8=AAapikey=E5=B9=B6=E8=BF=87=E6=9C=9F=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E6=97=B6=E4=B8=8D=E5=81=9C=E8=BE=93=E5=87=BAapikey=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/openai/keymgr.py | 2 +- pkg/qqbot/manager.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/openai/keymgr.py b/pkg/openai/keymgr.py index 3f878d90..75d7b196 100644 --- a/pkg/openai/keymgr.py +++ b/pkg/openai/keymgr.py @@ -53,7 +53,7 @@ class KeysManager: return True, key_name self.using_key = list(self.api_key.values())[0] - logging.info("使用api-key:" + self.using_key) + logging.info("使用api-key:" + self.api_key.keys()[0]) return False, "" diff --git a/pkg/qqbot/manager.py b/pkg/qqbot/manager.py index ed362705..c117ea26 100644 --- a/pkg/qqbot/manager.py +++ b/pkg/qqbot/manager.py @@ -113,7 +113,6 @@ class QQBotManager: # 私聊消息处理 def on_person_message(self, event: MessageEvent): - global processing reply = '' @@ -144,7 +143,6 @@ class QQBotManager: # 群消息处理 def on_group_message(self, event: GroupMessage): - global processing reply = '' From 8bd91025adcfd66590017e5319d176636d0387de Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Mon, 26 Dec 2022 23:30:37 +0800 Subject: [PATCH 03/12] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config-template.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/config-template.py b/config-template.py index bee7a561..838a2b24 100644 --- a/config-template.py +++ b/config-template.py @@ -30,13 +30,7 @@ openai_config = { }, } -# [可选] 机器人的配置 -#user_name: 管理员(主人)的名字 -#bot_name: 机器人的名字 -user_name = 'You' -bot_name = 'Bot' - -# [可选] 情景预设(机器人人格) +# 情景预设(机器人人格) # 每个会话的预设信息,影响所有会话,无视指令重置 # 可以通过这个字段指定某些情况的回复,可直接用自然语言描述指令 # 例如: 如果我之后想获取帮助,请你说“输入!help获取帮助”, @@ -86,6 +80,12 @@ completion_api_params = { # 消息处理的超时时间,单位为秒 process_message_timeout = 15 +# 机器人的配置 +# user_name: 管理员(主人)的名字 +# bot_name: 机器人的名字 +user_name = 'You' +bot_name = 'Bot' + # 回复消息时是否显示[GPT]前缀 show_prefix = False From 5c0cd841f9f8392365bb03719548cfc7bedce29b Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Mon, 26 Dec 2022 23:53:56 +0800 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20process=5Fmessage=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E7=8E=B0=E5=9C=A8=E8=BF=94=E5=9B=9E=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/qqbot/manager.py | 8 ++-- pkg/qqbot/process.py | 89 +++++++++++++++++++++++--------------------- 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/pkg/qqbot/manager.py b/pkg/qqbot/manager.py index c117ea26..af58babf 100644 --- a/pkg/qqbot/manager.py +++ b/pkg/qqbot/manager.py @@ -136,9 +136,9 @@ class QQBotManager: if failed == self.retry: pkg.openai.session.get_session('person_{}'.format(event.sender.id)).release_response_lock() self.notify_admin("{} 请求超时".format("person_{}".format(event.sender.id))) - reply = "[bot]err:请求超时" + reply = ["[bot]err:请求超时"] - if reply != '': + if reply: return self.send(event, reply) # 群消息处理 @@ -164,7 +164,7 @@ class QQBotManager: if failed == self.retry: self.notify_admin("{} 请求超时".format("group_{}".format(event.sender.id))) - replys = "[bot]err:请求超时" + replys = ["[bot]err:请求超时"] return replys @@ -179,7 +179,7 @@ class QQBotManager: # 直接调用 reply = process() - if reply != '': + if reply: return self.send(event, reply) # 通知系统管理员 diff --git a/pkg/qqbot/process.py b/pkg/qqbot/process.py index 82cf0f5c..a646dc99 100644 --- a/pkg/qqbot/process.py +++ b/pkg/qqbot/process.py @@ -15,12 +15,12 @@ processing = [] @func_set_timeout(config.process_message_timeout) -def process_message(launcher_type: str, launcher_id: int, text_message: str) -> str: +def process_message(launcher_type: str, launcher_id: int, text_message: str) -> []: global processing - mgr = pkg.openai.manager.get_inst() + mgr = pkg.qqbot.manager.get_inst() - reply = '' + reply = [] session_name = "{}_{}".format(launcher_type, launcher_id) pkg.openai.session.get_session(session_name).acquire_response_lock() @@ -28,7 +28,7 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str) -> try: if session_name in processing: pkg.openai.session.get_session(session_name).release_response_lock() - return "[bot]err:正在处理中,请稍后再试" + return ["[bot]err:正在处理中,请稍后再试"] processing.append(session_name) @@ -44,36 +44,36 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str) -> params = text_message[1:].strip().split(' ')[1:] if cmd == 'help': - reply = "[bot]" + config.help_message + reply = ["[bot]" + config.help_message] elif cmd == 'reset': pkg.openai.session.get_session(session_name).reset(explicit=True) - reply = "[bot]会话已重置" + reply = ["[bot]会话已重置"] elif cmd == 'last': result = pkg.openai.session.get_session(session_name).last_session() if result is None: - reply = "[bot]没有前一次的对话" + reply = ["[bot]没有前一次的对话"] else: datetime_str = datetime.datetime.fromtimestamp(result.create_timestamp).strftime( '%Y-%m-%d %H:%M:%S') - reply = "[bot]已切换到前一次的对话:\n创建时间:{}\n".format( + reply = ["[bot]已切换到前一次的对话:\n创建时间:{}\n".format( datetime_str) + result.prompt[ :min(100, len(result.prompt))] + \ - ("..." if len(result.prompt) > 100 else "#END#") + ("..." if len(result.prompt) > 100 else "#END#")] elif cmd == 'next': result = pkg.openai.session.get_session(session_name).next_session() if result is None: - reply = "[bot]没有后一次的对话" + reply = ["[bot]没有后一次的对话"] else: datetime_str = datetime.datetime.fromtimestamp(result.create_timestamp).strftime( '%Y-%m-%d %H:%M:%S') - reply = "[bot]已切换到后一次的对话:\n创建时间:{}\n".format( + reply = ["[bot]已切换到后一次的对话:\n创建时间:{}\n".format( datetime_str) + result.prompt[ :min(100, len(result.prompt))] + \ - ("..." if len(result.prompt) > 100 else "#END#") + ("..." if len(result.prompt) > 100 else "#END#")] elif cmd == 'prompt': - reply = "[bot]当前对话所有内容:\n" + pkg.openai.session.get_session(session_name).prompt + reply = ["[bot]当前对话所有内容:\n" + pkg.openai.session.get_session(session_name).prompt] elif cmd == 'list': pkg.openai.session.get_session(session_name).persistence() page = 0 @@ -86,48 +86,52 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str) -> results = pkg.openai.session.get_session(session_name).list_history(page=page) if len(results) == 0: - reply = "[bot]第{}页没有历史会话".format(page) + reply = ["[bot]第{}页没有历史会话".format(page)] else: - reply = "[bot]历史会话 第{}页:\n".format(page) + reply_str = "[bot]历史会话 第{}页:\n".format(page) current = -1 for i in range(len(results)): # 时间(使用create_timestamp转换) 序号 部分内容 datetime_obj = datetime.datetime.fromtimestamp(results[i]['create_timestamp']) - reply += "#{} 创建:{} {}\n".format(i + page * 10, - datetime_obj.strftime("%Y-%m-%d %H:%M:%S"), - results[i]['prompt'][ - :min(20, len(results[i]['prompt']))]) + reply_str += "#{} 创建:{} {}\n".format(i + page * 10, + datetime_obj.strftime("%Y-%m-%d %H:%M:%S"), + results[i]['prompt'][ + :min(20, len(results[i]['prompt']))]) if results[i]['create_timestamp'] == pkg.openai.session.get_session( session_name).create_timestamp: current = i + page * 10 - reply += "\n以上信息倒序排列" + reply_str += "\n以上信息倒序排列" if current != -1: - reply += ",当前会话是 #{}\n".format(current) + reply_str += ",当前会话是 #{}\n".format(current) else: - reply += ",当前处于全新会话或不在此页" + reply_str += ",当前处于全新会话或不在此页" + + reply = [reply_str] elif cmd == 'usage': api_keys = pkg.openai.manager.get_inst().key_mgr.api_key - reply = "[bot]api-key使用情况:(阈值:{})\n\n".format( + reply_str = "[bot]api-key使用情况:(阈值:{})\n\n".format( pkg.openai.manager.get_inst().key_mgr.api_key_usage_threshold) using_key_name = "" for api_key in api_keys: - reply += "{}:\n - {}字 {}%\n".format(api_key, - pkg.openai.manager.get_inst().key_mgr.get_usage( - api_keys[api_key]), - round( + reply_str += "{}:\n - {}字 {}%\n".format(api_key, pkg.openai.manager.get_inst().key_mgr.get_usage( - api_keys[ - api_key]) / pkg.openai.manager.get_inst().key_mgr.api_key_usage_threshold * 100, - 3)) + api_keys[api_key]), + round( + pkg.openai.manager.get_inst().key_mgr.get_usage( + api_keys[ + api_key]) / pkg.openai.manager.get_inst().key_mgr.api_key_usage_threshold * 100, + 3)) if api_keys[api_key] == pkg.openai.manager.get_inst().key_mgr.using_key: using_key_name = api_key - reply += "\n当前使用:{}".format(using_key_name) + reply_str += "\n当前使用:{}".format(using_key_name) + + reply = [reply_str] except Exception as e: mgr.notify_admin("{}指令执行失败:{}".format(session_name, e)) logging.exception(e) - reply = "[bot]err:{}".format(e) + reply = ["[bot]err:{}".format(e)] else: # 消息 logging.info("[{}]发送消息:{}".format(session_name, text_message[:min(20, len(text_message))] + ( "..." if len(text_message) > 20 else ""))) @@ -135,10 +139,10 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str) -> session = pkg.openai.session.get_session(session_name) try: prefix = "[GPT]" if hasattr(config, "show_prefix") and config.show_prefix else "" - reply = prefix + session.append(text_message) + reply = [prefix + session.append(text_message)] except openai.error.APIConnectionError as e: mgr.notify_admin("{}会话调用API失败:{}".format(session_name, e)) - reply = "[bot]err:调用API失败,请重试或联系作者,或等待修复" + reply = ["[bot]err:调用API失败,请重试或联系作者,或等待修复"] except openai.error.RateLimitError as e: # 尝试切换api-key current_tokens_amt = pkg.openai.manager.get_inst().key_mgr.get_usage( @@ -149,24 +153,25 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str) -> if not switched: mgr.notify_admin("API调用额度超限({}),请向OpenAI账户充值或在config.py中更换api_key".format( current_tokens_amt)) - reply = "[bot]err:API调用额度超额,请联系作者,或等待修复" + reply = ["[bot]err:API调用额度超额,请联系作者,或等待修复"] else: openai.api_key = pkg.openai.manager.get_inst().key_mgr.get_using_key() mgr.notify_admin("API调用额度超限({}),已切换到{}".format(current_tokens_amt, name)) - reply = "[bot]err:API调用额度超额,已自动切换,请重新发送消息" + reply = ["[bot]err:API调用额度超额,已自动切换,请重新发送消息"] except openai.error.InvalidRequestError as e: mgr.notify_admin("{}API调用参数错误:{}\n\n这可能是由于config.py中的prompt_submit_length参数或" "completion_api_params中的max_tokens参数数值过大导致的,请尝试将其降低".format( session_name, e)) - reply = "[bot]err:API调用参数错误,请联系作者,或等待修复" + reply = ["[bot]err:API调用参数错误,请联系作者,或等待修复"] except Exception as e: logging.exception(e) - reply = "[bot]err:{}".format(e) + reply = ["[bot]err:{}".format(e)] - logging.info( - "回复[{}]消息:{}".format(session_name, - reply[:min(100, len(reply))] + ("..." if len(reply) > 100 else ""))) - reply = mgr.reply_filter.process(reply) + if reply is not None and type(reply[0]) == str: + logging.info( + "回复[{}]文字消息:{}".format(session_name, + reply[0][:min(100, len(reply[0]))] + ("..." if len(reply[0]) > 100 else ""))) + reply = [mgr.reply_filter.process(reply[0])] finally: processing.remove(session_name) From 4de70abf47acf1b58b9f4d37e945c6f110399121 Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Mon, 26 Dec 2022 23:59:45 +0800 Subject: [PATCH 05/12] =?UTF-8?q?perf:=20The=20server=20is=20overloaded=20?= =?UTF-8?q?or=20not=20ready=20yet.=E6=97=B6=E8=BF=9B=E8=A1=8C=E4=B8=AD?= =?UTF-8?q?=E6=96=87=E6=8F=90=E7=A4=BA=20#59?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/qqbot/process.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/qqbot/process.py b/pkg/qqbot/process.py index a646dc99..886f4fd8 100644 --- a/pkg/qqbot/process.py +++ b/pkg/qqbot/process.py @@ -163,6 +163,9 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str) -> "completion_api_params中的max_tokens参数数值过大导致的,请尝试将其降低".format( session_name, e)) reply = ["[bot]err:API调用参数错误,请联系作者,或等待修复"] + except openai.error.ServiceUnavailableError as e: + # mgr.notify_admin("{}API调用服务不可用:{}".format(session_name, e)) + reply = ["[bot]err:API调用服务暂不可用,请尝试重试"] except Exception as e: logging.exception(e) reply = ["[bot]err:{}".format(e)] From bdfaecea1cc7c5c05e0b5573816a6dc0babe439a Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Tue, 27 Dec 2022 20:42:14 +0800 Subject: [PATCH 06/12] =?UTF-8?q?doc:=20=E4=BF=AE=E6=94=B9=E9=9C=80?= =?UTF-8?q?=E6=B1=82=E5=BB=BA=E8=AE=AEissue=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/需求建议.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/需求建议.md b/.github/ISSUE_TEMPLATE/需求建议.md index c16fb51a..20b91445 100644 --- a/.github/ISSUE_TEMPLATE/需求建议.md +++ b/.github/ISSUE_TEMPLATE/需求建议.md @@ -7,4 +7,4 @@ assignees: '' --- -想写啥都行 +不是需求建议请勿填写此模板!!!! From 74fb8ef15cf1e711b0aa020a3eb74066de36269e Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Tue, 27 Dec 2022 22:52:53 +0800 Subject: [PATCH 07/12] =?UTF-8?q?feat:=20=E5=88=9D=E6=AD=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E7=BB=98=E5=9B=BEapi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config-template.py | 6 ++++++ main.py | 2 +- pkg/openai/manager.py | 19 +++++++++++++++---- pkg/openai/session.py | 3 +++ pkg/qqbot/process.py | 13 +++++++++++++ 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/config-template.py b/config-template.py index 838a2b24..c62dc0e5 100644 --- a/config-template.py +++ b/config-template.py @@ -77,6 +77,12 @@ completion_api_params = { "presence_penalty": 1.0, } +# OpenAI的Image API的参数 +# 具体请查看OpenAI的文档: https://beta.openai.com/docs/api-reference/images/create +image_api_params = { + "size": "256x256", +} + # 消息处理的超时时间,单位为秒 process_message_timeout = 15 diff --git a/main.py b/main.py index 988626fc..7352942c 100644 --- a/main.py +++ b/main.py @@ -59,7 +59,7 @@ def main(): database.initialize_database() - openai_interact = pkg.openai.manager.OpenAIInteract(config.openai_config['api_key'], config.completion_api_params) + openai_interact = pkg.openai.manager.OpenAIInteract(config.openai_config['api_key']) # 加载所有未超时的session pkg.openai.session.load_sessions() diff --git a/pkg/openai/manager.py b/pkg/openai/manager.py index e753953f..e9279cfd 100644 --- a/pkg/openai/manager.py +++ b/pkg/openai/manager.py @@ -15,9 +15,12 @@ class OpenAIInteract: key_mgr = None - def __init__(self, api_key: str, api_params: dict): + default_image_api_params = { + "size": "256x256", + } + + def __init__(self, api_key: str): # self.api_key = api_key - self.api_params = api_params self.key_mgr = pkg.openai.keymgr.KeysManager(api_key) @@ -28,12 +31,11 @@ class OpenAIInteract: # 请求OpenAI Completion def request_completion(self, prompt, stop): - logging.debug("请求OpenAI Completion, key:"+openai.api_key) response = openai.Completion.create( prompt=prompt, stop=stop, timeout=config.process_message_timeout, - **self.api_params + **config.completion_api_params ) switched = self.key_mgr.report_usage(prompt + response['choices'][0]['text']) if switched: @@ -41,6 +43,15 @@ class OpenAIInteract: return response + def request_image(self, prompt): + response = openai.Image.create( + prompt=prompt, + n=1, + **config.image_api_params if hasattr(config, "image_api_params") else self.default_image_api_params + ) + + return response + def get_inst() -> OpenAIInteract: global inst diff --git a/pkg/openai/session.py b/pkg/openai/session.py index 2d28f38a..9bc9e9e1 100644 --- a/pkg/openai/session.py +++ b/pkg/openai/session.py @@ -268,3 +268,6 @@ class Session: def list_history(self, capacity: int = 10, page: int = 0): return pkg.database.manager.get_inst().list_history(self.name, capacity, page, get_default_prompt()) + + def draw_image(self, prompt: str): + return pkg.openai.manager.get_inst().request_image(prompt) diff --git a/pkg/qqbot/process.py b/pkg/qqbot/process.py index 886f4fd8..7b09b850 100644 --- a/pkg/qqbot/process.py +++ b/pkg/qqbot/process.py @@ -6,6 +6,8 @@ from func_timeout import func_set_timeout import logging import openai +from mirai import Image + import config import pkg.openai.session @@ -128,6 +130,17 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str) -> reply_str += "\n当前使用:{}".format(using_key_name) reply = [reply_str] + + elif cmd == 'draw': + if len(params) == 0: + reply = ["[bot]err:请输入图片描述文字"] + else: + session = pkg.openai.session.get_session(session_name) + + res = session.draw_image(" ".join(params)) + + logging.debug("draw_image result:{}".format(res)) + reply = [Image(url=res['data'][0]['url'])] except Exception as e: mgr.notify_admin("{}指令执行失败:{}".format(session_name, e)) logging.exception(e) From 7b5d47a2ca2c5169dabdb0ed58ec8b375d442058 Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Tue, 27 Dec 2022 23:01:04 +0800 Subject: [PATCH 08/12] =?UTF-8?q?perf:=20=E5=9B=9E=E5=A4=8D=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E6=97=B6=E6=8E=A7=E5=88=B6=E5=8F=B0=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/qqbot/process.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/qqbot/process.py b/pkg/qqbot/process.py index 7b09b850..5e9a5821 100644 --- a/pkg/qqbot/process.py +++ b/pkg/qqbot/process.py @@ -188,6 +188,8 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str) -> "回复[{}]文字消息:{}".format(session_name, reply[0][:min(100, len(reply[0]))] + ("..." if len(reply[0]) > 100 else ""))) reply = [mgr.reply_filter.process(reply[0])] + else: + logging.info("回复[{}]图片消息:{}".format(session_name, reply)) finally: processing.remove(session_name) From 7ed558056fe8e5571c3e71a9e1c918422096157a Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Wed, 28 Dec 2022 00:05:25 +0800 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=E8=B4=B9?= =?UTF-8?q?=E7=94=A8=E4=BC=B0=E7=AE=97=E6=9B=BF=E4=BB=A3=E5=AD=97=E6=95=B0?= =?UTF-8?q?=E9=A2=9D=E5=BA=A6=E4=BC=B0=E7=AE=97=20#47?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config-template.py | 8 +-- main.py | 2 +- pkg/database/manager.py | 48 ++++++++++++++++++ pkg/openai/keymgr.py | 110 ++++++++++++++++++++++++++++------------ pkg/openai/manager.py | 16 +++++- pkg/openai/pricing.py | 21 ++++++++ pkg/qqbot/process.py | 12 ++--- 7 files changed, 171 insertions(+), 46 deletions(-) create mode 100644 pkg/openai/pricing.py diff --git a/config-template.py b/config-template.py index c62dc0e5..5fc5e283 100644 --- a/config-template.py +++ b/config-template.py @@ -52,10 +52,10 @@ response_rules = { "regexp": [] # "为什么.*", "怎么?样.*", "怎么.*", "如何.*", "[Hh]ow to.*", "[Ww]hy not.*", "[Ww]hat is.*", ".*怎么办", ".*咋办" } -# 单个api-key的使用量警告阈值 -# 当使用此api-key进行请求的文字量达到此阈值时,会在控制台输出警告并通知管理员 +# 单个api-key的费用警告阈值 +# 当使用此api-key进行请求所消耗的费用估算达到此阈值时,会在控制台输出警告并通知管理员 # 若之后还有未使用超过此值的api-key,则会切换到新的api-key进行请求 -api_key_usage_threshold = 900000 +api_key_fee_threshold = 18.0 # 敏感词过滤开关,以同样数量的*代替敏感词回复 # 请在sensitive.json中添加敏感词 @@ -80,7 +80,7 @@ completion_api_params = { # OpenAI的Image API的参数 # 具体请查看OpenAI的文档: https://beta.openai.com/docs/api-reference/images/create image_api_params = { - "size": "256x256", + "size": "256x256", # 图片尺寸,支持256x256, 512x512, 1024x1024 } # 消息处理的超时时间,单位为秒 diff --git a/main.py b/main.py index 7352942c..8c19f5df 100644 --- a/main.py +++ b/main.py @@ -78,7 +78,7 @@ def main(): time.sleep(86400) except KeyboardInterrupt: try: - pkg.openai.manager.get_inst().key_mgr.dump_usage() + pkg.openai.manager.get_inst().key_mgr.dump_fee() for session in pkg.openai.session.sessions: logging.info('持久化session: %s', session) pkg.openai.session.sessions[session].persistence() diff --git a/pkg/database/manager.py b/pkg/database/manager.py index ecd38002..3ede5f57 100644 --- a/pkg/database/manager.py +++ b/pkg/database/manager.py @@ -58,6 +58,15 @@ class DatabaseManager: `usage` bigint not null ) """) + + self.execute(""" + create table if not exists `account_fee`( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `key_md5` varchar(255) not null, + `timestamp` bigint not null, + `fee` DECIMAL(9,3) not null + ) + """) print('Database initialized.') # session持久化 @@ -264,6 +273,45 @@ class DatabaseManager: usage[key_md5] = usage_count return usage + def dump_api_key_fee(self, api_keys: dict, fee: dict): + logging.debug("dumping api key fee...") + logging.debug(api_keys) + logging.debug(fee) + for api_key in api_keys: + # 计算key的md5值 + key_md5 = hashlib.md5(api_keys[api_key].encode('utf-8')).hexdigest() + # 获取使用量 + fee_count = 0 + if key_md5 in fee: + fee_count = fee[key_md5] + # 将使用量存进数据库 + # 先检查是否已存在 + self.execute(""" + select count(*) from `account_fee` where `key_md5` = '{}'""".format(key_md5)) + result = self.cursor.fetchone() + if result[0] == 0: + # 不存在则插入 + self.execute(""" + insert into `account_fee` (`key_md5`, `fee`,`timestamp`) values ('{}', {}, {}) + """.format(key_md5, fee_count, int(time.time()))) + else: + # 存在则更新,timestamp设置为当前 + self.execute(""" + update `account_fee` set `fee` = {}, `timestamp` = {} where `key_md5` = '{}' + """.format(fee_count, int(time.time()), key_md5)) + + def load_api_key_fee(self): + self.execute(""" + select `key_md5`, `fee` from `account_fee` + """) + results = self.cursor.fetchall() + fee = {} + for result in results: + key_md5 = result[0] + fee_count = result[1] + fee[key_md5] = fee_count + return fee + def get_inst() -> DatabaseManager: global inst return inst diff --git a/pkg/openai/keymgr.py b/pkg/openai/keymgr.py index 75d7b196..728cea3e 100644 --- a/pkg/openai/keymgr.py +++ b/pkg/openai/keymgr.py @@ -14,7 +14,11 @@ class KeysManager: # 其中键为api-key的md5值,值为使用量 usage = {} - api_key_usage_threshold = 900000 + fee = {} + + api_key_usage_threshold = 900000 # 已弃用 + + api_key_fee_threshold = 18.0 using_key = "" @@ -24,9 +28,11 @@ class KeysManager: return self.using_key def __init__(self, api_key): - if hasattr(config, 'api_key_usage_threshold'): - self.api_key_usage_threshold = config.api_key_usage_threshold - self.load_usage() + # if hasattr(config, 'api_key_usage_threshold'): + # self.api_key_usage_threshold = config.api_key_usage_threshold + if hasattr(config, 'api_key_fee_threshold'): + self.api_key_fee_threshold = config.api_key_fee_threshold + self.load_fee() if type(api_key) is dict: self.api_key = api_key @@ -45,9 +51,9 @@ class KeysManager: # 根据使用量自动切换到可用的api-key # 返回是否切换成功, 切换后的api-key的别名 def auto_switch(self) -> (bool, str): - self.dump_usage() + self.dump_fee() for key_name in self.api_key: - if self.get_usage(self.api_key[key_name]) < self.api_key_usage_threshold: + if self.get_fee(self.api_key[key_name]) < self.api_key_fee_threshold: self.using_key = self.api_key[key_name] logging.info("使用api-key:" + key_name) return True, key_name @@ -57,30 +63,76 @@ class KeysManager: return False, "" - def get_usage(self, api_key): - md5 = hashlib.md5(api_key.encode('utf-8')).hexdigest() - if md5 not in self.usage: - self.usage[md5] = 0 - return self.usage[md5] - def add(self, key_name, key): self.api_key[key_name] = key + # def get_usage(self, api_key): + # md5 = hashlib.md5(api_key.encode('utf-8')).hexdigest() + # if md5 not in self.usage: + # self.usage[md5] = 0 + # return self.usage[md5] + # 报告使用 # 返回是否需要将openai的api-key切换 - def report_usage(self, new_content: str) -> bool: + # def report_usage(self, new_content: str) -> bool: + # md5 = hashlib.md5(self.using_key.encode('utf-8')).hexdigest() + # if md5 not in self.usage: + # self.usage[md5] = 0 + # + # # 经测算得出的理论与实际的偏差比例 + # salt_rate = 0.91 + # + # self.usage[md5] += ( (len(new_content.encode('utf-8')) - len(new_content)) / 2 + len(new_content) )*salt_rate + # + # self.usage[md5] = int(self.usage[md5]) + # + # if self.usage[md5] >= self.api_key_usage_threshold: + # switch_result, key_name = self.auto_switch() + # + # # 检查是否切换到新的 + # if switch_result: + # if key_name not in self.alerted: + # # 通知管理员 + # pkg.qqbot.manager.get_inst().notify_admin("api-key已切换到:" + key_name) + # self.alerted.append(key_name) + # return True + # else: + # if key_name not in self.alerted: + # # 通知管理员 + # pkg.qqbot.manager.get_inst().notify_admin("api-key已用完,无未使用的api-key可供切换") + # self.alerted.append(key_name) + # return False + + # 设置当前使用的api-key使用量超限 + # 这是在尝试调用api时发生超限异常时调用的 + def set_current_exceeded(self): md5 = hashlib.md5(self.using_key.encode('utf-8')).hexdigest() - if md5 not in self.usage: - self.usage[md5] = 0 + # self.usage[md5] = self.api_key_usage_threshold + self.fee[md5] = self.api_key_fee_threshold + self.dump_fee() - # 经测算得出的理论与实际的偏差比例 - salt_rate = 0.91 + # def dump_usage(self): + # pkg.database.manager.get_inst().dump_api_key_usage(api_keys=self.api_key, usage=self.usage) - self.usage[md5] += ( (len(new_content.encode('utf-8')) - len(new_content)) / 2 + len(new_content) )*salt_rate + # def load_usage(self): + # self.usage = pkg.database.manager.get_inst().load_api_key_usage() + # logging.debug("load usage:" + str(self.usage)) + # print("load usage:" + str(self.usage)) - self.usage[md5] = int(self.usage[md5]) + def get_fee(self, api_key): + md5 = hashlib.md5(api_key.encode('utf-8')).hexdigest() + if md5 not in self.fee: + self.fee[md5] = 0 + return self.fee[md5] - if self.usage[md5] >= self.api_key_usage_threshold: + def report_fee(self, fee: float) -> bool: + md5 = hashlib.md5(self.using_key.encode('utf-8')).hexdigest() + if md5 not in self.fee: + self.fee[md5] = 0 + + self.fee[md5] += fee + + if self.fee[md5] >= self.api_key_fee_threshold: switch_result, key_name = self.auto_switch() # 检查是否切换到新的 @@ -97,17 +149,9 @@ class KeysManager: self.alerted.append(key_name) return False - # 设置当前使用的api-key使用量超限 - # 这是在尝试调用api时发生超限异常时调用的 - def set_current_exceeded(self): - md5 = hashlib.md5(self.using_key.encode('utf-8')).hexdigest() - self.usage[md5] = self.api_key_usage_threshold - self.dump_usage() + def dump_fee(self): + pkg.database.manager.get_inst().dump_api_key_fee(api_keys=self.api_key, fee=self.fee) - def dump_usage(self): - pkg.database.manager.get_inst().dump_api_key_usage(api_keys=self.api_key, usage=self.usage) - - def load_usage(self): - self.usage = pkg.database.manager.get_inst().load_api_key_usage() - logging.debug("load usage:" + str(self.usage)) - print("load usage:" + str(self.usage)) + def load_fee(self): + self.fee = pkg.database.manager.get_inst().load_api_key_fee() + logging.info("load fee:" + str(self.fee)) \ No newline at end of file diff --git a/pkg/openai/manager.py b/pkg/openai/manager.py index e9279cfd..d65f0e54 100644 --- a/pkg/openai/manager.py +++ b/pkg/openai/manager.py @@ -5,6 +5,7 @@ import openai import config import pkg.openai.keymgr +import pkg.openai.pricing as pricing inst = None @@ -37,19 +38,30 @@ class OpenAIInteract: timeout=config.process_message_timeout, **config.completion_api_params ) - switched = self.key_mgr.report_usage(prompt + response['choices'][0]['text']) + + switched = self.key_mgr.report_fee(pricing.language_base_price(config.completion_api_params['model'], + prompt + response['choices'][0]['text'])) + if switched: openai.api_key = self.key_mgr.get_using_key() return response def request_image(self, prompt): + + params = config.image_api_params if hasattr(config, "image_api_params") else self.default_image_api_params + response = openai.Image.create( prompt=prompt, n=1, - **config.image_api_params if hasattr(config, "image_api_params") else self.default_image_api_params + **params ) + switched = self.key_mgr.report_fee(pricing.image_price(params['size'])) + + if switched: + openai.api_key = self.key_mgr.get_using_key() + return response diff --git a/pkg/openai/pricing.py b/pkg/openai/pricing.py new file mode 100644 index 00000000..1040a04c --- /dev/null +++ b/pkg/openai/pricing.py @@ -0,0 +1,21 @@ +pricing = { + "base": { # 文字模型单位是1000字符 + "text-davinci-003": 0.02, + }, + "image": { + "256x256": 0.016, + "512x512": 0.018, + "1024x1024": 0.02, + } +} + + +def language_base_price(model, text): + salt_rate = 0.93 + length = ((len(text.encode('utf-8')) - len(text)) / 2 + len(text)) * salt_rate + + return pricing["base"][model] * length / 1000 + + +def image_price(size): + return pricing["image"][size] diff --git a/pkg/qqbot/process.py b/pkg/qqbot/process.py index 5e9a5821..eadde547 100644 --- a/pkg/qqbot/process.py +++ b/pkg/qqbot/process.py @@ -113,17 +113,17 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str) -> elif cmd == 'usage': api_keys = pkg.openai.manager.get_inst().key_mgr.api_key reply_str = "[bot]api-key使用情况:(阈值:{})\n\n".format( - pkg.openai.manager.get_inst().key_mgr.api_key_usage_threshold) + pkg.openai.manager.get_inst().key_mgr.api_key_fee_threshold) using_key_name = "" for api_key in api_keys: - reply_str += "{}:\n - {}字 {}%\n".format(api_key, - pkg.openai.manager.get_inst().key_mgr.get_usage( + reply_str += "{}:\n - {}元 {}%\n".format(api_key, + pkg.openai.manager.get_inst().key_mgr.get_fee( api_keys[api_key]), round( - pkg.openai.manager.get_inst().key_mgr.get_usage( + pkg.openai.manager.get_inst().key_mgr.get_fee( api_keys[ - api_key]) / pkg.openai.manager.get_inst().key_mgr.api_key_usage_threshold * 100, + api_key]) / pkg.openai.manager.get_inst().key_mgr.api_key_fee_threshold * 100, 3)) if api_keys[api_key] == pkg.openai.manager.get_inst().key_mgr.using_key: using_key_name = api_key @@ -158,7 +158,7 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str) -> reply = ["[bot]err:调用API失败,请重试或联系作者,或等待修复"] except openai.error.RateLimitError as e: # 尝试切换api-key - current_tokens_amt = pkg.openai.manager.get_inst().key_mgr.get_usage( + current_tokens_amt = pkg.openai.manager.get_inst().key_mgr.get_fee( pkg.openai.manager.get_inst().key_mgr.get_using_key()) pkg.openai.manager.get_inst().key_mgr.set_current_exceeded() switched, name = pkg.openai.manager.get_inst().key_mgr.auto_switch() From 0b7d9a4a46d0ac01526a214c8d5e06019aeca1e9 Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Wed, 28 Dec 2022 00:06:41 +0800 Subject: [PATCH 10/12] =?UTF-8?q?perf:=20=E5=88=A0=E9=99=A4=E8=BF=87?= =?UTF-8?q?=E6=97=B6=E7=9A=84usage=E7=9B=B8=E5=85=B3=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/openai/keymgr.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/openai/keymgr.py b/pkg/openai/keymgr.py index 728cea3e..a0fea80d 100644 --- a/pkg/openai/keymgr.py +++ b/pkg/openai/keymgr.py @@ -12,12 +12,8 @@ class KeysManager: # api-key的使用量 # 其中键为api-key的md5值,值为使用量 - usage = {} - fee = {} - api_key_usage_threshold = 900000 # 已弃用 - api_key_fee_threshold = 18.0 using_key = "" From dbcacdefc3c2329f7a3ab49712f6a81b300aa04b Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Wed, 28 Dec 2022 00:11:25 +0800 Subject: [PATCH 11/12] =?UTF-8?q?perf:=20=E6=9B=B4=E5=8A=A0=E7=B2=BE?= =?UTF-8?q?=E7=BB=86=E7=9A=84=E8=B4=B9=E7=94=A8=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/database/manager.py | 2 +- pkg/qqbot/process.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/database/manager.py b/pkg/database/manager.py index 3ede5f57..cbe243e9 100644 --- a/pkg/database/manager.py +++ b/pkg/database/manager.py @@ -64,7 +64,7 @@ class DatabaseManager: `id` INTEGER PRIMARY KEY AUTOINCREMENT, `key_md5` varchar(255) not null, `timestamp` bigint not null, - `fee` DECIMAL(9,3) not null + `fee` DECIMAL(12,6) not null ) """) print('Database initialized.') diff --git a/pkg/qqbot/process.py b/pkg/qqbot/process.py index eadde547..025ac556 100644 --- a/pkg/qqbot/process.py +++ b/pkg/qqbot/process.py @@ -118,8 +118,8 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str) -> using_key_name = "" for api_key in api_keys: reply_str += "{}:\n - {}元 {}%\n".format(api_key, - pkg.openai.manager.get_inst().key_mgr.get_fee( - api_keys[api_key]), + round(pkg.openai.manager.get_inst().key_mgr.get_fee( + api_keys[api_key]), 6), round( pkg.openai.manager.get_inst().key_mgr.get_fee( api_keys[ From b2ee62646c29ceee4cd2c52a805e82acfec5633c Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Wed, 28 Dec 2022 00:14:41 +0800 Subject: [PATCH 12/12] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E6=88=90?= =?UTF-8?q?=E7=BE=8E=E5=85=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config-template.py | 1 + pkg/qqbot/process.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config-template.py b/config-template.py index 5fc5e283..adc92ca5 100644 --- a/config-template.py +++ b/config-template.py @@ -55,6 +55,7 @@ response_rules = { # 单个api-key的费用警告阈值 # 当使用此api-key进行请求所消耗的费用估算达到此阈值时,会在控制台输出警告并通知管理员 # 若之后还有未使用超过此值的api-key,则会切换到新的api-key进行请求 +# 单位:美元 api_key_fee_threshold = 18.0 # 敏感词过滤开关,以同样数量的*代替敏感词回复 diff --git a/pkg/qqbot/process.py b/pkg/qqbot/process.py index 025ac556..30351514 100644 --- a/pkg/qqbot/process.py +++ b/pkg/qqbot/process.py @@ -117,7 +117,7 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str) -> using_key_name = "" for api_key in api_keys: - reply_str += "{}:\n - {}元 {}%\n".format(api_key, + reply_str += "{}:\n - {}美元 {}%\n".format(api_key, round(pkg.openai.manager.get_inst().key_mgr.get_fee( api_keys[api_key]), 6), round(