Compare commits

..

34 Commits

Author SHA1 Message Date
Soulter 6a56b7bff5 Update README.md 2023-08-31 18:35:29 +08:00
Soulter 81e8997852 feat: 1. 支持llm网页搜索,实时消息。
2. 加入频道兼容层;支持频道发图
perf: 1. 稳定性优化
2. 精简部分代码结构
2023-08-31 18:34:20 +08:00
Soulter 372a204ba9 feat: QQ频道平台支持myid指令 2023-08-27 19:25:39 +08:00
Soulter 15ad5aae35 Update README.md 2023-08-20 17:44:39 +08:00
Soulter fd2e9ef93f Update README.md 2023-08-20 14:48:40 +08:00
Soulter 5be3bf1f46 feat: 网页版ChatGPT模型支持Plus账户、网页搜索、插件 2023-08-20 14:26:13 +08:00
Soulter 4915c2d480 bugfixes 2023-08-20 14:04:50 +08:00
Soulter bd56a19ac5 bugfixes 2023-08-20 14:03:44 +08:00
Soulter da8fa2d905 bugfixes 2023-08-20 14:00:46 +08:00
Soulter f56fd100d7 bugfixes 2023-08-20 14:00:25 +08:00
Soulter b725a1a20c Merge branch 'master' of https://github.com/Soulter/QQChannelChatGPT 2023-08-20 13:56:53 +08:00
Soulter ff1b5d02d2 perf: 优化初次启动后报错时的处理 2023-08-20 13:56:50 +08:00
Soulter d4882a8240 Update README.md 2023-08-15 21:18:47 +08:00
Soulter e37f84c1ae Update README.md 2023-08-15 21:18:08 +08:00
Soulter a23bd0a63c Update README.md 2023-08-15 16:21:34 +08:00
Soulter ae00e84974 Update README.md 2023-08-15 15:48:24 +08:00
Soulter 53b3250978 Update README.md 2023-08-15 15:42:54 +08:00
Soulter 7f15a59a4e Update README.md 2023-08-15 15:39:16 +08:00
Soulter 6a164c9961 Update README.md 2023-08-15 15:35:23 +08:00
Soulter bd779a3df3 Merge branch 'master' of https://github.com/Soulter/QQChannelChatGPT 2023-08-15 13:43:16 +08:00
Soulter 9ebb340c00 perf: 优化更新插件的相关逻辑;优化日志输出 2023-08-15 13:42:12 +08:00
Soulter e8edbaae2d Update README.md 2023-08-12 12:47:41 +08:00
Soulter 2aab1f4c96 Update requirements.txt 2023-08-11 23:43:38 +08:00
Soulter 90ea621c65 Update main.py 2023-08-11 23:36:38 +08:00
Soulter 34bdceb41b Update README.md 2023-08-11 02:38:44 +08:00
Soulter 6d2ded1c6c Update README.md 2023-08-11 02:37:03 +08:00
Soulter 9b926048ca Update README.md 2023-08-11 02:35:43 +08:00
Soulter 9cf4f0f57d Merge branch 'master' of https://github.com/Soulter/QQChannelChatGPT 2023-08-06 11:04:30 +08:00
Soulter 9123b9d773 fix: 修复windows启动下会弹出markdown测试窗口的问题 2023-08-06 11:04:25 +08:00
Soulter f9258ae1e1 fix: 修复生成图片时报错的问题 2023-08-06 11:02:09 +08:00
Soulter d8808de4a9 Update README.md 2023-08-03 22:30:54 +08:00
Soulter afcb152d8d Update requirements.txt 2023-07-21 21:43:11 +08:00
Soulter ff01174a1f 删除GUI界面下启动项目出现的二维码 2023-06-26 20:34:10 +08:00
Soulter 71f1625284 Update README.md 2023-06-18 13:30:08 +08:00
14 changed files with 690 additions and 184 deletions
+40 -18
View File
@@ -6,32 +6,54 @@
[![License](https://img.shields.io/badge/license-AGPL3-orange.svg?style=plastic)](https://github.com/Soulter/QQChannelChatGPT/blob/master/LICENSE)
![Python](https://img.shields.io/badge/python-3.9+-blue) -->
基于go-cq和官方QQ频道SDK的机器人项目。支持ChatGPT、NewBing等大模型。一次部署,同时使用
基于go-cqhttp和官方QQ频道SDK的QQ机器人项目。支持ChatGPT、Claude、HuggingChat、Bard大模型。一次部署,同时使用
部署文档:https://github.com/Soulter/QQChannelChatGPT/wiki
插件文档:https://github.com/Soulter/QQChannelChatGPT/wiki/%E5%9B%9B%E3%80%81%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6
欢迎体验 | **QQ群号:322154837**
欢迎加群讨论 | **QQ群号:322154837** | **频道号: x42d56aki2** |
<!-- <img src="https://user-images.githubusercontent.com/37870767/230417115-9dd3c9d5-6b6b-4928-8fe3-82f559208aab.JPG" width="300"></img> -->
</div>
## 🤔您可能想了解的
- **如何部署?** [帮助文档](https://github.com/Soulter/QQChannelChatGPT/wiki)
- **go-cqhttp启动不成功、报登录失败?** [在这里搜索解决方法](https://github.com/Mrs4s/go-cqhttp/issues)
- **程序闪退/机器人启动不成功?** [提交issue或加群反馈](https://github.com/Soulter/QQChannelChatGPT/issues)
- **如何开启ChatGPT、Bard、Claude等语言模型?** [查看帮助](https://github.com/Soulter/QQChannelChatGPT/wiki/%E8%A1%A5%E5%85%85%EF%BC%9A%E5%A6%82%E4%BD%95%E5%BC%80%E5%90%AFChatGPT%E3%80%81Bard%E3%80%81Claude%E7%AD%89%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%EF%BC%9F)
## 🧩功能:
近期新功能
- Markdown渲染支持:回复消息支持图片。
- 支持插件。https://github.com/Soulter/QQChannelChatGPT/wiki/%E5%9B%9B%E3%80%81%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6
- 热更新
- 接入QQ,支持在QQ上和QQ频道上同时聊天!https://github.com/Soulter/QQChannelChatGPT/issues/82
- Windows启动器。链接:https://github.com/Soulter/QQChatGPTLauncher/releases/latest
🌍支持的AI语言模型一览
**文字模型**
- OpenAI GPT-3模型(原生支持)
- OpenAI GPT-3.5模型(原生支持)
- OpenAI GPT-4模型(原生支持)
- ChatGPT网页版 GPT-3.5模型(免费,原生支持)
- ChatGPT网页版 GPT-4模型(需订阅Plus账户,原生支持)
- Bing(免费,原生支持)
- Claude模型(免费,由[LLMs插件](https://github.com/Soulter/llms)支持)
- HuggingChat模型(免费,由[LLMs插件](https://github.com/Soulter/llms)支持)
- Google Bard(免费,由[LLMs插件](https://github.com/Soulter/llms)支持)
**图片生成**
- NovelAI/Naifu (免费,由[AIDraw插件](https://github.com/Soulter/aidraw)支持)
🌍机器人支持的能力一览:
- 同时部署机器人到QQ和QQ频道
- 大模型对话
- 大模型网页搜索能力 **(目前仅支持OpenAI系的模型,最新版本下使用web on指令打开)**
- 插件安装(在QQ或QQ频道聊天框内输入`plugin`了解详情)
- 回复文字图片渲染(以图片markdown格式回复,降低被风控概率,需手动在`cmd_config.json`内开启)
- 人格设置
- 关键词回复
- 热更新(更新本项目时**仅需**在QQ或QQ频道聊天框内输入`update latest r`
- Windows一键部署(https://github.com/Soulter/QQChatGPTLauncher/releases/latest
支持的AI语言模型(请在`configs/config.yaml`下配置):
- 逆向ChatGPT
- 官方ChatGPT API
- Bing
- ...
<!--
### 基本功能
<details>
@@ -99,10 +121,10 @@
插件开发教程:https://github.com/Soulter/QQChannelChatGPT/wiki/%E5%9B%9B%E3%80%81%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6
部分好用的插件:
- `HuggingChat`: https://github.com/Soulter/HuggingChatForQQBot | HuggingChat模型接入
部分公开的插件:
- `LLMS`: https://github.com/Soulter/llms | Claude, HuggingChat 大语言模型接入。
- `GoodPlugins`: https://github.com/Soulter/goodplugins | 随机动漫图片、搜番、喜报生成器等等
- `sysstat`: https://github.com/Soulter/sysstatqcbot | 查看系统状态
+90 -57
View File
@@ -21,13 +21,16 @@ from nakuru import (
FriendMessage,
GuildMessage
)
from nakuru.entities.components import Plain,At
from nakuru.entities.components import Plain,At,Image
from model.command.command import Command
from model.command.command_rev_chatgpt import CommandRevChatGPT
from model.command.command_rev_edgegpt import CommandRevEdgeGPT
from model.command.command_openai_official import CommandOpenAIOfficial
from util import general_utils as gu
from util.cmd_config import CmdConfig as cc
import util.gplugin as gplugin
from PIL import Image as PILImage
import io
@@ -114,6 +117,9 @@ bing_cache_loop = None
# 插件
cached_plugins = {}
# 全局对象
_global_object = {}
# 统计
cnt_total = 0
cnt_valid = 0
@@ -123,6 +129,10 @@ cc.init_attributes(["qq_forward_threshold"], 200)
cc.init_attributes(["qq_welcome"], "欢迎加入本群!\n欢迎给https://github.com/Soulter/QQChannelChatGPT项目一个Star😊~\n输入help查看帮助~\n")
cc.init_attributes(["bing_proxy"], "")
cc.init_attributes(["qq_pic_mode"], False)
cc.init_attributes(["rev_chatgpt_model"], "")
cc.init_attributes(["rev_chatgpt_plugin_ids"], [])
cc.init_attributes(["rev_chatgpt_PUID"], "")
cc.init_attributes(["rev_chatgpt_unverified_plugin_domains"], [])
# cc.init_attributes(["qq_forward_mode"], False)
def new_sub_thread(func, args=()):
@@ -161,7 +171,6 @@ def upload():
addr = requests.get('http://myip.ipip.net', timeout=5).text
addr_ip = re.findall(r'\d+.\d+.\d+.\d+', addr)[0]
except BaseException as e:
print(e)
pass
try:
o = {"cnt_total": cnt_total,"admin": admin_qq,"addr": addr,}
@@ -175,7 +184,6 @@ def upload():
cnt_valid = 0
cnt_total = 0
except BaseException as e:
print(e)
pass
time.sleep(60*10)
@@ -184,8 +192,8 @@ def upload():
'''
def initBot(cfg, prov):
global chatgpt, provider, rev_chatgpt, baidu_judge, rev_edgegpt, chosen_provider
global reply_prefix, gpt_config, config, uniqueSession, frequency_count, frequency_time,announcement, direct_message_mode, version
global command_openai_official, command_rev_chatgpt, command_rev_edgegpt,reply_prefix, keywords, cached_plugins
global reply_prefix, gpt_config, config, uniqueSession, frequency_count, frequency_time, announcement, direct_message_mode, version
global command_openai_official, command_rev_chatgpt, command_rev_edgegpt,reply_prefix, keywords, cached_plugins, _global_object
provider = prov
config = cfg
if 'reply_prefix' in cfg:
@@ -224,9 +232,9 @@ def initBot(cfg, prov):
chatgpt = ProviderOpenAIOfficial(cfg['openai'])
chosen_provider = OPENAI_OFFICIAL
command_rev_edgegpt = CommandRevEdgeGPT(rev_edgegpt)
command_rev_chatgpt = CommandRevChatGPT(rev_chatgpt)
command_openai_official = CommandOpenAIOfficial(chatgpt)
command_rev_edgegpt = CommandRevEdgeGPT(rev_edgegpt, _global_object)
command_rev_chatgpt = CommandRevChatGPT(rev_chatgpt, _global_object)
command_openai_official = CommandOpenAIOfficial(chatgpt, _global_object)
gu.log("--------加载个性化配置--------", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow'])
# 得到关键词
@@ -266,8 +274,11 @@ def initBot(cfg, prov):
# 得到公告配置
if 'notice' in cfg:
gu.log("公告配置: "+cfg['notice'], gu.LEVEL_INFO)
announcement += cfg['notice']
if cc.get("qq_welcome", None) != None and cfg['notice'] == '此机器人由Github项目QQChannelChatGPT驱动。':
announcement = cc.get("qq_welcome", None)
else:
announcement = cfg['notice']
gu.log("公告配置: " + announcement, gu.LEVEL_INFO)
try:
if 'uniqueSessionMode' in cfg and cfg['uniqueSessionMode']:
uniqueSession = True
@@ -363,7 +374,12 @@ def run_qqchan_bot(cfg, loop, qqchannel_bot):
qqchannel_bot.run_bot(client, cfg['qqbot']['appid'], cfg['qqbot']['token'])
except BaseException as e:
gu.log("启动QQ频道机器人时出现错误, 原因如下: " + str(e), gu.LEVEL_CRITICAL, tag="QQ频道")
gu.log(r"【提醒】有可能你想启动的是gocq, 并不是这个旧版的QQ频道SDK, 如果是这样, 请修改配置文件(QQChannelChatGPT/config.yaml)详情请看:https://github.com/Soulter/QQChannelChatGPT/wiki/%E4%BA%8C%E3%80%81%E9%A1%B9%E7%9B%AE%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E9%85%8D%E7%BD%AE" + str(e), gu.LEVEL_CRITICAL, tag="QQ频道")
gu.log(r"【提醒】如果您是初次启动,请修改配置文件(QQChannelChatGPT/config.yaml)详情请看:https://github.com/Soulter/QQChannelChatGPT/wiki。" + str(e), gu.LEVEL_CRITICAL, tag="System")
i = input("输入y打开配置文件, 按回车退出程序。")
if i == "y":
abs_path = os.path.abspath("QQChannelChatGPT/configs/config.yaml")
os.system(f"notepad \"{abs_path}\"")
# gu.log("如果你使用了go-cqhttp, 则可以忽略上面的报错。" + str(e), gu.LEVEL_CRITICAL, tag="QQ频道")
# input(f"\n[System-Error] 启动QQ频道机器人时出现错误,原因如下:{e}\n可能是没有填写QQBOT appid和token?请在config中完善你的appid和token\n配置教程:https://soulter.top/posts/qpdg.html\n")
@@ -378,7 +394,6 @@ def run_gocq_bot(loop, gocq_bot, gocq_app):
gu.log("检查完毕,未发现问题。", tag="QQ")
break
global gocq_client
gocq_client = gocqClient()
try:
@@ -415,22 +430,15 @@ def save_provider_preference(chosen_provider):
'''
通用回复方法
'''
def send_message(platform, message, res, msg_ref = None, image = None, gocq_loop = None, qqchannel_bot = None, gocq_bot = None, image_mode=False):
def send_message(platform, message, res, msg_ref = None, image = None, image_mode=False):
# imagemode:
# For GOCQ: when image_mode is true, ALL plain texts in res will change into a new pic
global cnt_valid
global cnt_valid, qqchannel_bot, qqchannel_bot, gocq_loop
cnt_valid += 1
if platform == PLATFORM_QQCHAN:
if image != None:
qqchannel_bot.send_qq_msg(message, str(res), image_mode=True, msg_ref=msg_ref)
else:
qqchannel_bot.send_qq_msg(message, str(res), msg_ref=msg_ref)
if platform == PLATFORM_GOCQ:
if image != None:
# image is a url string
asyncio.run_coroutine_threadsafe(gocq_bot.send_qq_msg(message, [Plain(text="好的,我根据你的需要为你生成了一张图片😊"),Image.fromURL(image)], False), gocq_loop).result()
else:
asyncio.run_coroutine_threadsafe(gocq_bot.send_qq_msg(message, res, image_mode), gocq_loop).result()
qqchannel_bot.send_qq_msg(message, res, msg_ref=msg_ref)
if platform == PLATFORM_GOCQ:
asyncio.run_coroutine_threadsafe(gocq_bot.send_qq_msg(message, res, image_mode), gocq_loop).result()
def oper_msg(message,
@@ -452,7 +460,7 @@ def oper_msg(message,
hit = False # 是否命中指令
command_result = () # 调用指令返回的结果
global admin_qq, admin_qqchan, cached_plugins, gocq_bot, nick_qq
global cnt_total
global cnt_total, _global_object
cnt_total += 1
@@ -464,7 +472,7 @@ def oper_msg(message,
if isinstance(nick_qq, str):
nick_qq = (nick_qq,)
if isinstance(nick_qq, list):
nick_qq = tuple(nick_qq)
nick_qq = tuple(nick_qq)
if platform == PLATFORM_QQCHAN:
with_tag = True
@@ -542,13 +550,13 @@ def oper_msg(message,
role = "admin"
if qq_msg == "":
send_message(platform, message, f"Hi~", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"Hi~", msg_ref=msg_ref)
return
if with_tag:
# 检查发言频率
if not check_frequency(user_id):
send_message(platform, message, f'你的发言超过频率限制(╯▔皿▔)╯。\n管理员设置{frequency_time}秒内只能提问{frequency_count}次。', msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f'你的发言超过频率限制(╯▔皿▔)╯。\n管理员设置{frequency_time}秒内只能提问{frequency_count}次。', msg_ref=msg_ref)
return
# logf.write("[GOCQBOT] "+ qq_msg+'\n')
@@ -557,19 +565,19 @@ def oper_msg(message,
# 关键词回复
for k in keywords:
if qq_msg == k:
send_message(platform, message, keywords[k], msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, keywords[k], msg_ref=msg_ref)
return
# 关键词拦截器
for i in uw.unfit_words_q:
matches = re.match(i, qq_msg.strip(), re.I | re.M)
if matches:
send_message(platform, message, f"你的提问得到的回复未通过【自有关键词拦截】服务, 不予回复。", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"你的提问得到的回复未通过【自有关键词拦截】服务, 不予回复。", msg_ref=msg_ref)
return
if baidu_judge != None:
check, msg = baidu_judge.judge(qq_msg)
if not check:
send_message(platform, message, f"你的提问得到的回复未通过【百度AI内容审核】服务, 不予回复。\n\n{msg}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"你的提问得到的回复未通过【百度AI内容审核】服务, 不予回复。\n\n{msg}", msg_ref=msg_ref)
return
# 检查是否是更换语言模型的请求
@@ -590,50 +598,67 @@ def oper_msg(message,
qq_msg = l[1]
else:
# if role != "admin":
# send_message(platform, message, "你没有权限更换语言模型。", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
# send_message(platform, message, "你没有权限更换语言模型。", msg_ref=msg_ref)
# return
chosen_provider = target
save_provider_preference(chosen_provider)
send_message(platform, message, f"已切换至【{chosen_provider}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"已切换至【{chosen_provider}", msg_ref=msg_ref)
return
chatgpt_res = ""
if chosen_provider == OPENAI_OFFICIAL:
hit, command_result = command_openai_official.check_command(qq_msg, session_id, user_name, role, platform=platform, message_obj=message, cached_plugins=cached_plugins, qq_platform=gocq_bot)
hit, command_result = command_openai_official.check_command(qq_msg, session_id, user_name, role,
platform=platform, message_obj=message,
cached_plugins=cached_plugins,
qq_platform=gocq_bot)
# hit: 是否触发了指令
if not hit:
if not with_tag:
return
if chatgpt == None:
send_message(platform, message, f"管理员未启动OpenAI模型或初始化时失败。", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"管理员未启动OpenAI模型或初始化时失败。", msg_ref=msg_ref)
return
# 请求ChatGPT获得结果
try:
chatgpt_res = chatgpt.text_chat(qq_msg, session_id)
if _global_object != None and "web_search" in _global_object and _global_object["web_search"]:
chatgpt_res = gplugin.web_search(qq_msg, chatgpt)
else:
chatgpt_res = str(chatgpt.text_chat(qq_msg))
if OPENAI_OFFICIAL in reply_prefix:
chatgpt_res = reply_prefix[OPENAI_OFFICIAL] + chatgpt_res
except (BaseException) as e:
gu.log("OpenAI API请求错误, 原因: "+str(e), gu.LEVEL_ERROR)
send_message(platform, message, f"OpenAI API错误, 原因: {str(e)}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"OpenAI API错误, 原因: {str(e)}", msg_ref=msg_ref)
elif chosen_provider == REV_CHATGPT:
hit, command_result = command_rev_chatgpt.check_command(qq_msg, role, platform=platform, message_obj=message, cached_plugins=cached_plugins, qq_platform=gocq_bot)
hit, command_result = command_rev_chatgpt.check_command(qq_msg, role,
platform=platform,
message_obj=message,
cached_plugins=cached_plugins,
qq_platform=gocq_bot)
if not hit:
if not with_tag:
return
if rev_chatgpt == None:
send_message(platform, message, f"管理员未启动此模型或者此模型初始化时失败。", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"管理员未启动此模型或者此模型初始化时失败。", msg_ref=msg_ref)
return
try:
while rev_chatgpt.is_all_busy():
time.sleep(1)
chatgpt_res = str(rev_chatgpt.text_chat(qq_msg))
# ws_prompt = f"{qq_msg}\n\n提示:"
# chatgpt_res = str(rev_chatgpt.text_chat(ws_prompt))
if _global_object != None and "web_search" in _global_object and _global_object["web_search"]:
chatgpt_res = gplugin.web_search(qq_msg, rev_chatgpt)
else:
chatgpt_res = str(rev_chatgpt.text_chat(qq_msg))
if REV_CHATGPT in reply_prefix:
chatgpt_res = reply_prefix[REV_CHATGPT] + chatgpt_res
except BaseException as e:
gu.log("逆向ChatGPT请求错误, 原因: "+str(e), gu.LEVEL_ERROR)
send_message(platform, message, f"RevChatGPT错误, 原因: \n{str(e)}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"RevChatGPT错误, 原因: \n{str(e)}", msg_ref=msg_ref)
elif chosen_provider == REV_EDGEGPT:
if bing_cache_loop == None:
@@ -641,32 +666,35 @@ def oper_msg(message,
bing_cache_loop = gocq_loop
elif platform == PLATFORM_QQCHAN:
bing_cache_loop = qqchan_loop
hit, command_result = command_rev_edgegpt.check_command(qq_msg, bing_cache_loop, role, platform=platform, message_obj=message, cached_plugins=cached_plugins, qq_platform=gocq_bot)
hit, command_result = command_rev_edgegpt.check_command(qq_msg, bing_cache_loop, role,
platform=platform, message_obj=message,
cached_plugins=cached_plugins,
qq_platform=gocq_bot)
if not hit:
try:
if not with_tag:
return
if rev_edgegpt == None:
send_message(platform, message, f"管理员未启动此模型或者此模型初始化时失败。", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"管理员未启动此模型或者此模型初始化时失败。", msg_ref=msg_ref)
return
while rev_edgegpt.is_busy():
time.sleep(1)
res, res_code = asyncio.run_coroutine_threadsafe(rev_edgegpt.text_chat(qq_msg, platform), bing_cache_loop).result()
if res_code == 0: # bing不想继续话题,重置会话后重试。
send_message(platform, message, "Bing不想继续话题了, 正在自动重置会话并重试。", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, "Bing不想继续话题了, 正在自动重置会话并重试。", msg_ref=msg_ref)
asyncio.run_coroutine_threadsafe(rev_edgegpt.forget(), bing_cache_loop).result()
res, res_code = asyncio.run_coroutine_threadsafe(rev_edgegpt.text_chat(qq_msg, platform), bing_cache_loop).result()
if res_code == 0: # bing还是不想继续话题,大概率说明提问有问题。
asyncio.run_coroutine_threadsafe(rev_edgegpt.forget(), bing_cache_loop).result()
send_message(platform, message, "Bing仍然不想继续话题, 会话已重置, 请检查您的提问后重试。", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, "Bing仍然不想继续话题, 会话已重置, 请检查您的提问后重试。", msg_ref=msg_ref)
res = ""
chatgpt_res = str(res)
if REV_EDGEGPT in reply_prefix:
chatgpt_res = reply_prefix[REV_EDGEGPT] + chatgpt_res
except BaseException as e:
gu.log("NewBing请求错误, 原因: "+str(e), gu.LEVEL_ERROR)
send_message(platform, message, f"Rev NewBing API错误。原因如下:\n{str(e)} \n前往官方频道反馈~", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"Rev NewBing API错误。原因如下:\n{str(e)} \n前往官方频道反馈~", msg_ref=msg_ref)
# 切换回原来的语言模型
if temp_switch != "":
@@ -691,17 +719,22 @@ def oper_msg(message,
if isinstance(command_result[1], list) and len(command_result) == 3 and command_result[2] == 'draw':
if chatgpt != None:
for i in command_result[1]:
send_message(platform, message, i, msg_ref=msg_ref, image=i, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
# i is a link
# 保存到本地
pic_res = requests.get(i, stream = True)
if pic_res.status_code == 200:
image = PILImage.open(io.BytesIO(pic_res.content))
send_message(platform, message, [Image.fromFileSystem(gu.save_temp_img(image))], msg_ref=msg_ref)
else:
send_message(platform, message, "画图指令需要启用OpenAI官方模型.", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, "画图指令需要启用OpenAI官方模型.", msg_ref=msg_ref)
else:
try:
send_message(platform, message, command_result[1], msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, command_result[1], msg_ref=msg_ref)
except BaseException as e:
send_message(platform, message, f"回复消息出错: {str(e)}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"回复消息出错: {str(e)}", msg_ref=msg_ref)
else:
send_message(platform, message, f"指令调用错误: \n{str(command_result[1])}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"指令调用错误: \n{str(command_result[1])}", msg_ref=msg_ref)
return
@@ -720,18 +753,18 @@ def oper_msg(message,
if baidu_judge != None:
check, msg = baidu_judge.judge(chatgpt_res)
if not check:
send_message(platform, message, f"你的提问得到的回复【百度内容审核】未通过,不予回复。\n\n{msg}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, f"你的提问得到的回复【百度内容审核】未通过,不予回复。\n\n{msg}", msg_ref=msg_ref)
return
# 发送qq信息
try:
if platform==PLATFORM_GOCQ:
if cc.get("qq_pic_mode", False):
send_message(platform, message, chatgpt_res, image_mode=True, msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, chatgpt_res, image_mode=True, msg_ref=msg_ref)
else:
send_message(platform, message, chatgpt_res, msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, chatgpt_res, msg_ref=msg_ref)
else:
send_message(platform, message, chatgpt_res, msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot)
send_message(platform, message, chatgpt_res, msg_ref=msg_ref)
except BaseException as e:
gu.log("回复消息错误: \n"+str(e), gu.LEVEL_ERROR)
@@ -802,9 +835,9 @@ class gocqClient():
@gocq_app.receiver("GroupMemberIncrease")
async def _(app: CQHTTP, source: GroupMemberIncrease):
global nick_qq, cc
global nick_qq, announcement
await app.sendGroupMessage(source.group_id, [
Plain(text=cc.get("qq_welcome", "欢迎新人~")),
Plain(text = announcement),
])
@gocq_app.receiver("GuildMessage")
+8 -3
View File
@@ -1,6 +1,5 @@
import os, sys
from pip._internal import main as pipmain
import util.general_utils as gu
abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/'
@@ -14,11 +13,17 @@ def main():
print(e)
input("第三方依赖库未完全安装完毕,请退出程序重试。")
exit()
import util.general_utils as gu
if 'http_proxy' in cfg:
os.environ['HTTP_PROXY'] = cfg['http_proxy']
if 'https_proxy' in cfg:
os.environ['HTTPS_PROXY'] = cfg['https_proxy']
os.environ['NO_PROXY'] = 'cn.bing.com,https://api.sgroup.qq.com'
# 检查temp文件夹
if not os.path.exists(abs_path+"temp"):
os.mkdir(abs_path+"temp")
provider = privider_chooser(cfg)
if len(provider) == 0:
@@ -119,4 +124,4 @@ if __name__ == "__main__":
except BaseException as e:
print(e)
print(f"[System-err] Replit Web保活服务启动失败:{str(e)}")
main()
main()
+64 -36
View File
@@ -41,7 +41,11 @@ class Command:
except BaseException as e:
raise e
def check_command(self, message, role, platform, message_obj, cached_plugins: dict, qq_platform: QQ):
def check_command(self, message, role, platform,
message_obj,
cached_plugins: dict,
qq_platform: QQ,
global_object: dict):
# 插件
for k, v in cached_plugins.items():
@@ -62,15 +66,30 @@ class Command:
return True, self.get_my_id(message_obj, platform)
if self.command_start_with(message, "nconf") or self.command_start_with(message, "newconf"):
return True, self.get_new_conf(message, role, platform)
if self.command_start_with(message, "web"): # 网页搜索
return True, self.web_search(message, global_object)
return False, None
def web_search(self, message, global_object):
if "web_search" not in global_object:
global_object["web_search"] = False
if message == "web on":
global_object["web_search"] = True
return True, "已开启网页搜索", "web"
elif message == "web off":
global_object["web_search"] = False
return True, "已关闭网页搜索", "web"
return True, f"网页搜索功能当前状态: {global_object['web_search']}", "web"
def get_my_id(self, message_obj, platform):
print(message_obj)
if platform == "gocq":
if message_obj.type == "GuildMessage":
return True, f"你的频道id是{str(message_obj.sender.tiny_id)}", "plugin"
else:
return True, f"你的QQ是{str(message_obj.sender.user_id)}", "plugin"
else:
return True, f"{str(message_obj)}\n(此指令为开发专用,为提供更多数据,请自行从中找出您的频道ID。在author->id中。)", "plugin"
def get_new_conf(self, message, role, platform):
if role != "admin":
@@ -88,40 +107,39 @@ class Command:
def plugin_reload(self, cached_plugins: dict, target: str = None, all: bool = False):
plugins = self.get_plugin_modules()
fail_rec = ""
if plugins != None:
for p in plugins:
try:
if p not in cached_plugins or p == target or all:
module = __import__("addons.plugins." + p + "." + p, fromlist=[p])
if p in cached_plugins:
module = importlib.reload(module)
cls = putil.get_classes(p, module)
obj = getattr(module, cls[0])()
try:
info = obj.info()
if 'name' not in info or 'desc' not in info or 'version' not in info or 'author' not in info:
fail_rec += f"载入插件{p}失败,原因: 插件信息不完整\n"
continue
if isinstance(info, dict) == False:
fail_rec += f"载入插件{p}失败,原因: 插件信息格式不正确\n"
continue
except BaseException as e:
fail_rec += f"调用插件{p} info失败, 原因: {str(e)}\n"
continue
cached_plugins[p] = {
"module": module,
"clsobj": obj,
"info": info
}
except BaseException as e:
raise e
fail_rec += f"加载{p}插件出现问题,原因{str(e)}\n"
if fail_rec == "":
return True, None
else:
return False, fail_rec
else:
if plugins is None:
return False, "未找到任何插件模块"
for p in plugins:
try:
if p not in cached_plugins or p == target or all:
module = __import__("addons.plugins." + p + "." + p, fromlist=[p])
if p in cached_plugins:
module = importlib.reload(module)
cls = putil.get_classes(p, module)
obj = getattr(module, cls[0])()
try:
info = obj.info()
if 'name' not in info or 'desc' not in info or 'version' not in info or 'author' not in info:
fail_rec += f"载入插件{p}失败,原因: 插件信息不完整\n"
continue
if isinstance(info, dict) == False:
fail_rec += f"载入插件{p}失败,原因: 插件信息格式不正确\n"
continue
except BaseException as e:
fail_rec += f"调用插件{p} info失败, 原因: {str(e)}\n"
continue
cached_plugins[p] = {
"module": module,
"clsobj": obj,
"info": info
}
except BaseException as e:
fail_rec += f"加载{p}插件出现问题,原因{str(e)}\n"
if fail_rec == "":
return True, None
else:
return False, fail_rec
'''
插件指令
@@ -130,9 +148,9 @@ class Command:
l = message.split(" ")
if len(l) < 2:
if platform == gu.PLATFORM_GOCQ:
p = gu.create_text_image("【插件指令面板】", "安装插件: \nplugin i 插件Github地址\n卸载插件: \nplugin i 插件名 \n重载插件: \nplugin reload\n查看插件列表:\nplugin l\n更新插件: plugin u 插件名\n")
p = gu.create_text_image("【插件指令面板】", "安装插件: \nplugin i 插件Github地址\n卸载插件: \nplugin d 插件名 \n重载插件: \nplugin reload\n查看插件列表:\nplugin l\n更新插件: plugin u 插件名\n")
return True, [Image.fromFileSystem(p)], "plugin"
return True, "\n=====插件指令面板=====\n安装插件: \nplugin i 插件Github地址\n卸载插件: \nplugin i 插件名 \n重载插件: \nplugin reload\n查看插件列表:\nplugin l\n更新插件: plugin u 插件名\n===============", "plugin"
return True, "\n=====插件指令面板=====\n安装插件: \nplugin i 插件Github地址\n卸载插件: \nplugin d 插件名 \n重载插件: \nplugin reload\n查看插件列表:\nplugin l\n更新插件: plugin u 插件名\n===============", "plugin"
else:
ppath = ""
if os.path.exists("addons/plugins"):
@@ -188,6 +206,15 @@ class Command:
try:
repo = Repo(path = plugin_path)
repo.remotes.origin.pull()
# 读取插件的requirements.txt
if os.path.exists(os.path.join(plugin_path, "requirements.txt")):
with open(os.path.join(plugin_path, "requirements.txt"), "r", encoding="utf-8") as f:
for line in f.readlines():
mm = os.system(f"pip3 install {line.strip()}")
if mm != 0:
return False, "插件依赖安装失败,需要您手动pip安装对应插件的依赖。", "plugin"
ok, err = self.plugin_reload(cached_plugins, target=l[2])
if ok:
return True, "\n更新插件成功!!", "plugin"
@@ -292,6 +319,7 @@ class Command:
"reset": "重置会话",
"nick": "设置机器人昵称",
"plugin": "插件安装、卸载和重载",
"web on/off": "启动或关闭网页搜索能力",
"/bing": "切换到bing模型",
"/gpt": "切换到OpenAI ChatGPT API",
"/revgpt": "切换到网页版ChatGPT",
+8 -4
View File
@@ -6,9 +6,10 @@ from util import general_utils as gu
class CommandOpenAIOfficial(Command):
def __init__(self, provider: ProviderOpenAIOfficial):
def __init__(self, provider: ProviderOpenAIOfficial, global_object: dict):
self.provider = provider
self.cached_plugins = {}
self.global_object = global_object
def check_command(self,
message: str,
@@ -18,9 +19,12 @@ class CommandOpenAIOfficial(Command):
platform: str,
message_obj,
cached_plugins: dict,
qq_platform: QQ):
qq_platform: QQ,):
self.platform = platform
hit, res = super().check_command(message, role, platform, message_obj=message_obj, cached_plugins=cached_plugins, qq_platform=qq_platform)
hit, res = super().check_command(message, role, platform, message_obj=message_obj,
cached_plugins=cached_plugins,
qq_platform=qq_platform,
global_object=self.global_object)
if hit:
return True, res
if self.command_start_with(message, "reset", "重置"):
@@ -43,7 +47,7 @@ class CommandOpenAIOfficial(Command):
return True, self.set(message, session_id)
elif self.command_start_with(message, "update"):
return True, self.update(message, role)
elif self.command_start_with(message, ""):
elif self.command_start_with(message, "", "draw"):
return True, self.draw(message)
elif self.command_start_with(message, "keyword"):
return True, self.keyword(message, role)
+6 -2
View File
@@ -3,9 +3,10 @@ from model.provider.provider_rev_chatgpt import ProviderRevChatGPT
from model.platform.qq import QQ
class CommandRevChatGPT(Command):
def __init__(self, provider: ProviderRevChatGPT):
def __init__(self, provider: ProviderRevChatGPT, global_object: dict):
self.provider = provider
self.cached_plugins = {}
self.global_object = global_object
def check_command(self,
message: str,
@@ -15,7 +16,10 @@ class CommandRevChatGPT(Command):
cached_plugins: dict,
qq_platform: QQ):
self.platform = platform
hit, res = super().check_command(message, role, platform, message_obj=message_obj, cached_plugins=cached_plugins, qq_platform=qq_platform)
hit, res = super().check_command(message, role, platform, message_obj=message_obj,
cached_plugins=cached_plugins,
qq_platform=qq_platform,
global_object=self.global_object)
if hit:
return True, res
if self.command_start_with(message, "help", "帮助"):
+6 -3
View File
@@ -4,11 +4,11 @@ import asyncio
from model.platform.qq import QQ
class CommandRevEdgeGPT(Command):
def __init__(self, provider: ProviderRevEdgeGPT):
def __init__(self, provider: ProviderRevEdgeGPT, global_object: dict):
self.provider = provider
self.cached_plugins = {}
self.global_object = global_object
def check_command(self,
message: str,
loop,
@@ -18,7 +18,10 @@ class CommandRevEdgeGPT(Command):
cached_plugins: dict,
qq_platform: QQ):
self.platform = platform
hit, res = super().check_command(message, role, platform, message_obj=message_obj, cached_plugins=cached_plugins, qq_platform=qq_platform)
hit, res = super().check_command(message, role, platform, message_obj=message_obj,
cached_plugins=cached_plugins,
qq_platform=qq_platform,
global_object=self.global_object)
if hit:
return True, res
if self.command_start_with(message, "reset"):
+53 -46
View File
@@ -1,12 +1,13 @@
import io
import botpy
from PIL import Image
from PIL import Image as PILImage
from botpy.message import Message, DirectMessage
import re
import asyncio
import requests
from cores.qqbot.personality import personalities
from util import general_utils as gu
from nakuru.entities.components import Plain, At, Image
class QQChan():
@@ -15,51 +16,57 @@ class QQChan():
self.client = botclient
self.client.run(appid=appid, token=token)
def send_qq_msg(self, message, res, image_mode=False, msg_ref = None):
gu.log("回复QQ频道消息: "+str(res), level=gu.LEVEL_INFO, tag="QQ频道", max_len=30)
# gocq兼容层
def gocq_compatible(self, gocq_message_chain: list):
plain_text = ""
image_path = None # only one img supported
for i in gocq_message_chain:
if isinstance(i, Plain):
plain_text += i.text
elif isinstance(i, Image) and image_path == None:
image_path = i.path
return plain_text, image_path
if not image_mode:
try:
if msg_ref is not None:
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=str(res), message_reference = msg_ref), self.client.loop)
else:
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=str(res)), self.client.loop)
reply_res.result()
except BaseException as e:
# 分割过长的消息
if "msg over length" in str(e):
split_res = []
split_res.append(res[:len(res)//2])
split_res.append(res[len(res)//2:])
for i in split_res:
if msg_ref is not None:
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=i, message_reference = msg_ref), self.client.loop)
else:
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=i), self.client.loop)
reply_res.result()
else:
# 发送qq信息
def send_qq_msg(self, message: Message, res, msg_ref = None):
gu.log("回复QQ频道消息: "+str(res), level=gu.LEVEL_INFO, tag="QQ频道", max_len=500)
plain_text = ""
image_path = None
if isinstance(res, list):
# 兼容gocq
plain_text, image_path = self.gocq_compatible(res)
elif isinstance(res, str):
plain_text = res
print(plain_text, image_path)
try:
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=str(plain_text), message_reference = msg_ref, file_image=image_path), self.client.loop)
reply_res.result()
except BaseException as e:
# 分割过长的消息
if "msg over length" in str(e):
split_res = []
split_res.append(plain_text[:len(plain_text)//2])
split_res.append(plain_text[len(plain_text)//2:])
for i in split_res:
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=str(i), message_reference = msg_ref, file_image=image_path), self.client.loop)
reply_res.result()
else:
# 发送qq信息
try:
# 防止被qq频道过滤消息
plain_text = plain_text.replace(".", " . ")
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=str(plain_text), message_reference = msg_ref, file_image=image_path), self.client.loop)
# 发送信息
except BaseException as e:
print("QQ频道API错误: \n"+str(e))
try:
# 防止被qq频道过滤消息
res = res.replace(".", " . ")
asyncio.run_coroutine_threadsafe(message.reply(content=res), self.client.loop).result()
# 发送信息
reply_res = asyncio.run_coroutine_threadsafe(message.reply(content=str(str.join(" ", plain_text)), message_reference = msg_ref, file_image=image_path), self.client.loop)
except BaseException as e:
print("QQ频道API错误: \n"+str(e))
res = str.join(" ", res)
try:
asyncio.run_coroutine_threadsafe(message.reply(content=res), self.client.loop).result()
except BaseException as e:
# 如果还是不行则报出错误
res = re.sub(r'(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b', '[被隐藏的链接]', str(e), flags=re.MULTILINE)
res = res.replace(".", "·")
asyncio.run_coroutine_threadsafe(message.reply(content=res), self.client.loop).result()
# send(message, f"QQ频道API错误:{str(e)}\n下面是格式化后的回答:\n{f_res}")
else:
pic_res = requests.get(str(res), stream=True)
if pic_res.status_code == 200:
# 将二进制数据转换成图片对象
image = Image.open(io.BytesIO(pic_res.content))
# 保存图片到本地
image.save('tmp_image.jpg')
asyncio.run_coroutine_threadsafe(message.reply(file_image='tmp_image.jpg', content=""), self.client.loop)
plain_text = re.sub(r'(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b', '[被隐藏的链接]', str(e), flags=re.MULTILINE)
plain_text = plain_text.replace(".", "·")
asyncio.run_coroutine_threadsafe(message.reply(content=plain_text), self.client.loop).result()
# send(message, f"QQ频道API错误:{str(e)}\n下面是格式化后的回答:\n{f_res}")
+6 -1
View File
@@ -90,7 +90,10 @@ class ProviderOpenAIOfficial(Provider):
# 每隔10分钟转储一次
time.sleep(10*self.history_dump_interval)
def text_chat(self, prompt, session_id):
def text_chat(self, prompt, session_id = None):
if session_id is None:
session_id = "unknown"
del self.session_dict["unknown"]
# 会话机制
if session_id not in self.session_dict:
self.session_dict[session_id] = []
@@ -136,6 +139,8 @@ class ProviderOpenAIOfficial(Provider):
gu.log("token超限, 清空对应缓存")
self.session_dict[session_id] = []
cache_data_list, new_record, req = self.wrap(prompt, session_id)
elif 'Limit: 3 / min. Please try again in 20s.' in str(e):
time.sleep(60)
else:
gu.log(str(e), level=gu.LEVEL_ERROR)
err = str(e)
+41 -11
View File
@@ -2,28 +2,53 @@ from revChatGPT.V1 import Chatbot
from revChatGPT import typings
from model.provider.provider import Provider
from util import general_utils as gu
from util import cmd_config as cc
import time
class ProviderRevChatGPT(Provider):
def __init__(self, config):
self.rev_chatgpt = []
self.cc = cc.CmdConfig()
for i in range(0, len(config['account'])):
try:
gu.log(f"创建rev_ChatGPT负载{str(i)}中...", level=gu.LEVEL_INFO, tag="RevChatGPT")
gu.log(f"创建逆向ChatGPT负载{str(i+1)}中...", level=gu.LEVEL_INFO, tag="RevChatGPT")
if 'password' in config['account'][i]:
config['account'][i]['password'] = str(config['account'][i]['password'])
gu.log(f"创建逆向ChatGPT负载{str(i+1)}失败: 已不支持账号密码登录,请使用access_token方式登录。", level=gu.LEVEL_ERROR, tag="RevChatGPT")
continue
rev_account_config = {
'access_token': config['account'][i]['access_token'],
}
if self.cc.get("rev_chatgpt_model") != "":
rev_account_config['model'] = self.cc.get("rev_chatgpt_model")
if len(self.cc.get("rev_chatgpt_plugin_ids")) > 0:
rev_account_config['plugin_ids'] = self.cc.get("rev_chatgpt_plugin_ids")
if self.cc.get("rev_chatgpt_PUID") != "":
rev_account_config['PUID'] = self.cc.get("rev_chatgpt_PUID")
if len(self.cc.get("rev_chatgpt_unverified_plugin_domains")) > 0:
rev_account_config['unverified_plugin_domains'] = self.cc.get("rev_chatgpt_unverified_plugin_domains")
cb = Chatbot(config=rev_account_config)
# cb.captcha_solver = self.__captcha_solver
revstat = {
'obj': Chatbot(config=config['account'][i]),
'obj': cb,
'busy': False
}
self.rev_chatgpt.append(revstat)
except BaseException as e:
gu.log(f"创建rev_ChatGPT负载{str(i)}失败: {str(e)}", level=gu.LEVEL_ERROR, tag="RevChatGPT")
gu.log(f"创建逆向ChatGPT负载{str(i+1)}失败: {str(e)}", level=gu.LEVEL_ERROR, tag="RevChatGPT")
def forget(self) -> bool:
return False
# def __captcha_solver(images: list[str], challenge_details: dict) -> int:
# # Create tempfile
# print("Captcha solver called")
# print(images)
# print(challenge_details)
# input("Press Enter to continue...")
# return 0
def request_text(self, prompt: str, bot) -> str:
resp = ''
err_count = 0
@@ -35,8 +60,6 @@ class ProviderRevChatGPT(Provider):
resp = data["message"]
break
except typings.Error as e:
if e.code == typings.ErrorType.RATE_LIMIT_ERROR:
raise e
if e.code == typings.ErrorType.INVALID_ACCESS_TOKEN_ERROR:
raise e
if e.code == typings.ErrorType.EXPIRED_ACCESS_TOKEN_ERROR:
@@ -44,17 +67,25 @@ class ProviderRevChatGPT(Provider):
if e.code == typings.ErrorType.PROHIBITED_CONCURRENT_QUERY_ERROR:
raise e
if "The message you submitted was too long" in str(e):
raise e
if "You've reached our limit of messages per hour." in str(e):
raise e
if "Rate limited by proxy" in str(e):
gu.log(f"触发请求频率限制, 60秒后自动重试。", level=gu.LEVEL_WARNING, tag="RevChatGPT")
time.sleep(60)
err_count += 1
gu.log(f"请求出现问题: {str(e)} | 正在重试: {str(err_count)}", level=gu.LEVEL_WARNING, tag="RevChatGPT")
gu.log(f"请求异常: {str(e)}正在重试。({str(err_count)})", level=gu.LEVEL_WARNING, tag="RevChatGPT")
if err_count >= retry_count:
raise e
except BaseException as e:
err_count += 1
gu.log(f"请求出现问题: {str(e)} | 正在重试: {str(err_count)}", level=gu.LEVEL_WARNING, tag="RevChatGPT")
gu.log(f"请求异常: {str(e)}正在重试。({str(err_count)})", level=gu.LEVEL_WARNING, tag="RevChatGPT")
if err_count >= retry_count:
raise e
if resp == '':
resp = "RevChatGPT出现故障."
resp = "RevChatGPT请求异常。"
# print("[RevChatGPT] "+str(resp))
return resp
@@ -80,8 +111,7 @@ class ProviderRevChatGPT(Provider):
else:
err_msg += f"账号{cursor} - 错误原因: 忙碌"
continue
res = f'回复失败。错误跟踪:{err_msg}'
return res
raise Exception(f'回复失败。错误跟踪:{err_msg}')
def is_all_busy(self) -> bool:
for revstat in self.rev_chatgpt:
+2 -1
View File
@@ -1,7 +1,8 @@
pydantic~=1.10.4
requests~=2.28.1
openai~=0.27.4
qq-botpy~=1.1.2
revChatGPT~=5.0.0
revChatGPT~=6.8.6
baidu-aip~=4.16.9
EdgeGPT~=0.1.22.1
chardet~=5.1.0
+214
View File
@@ -0,0 +1,214 @@
import json
import util.general_utils as gu
class FuncCallJsonFormatError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
class FuncNotFoundError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
class FuncCall():
def __init__(self, provider) -> None:
self.func_list = []
self.provider = provider
def add_func(self, name: str = None, func_args: list = None, desc: str = None, func_obj = None) -> None:
if name == None or func_args == None or desc == None or func_obj == None:
raise FuncCallJsonFormatError("name, func_args, desc must be provided.")
self._func = {
"name": name,
"args": func_args,
"description": desc,
"func_obj": func_obj,
}
self.func_list.append(self._func)
def func_dump(self, intent: int = 2) -> str:
_l = []
for f in self.func_list:
_l.append({
"name": f["name"],
"args": f["args"],
"description": f["description"],
})
return json.dumps(_l, indent=intent, ensure_ascii=False)
def func_call(self, question, func_definition, is_task = False, tasks = None, taskindex = -1, is_summary = True):
funccall_prompt = """
我正在实现function call功能,该功能旨在让你变成给定的问题到给定的函数的解析器(这意味着你不是创造函数)。
下面会给你提供可能会用到函数的相关信息,和一个问题,你需要将其转换成给定的函数调用。
- 你的返回信息只含json,且严格仿照以下内容(不含注释):
```
{
"res": string // 如果没有找到对应的函数,那么你可以在这里正常输出内容。如果有,这里是空字符串。
"func_call": [ // 这是一个数组,里面包含了所有的函数调用,如果没有函数调用,那么这个数组是空数组。
{
"res": string // 如果没有找到对应的函数,那么你可以在这里正常输出内容。如果有,这里是空字符串。
"name": str, // 函数的名字
"args_type": {
"arg1": str, // 函数的参数的类型
"arg2": str,
...
},
"args": {
"arg1": any, // 函数的参数
"arg2": any,
...
}
},
... // 可能在这个问题中会有多个函数调用
],
}
```
- 如果用户的要求较复杂,允许返回多个函数调用,但需保证这些函数调用的顺序正确。
- 当问题没有提到给定的函数时,相当于提问方不打算使用function call功能,这时你可以在res中正常输出这个问题的回答(以AI的身份正常回答该问题,并将答案输出在res字段中,回答不要涉及到任何函数调用的内容,就只是正常讨论这个问题。)
提供的函数是:
"""
prompt = f"{funccall_prompt}\n```\n{func_definition}\n```\n"
prompt += f"""
用户的提问是:
```
{question}
```
"""
# if is_task:
# # task_prompt = f"\n任务列表为{str(tasks)}\n你目前进行到了任务{str(taskindex)}, **你不需要重新进行已经进行过的任务, 不要生成已经进行过的**"
# prompt += task_prompt
# provider.forget()
_c = 0
while _c < 3:
try:
res = self.provider.text_chat(prompt)
if res.find('```') != -1:
res = res[res.find('```json') + 7: res.rfind('```')]
gu.log("REVGPT func_call json result", bg=gu.BG_COLORS["green"], fg=gu.FG_COLORS["white"])
print(res)
res = json.loads(res)
break
except Exception as e:
_c += 1
if _c == 3:
raise e
if "The message you submitted was too long" in str(e):
raise e
invoke_func_res = ""
if len(res["func_call"]) > 0:
task_list = res["func_call"]
invoke_func_res_list = []
for res in task_list:
# 说明有函数调用
func_name = res["name"]
# args_type = res["args_type"]
args = res["args"]
# 调用函数
# func = eval(func_name)
func_target = None
for func in self.func_list:
if func["name"] == func_name:
func_target = func["func_obj"]
break
if func_target == None:
raise FuncNotFoundError(f"Request function {func_name} not found.")
t_res = str(func_target(**args))
invoke_func_res += f"{func_name} 调用结果:\n```\n{t_res}\n```\n"
invoke_func_res_list.append(invoke_func_res)
gu.log(f"[FUNC| {func_name} invoked]", bg=gu.BG_COLORS["green"], fg=gu.FG_COLORS["white"])
# print(str(t_res))
if is_summary:
# 生成返回结果
after_prompt = """
函数返回以下内容:"""+invoke_func_res+"""
请以AI助手的身份结合返回的内容对用户提问做详细全面的回答。
用户的提问是:
```""" + question + """```
- 在res字段中,不要输出函数的返回值,也不要针对返回值的字段进行分析,也不要输出用户的提问,而是理解这一段返回的结果,并以AI助手的身份回答问题,只需要输出回答的内容,不需要在回答的前面加上身份词。
- 你的返回信息必须只能是json,且需严格遵循以下内容(不含注释):
```json
{
"res": string, // 回答的内容
"func_call_again": bool // 如果函数返回的结果有错误或者问题,可将其设置为true,否则为false
}
```
- 如果func_call_again为trueres请你设为空值,否则请你填写回答的内容。"""
_c = 0
while _c < 5:
try:
res = self.provider.text_chat(after_prompt)
# 截取```之间的内容
gu.log("DEBUG BEGIN", bg=gu.BG_COLORS["yellow"], fg=gu.FG_COLORS["white"])
print(res)
gu.log("DEBUG END", bg=gu.BG_COLORS["yellow"], fg=gu.FG_COLORS["white"])
if res.find('```') != -1:
res = res[res.find('```json') + 7: res.rfind('```')]
gu.log("REVGPT after_func_call json result", bg=gu.BG_COLORS["green"], fg=gu.FG_COLORS["white"])
after_prompt_res = res
after_prompt_res = json.loads(after_prompt_res)
break
except Exception as e:
_c += 1
if _c == 5:
raise e
if "The message you submitted was too long" in str(e):
# 如果返回的内容太长了,那么就截取一部分
invoke_func_res = invoke_func_res[:int(len(invoke_func_res) / 2)]
after_prompt = """
函数返回以下内容:"""+invoke_func_res+"""
请以AI助手的身份结合返回的内容对用户提问做详细全面的回答。
用户的提问是:
```""" + question + """```
- 在res字段中,不要输出函数的返回值,也不要针对返回值的字段进行分析,也不要输出用户的提问,而是理解这一段返回的结果,并以AI助手的身份回答问题,只需要输出回答的内容,不需要在回答的前面加上身份词。
- 你的返回信息必须只能是json,且需严格遵循以下内容(不含注释):
```json
{
"res": string, // 回答的内容
"func_call_again": bool // 如果函数返回的结果有错误或者问题,可将其设置为true,否则为false
}
```
- 如果func_call_again为trueres请你设为空值,否则请你填写回答的内容。"""
else:
raise e
if "func_call_again" in after_prompt_res and after_prompt_res["func_call_again"]:
# 如果需要重新调用函数
# 重新调用函数
gu.log("REVGPT func_call_again", bg=gu.BG_COLORS["purple"], fg=gu.FG_COLORS["white"])
res = self.func_call(question, func_definition)
return res, True
gu.log("REVGPT func callback:", bg=gu.BG_COLORS["green"], fg=gu.FG_COLORS["white"])
# print(after_prompt_res["res"])
return after_prompt_res["res"], True
else:
return str(invoke_func_res_list), True
else:
# print(res["res"])
return res["res"], False
+2 -2
View File
@@ -51,7 +51,7 @@ def log(
tag: str = "System",
fg: str = None,
bg: str = None,
max_len: int = 100):
max_len: int = 300):
"""
日志记录函数
"""
@@ -489,5 +489,5 @@ def test_markdown():
image = render_markdown(markdown_text)
image.show()
test_markdown()
# test_markdown()
+150
View File
@@ -0,0 +1,150 @@
import requests
import util.general_utils as gu
from bs4 import BeautifulSoup
import time
from util.func_call import (
FuncCall,
FuncCallJsonFormatError,
FuncNotFoundError
)
def tidy_text(text: str) -> str:
return text.strip().replace("\n", "").replace(" ", "").replace("\r", "")
def special_fetch_zhihu(link: str) -> str:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
response = requests.get(link, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
r = soup.find(class_="List-item").find(class_="RichContent-inner")
if r is None:
print("debug: zhihu none")
raise Exception("zhihu none")
return tidy_text(r.text)
def web_keyword_search_via_bing(keyword) -> str:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
url = "https://cn.bing.com/search?q="+keyword
_cnt = 0
_detail_store = []
while _cnt < 5:
try:
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
res = []
ols = soup.find(id="b_results")
for i in ols.find_all("li", class_="b_algo"):
try:
title = i.find("h2").text
desc = i.find("p").text
link = i.find("h2").find("a").get("href")
res.append({
"title": title,
"desc": desc,
"link": link,
})
if len(_detail_store) < 2 and "zhihu.com" in link:
try:
_detail_store.append(special_fetch_zhihu(link)[:800])
except BaseException as e:
print(f"zhihu parse err: {str(e)}")
if len(res) >= 5: # 限制5条
break
except Exception as e:
print(f"bing parse err: {str(e)}")
if len(res) == 0:
break
if len(_detail_store) > 0:
ret = f"{str(res)} \n来源知乎的具体资料: {str(_detail_store)}"
else:
ret = f"{str(res)}"
return str(ret)
except Exception as e:
print(f"bing fetch err: {str(e)}")
_cnt += 1
time.sleep(1)
print("fail to fetch bing info, using sougou.")
return web_keyword_search_via_sougou(keyword)
def web_keyword_search_via_sougou(keyword) -> str:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
}
url = f"https://sogou.com/web?query={keyword}"
response = requests.get(url, headers=headers)
response.encoding = "utf-8"
soup = BeautifulSoup(response.text, "html.parser")
res = []
results = soup.find("div", class_="results")
for i in results.find_all("div", class_="vrwrap"):
try:
title = tidy_text(i.find("h3").text)
link = tidy_text(i.find("h3").find("a").get("href"))
if link.startswith("/link?url="):
link = "https://www.sogou.com" + link
res.append({
"title": title,
"link": link,
})
except:
pass
ret = f"{str(res)} \n全部内容: {tidy_text(soup.text)}"
return ret
def fetch_website_content(url):
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
res = soup.text
res = res.replace("\n", "")
with open(f"temp_{time.time()}.html", "w", encoding="utf-8") as f:
f.write(res)
return res
def web_search(question, provider):
new_func_call = FuncCall(provider)
new_func_call.add_func("web_keyword_search_via_bing", [{
"type": "string",
"name": "keyword",
"brief": "必应搜索的关键词(分词,尽量保留所有信息)"
}],
"在必应搜索引擎上搜索给定的关键词,并且返回第一页的搜索结果列表(标题,简介和链接)",
web_keyword_search_via_bing
)
func_definition1 = new_func_call.func_dump()
question1 = f"{question} \n(只能调用一个函数。)"
res1, has_func = new_func_call.func_call(question1, func_definition1, is_task=False, is_summary=False)
has_func = True
if has_func:
provider.forget()
question3 = f"""请你回答`{question}`问题。\n以下是相关材料,你请直接拿此材料针对问题进行总结回答,然后再给出参考链接。不要提到任何函数调用的信息。```\n{res1}\n```\n"""
print(question3)
_c = 0
while _c < 5:
try:
print('text chat')
res3 = provider.text_chat(question3)
break
except Exception as e:
print(e)
_c += 1
if _c == 5:
raise e
if "The message you submitted was too long" in str(e):
res2 = res2[:int(len(res2) / 2)]
question3 = f"""请你回答`{question}`问题。\n以下是相关材料,请直接拿此材料针对问题进行回答,然后再给出参考链接。```\n{res1}\n{res2}\n```\n"""
return res3
else:
return res1