Compare commits

...

52 Commits

Author SHA1 Message Date
Rock Chin
cf23c5d31c Release v2.2.4 2023-03-19 08:38:07 +00:00
Rock Chin
84418a296b doc: 完善pr模板 2023-03-19 08:37:23 +00:00
Rock Chin
5f83cc6bb7 Merge pull request #300 from RockChinQ/token-process
[Perf] Tokens相关处理逻辑优化
2023-03-19 16:35:25 +08:00
Rock Chin
cde168c93c doc: full_scenario的编写教程 (#301) 2023-03-19 08:32:34 +00:00
Rock Chin
fed24c0748 doc: 添加chordfish-k/QChartGPT_Emoticon_Plugin 2023-03-19 13:35:20 +08:00
Rock Chin
b45d11b3c3 Update pull_request_template.md 2023-03-19 11:28:38 +08:00
Rock Chin
84d9af69bb Update pull_request_template.md 2023-03-19 11:28:17 +08:00
Rock Chin
684d356646 Update pull_request_template.md 2023-03-19 11:17:07 +08:00
Rock Chin
975300c9fc Create pull_request_template.md 2023-03-19 11:15:45 +08:00
Rock Chin
ca349e33fc feat: 实现新的前文剪切模式 2023-03-18 15:57:28 +00:00
Rock Chin
ccf62fe95c doc: 致谢GPT-4内测提供者 2023-03-18 22:28:06 +08:00
Rock Chin
d056cb6769 feat: 数据库接口支持 2023-03-18 12:57:36 +00:00
Rock Chin
b0016eebf9 feat: 添加override-all.json 2023-03-18 20:44:14 +08:00
Rock Chin
0490ad9207 test: token计数测试 2023-03-18 11:26:18 +00:00
Rock Chin
4a20ae236b doc: README.md格式错误 2023-03-18 09:15:26 +00:00
Rock Chin
9be1c7fc6f doc: 添加WaitYiYan插件链接 2023-03-18 08:17:51 +00:00
Rock Chin
5621d32b30 doc: GPT-4说明 2023-03-18 04:42:46 +00:00
Rock Chin
b7642fe876 feat: 支持GPT-4 API 2023-03-18 04:38:48 +00:00
Rock Chin
c842485d33 perf: 尝试安装依赖时的逻辑 2023-03-17 07:49:27 +00:00
Rock Chin
341444ef1c chore: 添加devcontainer配置 2023-03-17 07:39:16 +00:00
Rock Chin
66f5a219d2 feat: 不再提示InvalidRequestError的可能原因 2023-03-16 21:10:10 +08:00
Rock Chin
002919fffe doc: 优化README.md格式 2023-03-16 19:38:35 +08:00
Rock Chin
087d097204 feat: 不再默认提供max_tokens 2023-03-16 13:37:48 +08:00
Rock Chin
ca4eeda6f0 doc: 添加oliverkirk-sudo的文字转语音插件 2023-03-16 09:08:00 +08:00
Rock Chin
94543a4708 Merge pull request #282 from systemtang/bugfix
[Feat] 修复usage命令的代理问题
2023-03-16 08:53:25 +08:00
Rock Chin
d4738dfb46 Release v2.2.3 2023-03-15 22:50:40 +08:00
Rock Chin
3bdf6810aa fix: 消息处理时的错误 2023-03-15 22:47:20 +08:00
systemt
f489c2f3b4 修复usage命令的代理问题 2023-03-15 21:04:55 +08:00
Rock Chin
a724bfe155 Release v2.2.2 2023-03-15 20:39:10 +08:00
Rock Chin
179a372bfe feat: 更改到process.py处理长消息 2023-03-15 20:33:44 +08:00
Rock Chin
651d765ab0 doc: 添加New Bing说明 2023-03-15 17:33:31 +08:00
Rock Chin
7ddc853f63 chore: 忽略保存的公告 2023-03-15 15:50:14 +08:00
Rock Chin
1bd1bfc725 chore: 删除测试公告 2023-03-15 15:47:24 +08:00
Rock Chin
f6ec0fda7a Merge pull request #280 from RockChinQ/announcement
[Feat] 添加公告输出功能
2023-03-15 15:46:58 +08:00
Rock Chin
7be368ae8c feat: 添加公告功能 2023-03-15 15:43:36 +08:00
Rock Chin
f67db2617b debug: 测试公告内容1 2023-03-15 15:37:07 +08:00
Rock Chin
ed5bf8100f chore: 添加公告内容 2023-03-15 15:22:19 +08:00
Rock Chin
0ef8a1c9ae chore: 为new bing忽略cookies.json 2023-03-15 11:24:45 +08:00
Rock Chin
32460cbf78 doc: 添加GPT-4公告 2023-03-15 11:04:10 +08:00
Rock Chin
6f6c9c222c doc: 添加网页版GPT-4说明 2023-03-15 10:57:29 +08:00
Rock Chin
438d0ed1ea Merge pull request #277 from zyckk4/dev
chore: 去除多余import
2023-03-14 13:11:47 +08:00
zyckk4
3ef1c71cad chore: 去除多余import 2023-03-14 13:03:50 +08:00
Rock Chin
aaadf6b8ba doc: 部署方式依赖项指令 2023-03-14 10:57:02 +08:00
Rock Chin
6af614f319 doc: 整理致谢列表 2023-03-14 10:54:46 +08:00
Rock Chin
c75dbd67df doc: 整理致谢列表 2023-03-14 10:53:32 +08:00
Rock Chin
dc3d186e2a Merge pull request #274 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.2
chore(deps): update openai requirement from ~=0.27.0 to ~=0.27.2
2023-03-13 17:48:10 +08:00
dependabot[bot]
44550feddd chore(deps): update openai requirement from ~=0.27.0 to ~=0.27.2
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.0...v0.27.2)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-13 09:05:46 +00:00
Rock Chin
a0810d5f63 Merge pull request #271 from RockChinQ/config-covering
feat: 支持json格式的配置文件 (#265)
2023-03-13 11:05:11 +08:00
Rock Chin
cfc97fb22d feat: 支持json格式的配置文件 (#265) 2023-03-13 10:58:15 +08:00
Rock Chin
d67dbe8062 doc: 添加JSON格式情景预设的说明 2023-03-13 10:31:21 +08:00
Rock Chin
e89035e11c Release v2.2.1 2023-03-12 22:43:39 +08:00
Rock Chin
2ea711e629 fix: 更新包中包含新文件时更新失败 2023-03-12 22:43:02 +08:00
24 changed files with 423 additions and 74 deletions

View File

@@ -0,0 +1,34 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
"name": "QChatGPT 3.10",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/python:0-3.10",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "pip3 install --user -r requirements.txt",
// Configure tool-specific properties.
// "customizations": {},
"customizations": {
"codespaces": {
"repositories": {
"RockChinQ/QChatGPT": {
"permissions": "write-all"
},
"RockChinQ/revLibs": {
"permissions": "write-all"
}
}
}
}
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

22
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,22 @@
### 概述
实现/解决/优化的内容:
### 事务
- [ ] 已阅读仓库[贡献指引](../CONTRIBUTING.md)
- [ ] 已与维护者在issues或其他平台沟通此PR大致内容
### 功能
- [ ] 已编写完善的配置文件字段说明(若有新增)
- [ ] 已测试新功能
### 兼容性
- [ ] 已处理版本兼容性
- [ ] 已处理插件兼容问题
### 风险
可能导致或已知的问题:

5
.gitignore vendored
View File

@@ -13,4 +13,7 @@ sensitive.json
temp/
current_tag
scenario/
!scenario/default-template.json
!scenario/default-template.json
override.json
cookies.json
res/announcement_saved

View File

@@ -1,8 +1,9 @@
# QChatGPT🤖
> 2023/3/3 官方接口疑似被墙,可考虑使用网络代理 [#198](https://github.com/RockChinQ/QChatGPT/issues/198)
> 2023/3/18 现已支持GPT-4 API内测请查看`config-template.py`中的`completion_api_params`
> 2023/3/15 逆向库已支持New Bing使用方法查看[插件文档](https://github.com/RockChinQ/revLibs)
> 2023/3/15 逆向库已支持GPT-4模型使用方法查看[插件](https://github.com/RockChinQ/revLibs)
> 2023/3/3 现已在主线支持官方ChatGPT接口使用方法查看[#195](https://github.com/RockChinQ/QChatGPT/issues/195)
> 2023/3/2 OpenAI已发布ChatGPT官方接口我们正在全力接入预计明日前完成请查看[此PR](https://github.com/RockChinQ/QChatGPT/pull/194)
> 2023/2/16 现已支持接入ChatGPT网页版详情请完成部署并查看底部**插件**小节或[此仓库](https://github.com/RockChinQ/revLibs)
- 到[项目Wiki](https://github.com/RockChinQ/QChatGPT/wiki)可了解项目详细信息
- 由bilibili TheLazy制作的[视频教程](https://www.bilibili.com/video/BV15v4y1X7aP)
@@ -17,8 +18,11 @@
### 文字对话
- OpenAI GPT-3.5模型(ChatGPT API), 本项目原生支持, 默认使用
- OpenAI GPT-3模型, 本项目原生支持, 部署完成后前往config.py切换
- ChatGPT网页版逆向API, 由[插件](https://github.com/RockChinQ/revLibs)接入
- OpenAI GPT-3模型, 本项目原生支持, 部署完成后前往`config.py`切换
- OpenAI GPT-4模型, 本项目原生支持, 目前需要您的账户通过OpenAI的内测申请, 请前往`config.py`切换
- ChatGPT网页版GPT-3.5模型, 由[插件](https://github.com/RockChinQ/revLibs)接入
- ChatGPT网页版GPT-4模型, 目前需要ChatGPT Plus订阅, 由[插件](https://github.com/RockChinQ/revLibs)接入
- New Bing逆向库, 由[插件](https://github.com/RockChinQ/revLibs)接入
### 故事续写
@@ -32,6 +36,7 @@
### 语音生成
- TTS+VITS, 由[插件](https://github.com/dominoar/QChatPlugins)接入
- Plachta/VITS-Umamusume-voice-synthesizer, 由[插件](https://github.com/oliverkirk-sudo/chat_voice)接入
## ✅功能
@@ -106,6 +111,12 @@
- “丢弃”策略:此分钟内对话次数达到限制时,丢弃之后的对话
- 详细请查看config.py中的相关配置
</details>
<details>
<summary>✅支持使用网络代理</summary>
- 目前已支持正向代理访问接口
- 详细请查看config.py中的`openai_config`的说明
</details>
详情请查看[Wiki功能使用页](https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E5%8A%9F%E8%83%BD%E7%82%B9%E5%88%97%E4%B8%BE)
@@ -117,7 +128,7 @@
参考以下文章自行注册
> [国内注册ChatGPT的方法(100%可用)](https://www.pythonthree.com/register-openai-chatgpt/)
> [国内注册ChatGPT的方法(100%可用)](https://www.pythonthree.com/register-openai-chatgpt/)
> [手把手教你如何注册ChatGPT超级详细](https://guxiaobei.com/51461)
注册成功后请前往[个人中心查看](https://beta.openai.com/account/api-keys)api_key
@@ -162,8 +173,7 @@ cd QChatGPT
2. 安装依赖
```bash
pip3 install yiri-mirai openai colorlog func_timeout
pip3 install dulwich
pip3 install yiri-mirai openai colorlog func_timeout dulwich Pillow
```
3. 运行一次主程序,生成配置文件
@@ -218,18 +228,19 @@ python3 main.py
- [hello_plugin](https://github.com/RockChinQ/hello_plugin) - `hello_plugin` 的储存库形式,插件开发模板
- [dominoar/QChatPlugins](https://github.com/dominoar/QchatPlugins) - dominoar编写的诸多新功能插件语言输出、Ranimg、屏蔽词规则等
- [dominoar/QCP-NovelAi](https://github.com/dominoar/QCP-NovelAi) - NovelAI 故事叙述与绘画
- [oliverkirk-sudo/chat_voice](https://github.com/oliverkirk-sudo/chat_voice) - 文字转语音输出使用HuggingFace上的[VITS-Umamusume-voice-synthesizer模型](https://huggingface.co/spaces/Plachta/VITS-Umamusume-voice-synthesizer)
- [RockChinQ/WaitYiYan](https://github.com/RockChinQ/WaitYiYan) - 实时获取百度`文心一言`等待列表人数
- [QChartGPT_Emoticon_Plugin](https://github.com/chordfish-k/QChartGPT_Emoticon_Plugin) - 使机器人根据回复内容发送表情包
## 😘致谢
- [@the-lazy-me](https://github.com/the-lazy-me) 为本项目制作[视频教程](https://www.bilibili.com/video/BV15v4y1X7aP)
- [@mikumifa](https://github.com/mikumifa) 本项目Docker部署仓库开发者
- [@dominoar](https://github.com/dominoar) 为本项目开发多种插件
- [@hissincn](https://github.com/hissincn) 本项目贡献者
- [@LINSTCL](https://github.com/LINSTCL) GPT-3.5官方模型适配贡献者
- [@Haibersut](https://github.com/Haibersut) 本项目贡献者
- [@万神的星空](https://github.com/qq255204159) 整合包发行
- [@ljcduo](https://github.com/ljcduo) GPT-4 API内测账号提供
以及其他所有为本项目提供支持的朋友们。
以及所有[贡献者](https://github.com/RockChinQ/QChatGPT/graphs/contributors)和其他为本项目提供支持的朋友们。
## 👍赞赏

View File

@@ -79,9 +79,33 @@ default_prompt = {
"default": "如果我之后想获取帮助,请你说“输入!help获取帮助”",
}
# 实验性设置项: JSON完整情景导入
# 预设prompt模式
# 情景预设格式
# 参考值旧版本方式default | 完整情景full_scenario
# 旧版本的格式为上述default_prompt中的内容或prompts目录下的文件名
#
# 完整情景预设的格式为JSON在scenario目录下的JSON文件中列出对话的每个回合编写方法见scenario/default-template.json
# 编写方法例如:
# {
# "prompt": [
# {
# "role": "user",
# "content": "之后当我需要帮助时,请说“输入!help获取帮助”"
# },{
# "role": "assistant",
# "content": "好的,当你之后需要帮助时,我会说“输入!help获取帮助”"
# },{
# "role": "user",
# "content": "帮助"
# },{
# "role": "assistant",
# "content": "输入!help获取帮助"
# }
# ]
# }
#
# 您可以按照上述格式编写自己的情景预设在prompt中列出对话的每个回合
# role为user或assistant分别表示用户和机器人的回复
# 每个JSON文件是一个情景预设文件名即为情景预设的名称
preset_mode = "default"
# 群内响应规则
@@ -138,12 +162,16 @@ encourage_sponsor_at_start = True
# 每次向OpenAI接口发送对话记录上下文的字符数
# 最大不超过(4096 - max_tokens)个字符max_tokens为下方completion_api_params中的max_tokens
# 注意较大的prompt_submit_length会导致OpenAI账户额度消耗更快
prompt_submit_length = 1024
prompt_submit_length = 2048
# OpenAI补全API的参数
# 请在下方填写模型,程序自动选择接口
# 现已支持的模型有:
#
# 'gpt-4'
# 'gpt-4-0314'
# 'gpt-4-32k'
# 'gpt-4-32k-0314'
# 'gpt-3.5-turbo'
# 'gpt-3.5-turbo-0301'
# 'text-davinci-003'
@@ -155,10 +183,10 @@ prompt_submit_length = 1024
# 'text-ada-001'
#
# 具体请查看OpenAI的文档: https://beta.openai.com/docs/api-reference/completions/create
# 请将内容修改到config.py中请勿修改此文件
completion_api_params = {
"model": "gpt-3.5-turbo",
"temperature": 0.9, # 数值越低得到的回答越理性,取值范围[0, 1]
"max_tokens": 1024, # 每次获取OpenAI接口响应的文字量上限, 不高于4096
"top_p": 1, # 生成的文本的文本与要求的符合度, 取值范围[0, 1]
"frequency_penalty": 0.2,
"presence_penalty": 1.0,

23
generate_override_all.py Normal file
View File

@@ -0,0 +1,23 @@
# 使用config-template生成override.json的字段全集模板文件override-all.json
# 关于override.json机制请参考https://github.com/RockChinQ/QChatGPT/pull/271
import json
import importlib
template = importlib.import_module("config-template")
output_json = {
"comment": "这是override.json支持的字段全集, 关于override.json机制, 请查看https://github.com/RockChinQ/QChatGPT/pull/271"
}
for k, v in template.__dict__.items():
if k.startswith("__"):
continue
# 如果是module
if type(v) == type(template):
continue
print(k, v, type(v))
output_json[k] = v
with open("override-all.json", "w", encoding="utf-8") as f:
json.dump(output_json, f, indent=4, ensure_ascii=False)

28
main.py
View File

@@ -1,4 +1,5 @@
import importlib
import json
import os
import shutil
import threading
@@ -12,8 +13,8 @@ try:
except ImportError:
# 尝试安装
import pkg.utils.pkgmgr as pkgmgr
pkgmgr.install_requirements("requirements.txt")
try:
pkgmgr.install_requirements("requirements.txt")
import colorlog
except ImportError:
print("依赖不满足,请查看 https://github.com/RockChinQ/qcg-installer/issues/15")
@@ -32,7 +33,7 @@ log_colors_config = {
'INFO': 'white',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'bold_red',
'CRITICAL': 'cyan',
}
@@ -143,9 +144,22 @@ def main(first_time_init=False):
setattr(config, key, getattr(config_template, key))
logging.warning("[{}]不存在".format(key))
is_integrity = False
if not is_integrity:
logging.warning("配置文件不完整请依据config-template.py检查config.py")
logging.warning("以上配置已被设为默认值将在5秒后继续启动... ")
# 检查override.json覆盖
if os.path.exists("override.json"):
override_json = json.load(open("override.json", "r", encoding="utf-8"))
for key in override_json:
if hasattr(config, key):
setattr(config, key, override_json[key])
logging.info("覆写配置[{}]为[{}]".format(key, override_json[key]))
else:
logging.error("无法覆写配置[{}]为[{}]该配置不存在请检查override.json是否正确".format(key, override_json[key]))
if not is_integrity:
time.sleep(5)
import pkg.utils.context
@@ -293,13 +307,21 @@ def main(first_time_init=False):
import pkg.utils.updater
try:
if pkg.utils.updater.is_new_version_available():
pkg.utils.context.get_qqbot_manager().notify_admin("新版本可用,请发送 !update 进行自动更新\n更新日志:\n{}".format("\n".join(pkg.utils.updater.get_rls_notes())))
logging.info("新版本可用,请发送 !update 进行自动更新\n更新日志:\n{}".format("\n".join(pkg.utils.updater.get_rls_notes())))
else:
logging.info("当前已是最新版本")
except Exception as e:
logging.warning("检查更新失败:{}".format(e))
try:
import pkg.utils.announcement as announcement
new_announcement = announcement.fetch_new()
if new_announcement != "":
logging.critical("[公告] {}".format(new_announcement))
except Exception as e:
logging.warning("获取公告失败:{}".format(e))
return qqbot

75
override-all.json Normal file
View File

@@ -0,0 +1,75 @@
{
"comment": "这是override.json支持的字段全集, 关于override.json机制, 请查看https://github.com/RockChinQ/QChatGPT/pull/271",
"mirai_http_api_config": {
"adapter": "WebSocketAdapter",
"host": "localhost",
"port": 8080,
"verifyKey": "yirimirai",
"qq": 1234567890
},
"openai_config": {
"api_key": {
"default": "openai_api_key"
},
"http_proxy": null
},
"admin_qq": 0,
"default_prompt": {
"default": "如果我之后想获取帮助,请你说“输入!help获取帮助”"
},
"preset_mode": "default",
"response_rules": {
"at": true,
"prefix": [
"/ai",
"!ai",
"ai",
"ai"
],
"regexp": [],
"random_rate": 0.0
},
"ignore_rules": {
"prefix": [
"/"
],
"regexp": []
},
"income_msg_check": false,
"sensitive_word_filter": true,
"baidu_check": false,
"baidu_api_key": "",
"baidu_secret_key": "",
"inappropriate_message_tips": "[百度云]请珍惜机器人,当前返回内容不合规",
"encourage_sponsor_at_start": true,
"prompt_submit_length": 1024,
"completion_api_params": {
"model": "gpt-3.5-turbo",
"temperature": 0.9,
"top_p": 1,
"frequency_penalty": 0.2,
"presence_penalty": 1.0
},
"image_api_params": {
"size": "256x256"
},
"quote_origin": true,
"include_image_description": true,
"process_message_timeout": 30,
"show_prefix": false,
"blob_message_threshold": 256,
"blob_message_strategy": "forward",
"font_path": "",
"retry_times": 3,
"hide_exce_info_to_user": false,
"alter_tip_message": "出错了,请稍后再试",
"pool_num": 10,
"session_expire_time": 1200,
"rate_limitation": 60,
"rate_limit_strategy": "wait",
"rate_limit_drop_tip": "本分钟对话次数超过限速次数,此对话被丢弃",
"upgrade_dependencies": true,
"report_usage": true,
"logging_level": 20,
"help_message": "此机器人通过调用OpenAI的GPT-3大型语言模型生成回复不具有情感。\n你可以用自然语言与其交流回复的消息中[GPT]开头的为模型生成的语言,[bot]开头的为程序提示。\n了解此项目请找QQ 1010553892 联系作者\n请不要用其生成整篇文章或大段代码因为每次只会向模型提交少部分文字生成大部分文字会产生偏题、前后矛盾等问题\n每次会话最后一次交互后20分钟后会自动结束结束后将开启新会话如需继续前一次会话请发送 !last 重新开启\n欢迎到github.com/RockChinQ/QChatGPT 给个star\n\n指令帮助信息请查看: https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E6%9C%BA%E5%99%A8%E4%BA%BA%E6%8C%87%E4%BB%A4"
}

View File

@@ -54,20 +54,27 @@ class DatabaseManager:
`last_interact_timestamp` bigint not null,
`status` varchar(255) not null default 'on_going',
`default_prompt` text not null default '',
`prompt` text not null
`prompt` text not null,
`token_counts` text not null default '[]'
)
""")
# 检查sessions表是否存在`default_prompt`字段
# 检查sessions表是否存在`default_prompt`字段, 检查是否存在`token_counts`字段
self.__execute__("PRAGMA table_info('sessions')")
columns = self.cursor.fetchall()
has_default_prompt = False
has_token_counts = False
for field in columns:
if field[1] == 'default_prompt':
has_default_prompt = True
if field[1] == 'token_counts':
has_token_counts = True
if has_default_prompt and has_token_counts:
break
if not has_default_prompt:
self.__execute__("alter table `sessions` add column `default_prompt` text not null default ''")
if not has_token_counts:
self.__execute__("alter table `sessions` add column `token_counts` text not null default '[]'")
self.__execute__("""
@@ -89,7 +96,7 @@ class DatabaseManager:
# session持久化
def persistence_session(self, subject_type: str, subject_number: int, create_timestamp: int,
last_interact_timestamp: int, prompt: str, default_prompt: str = ''):
last_interact_timestamp: int, prompt: str, default_prompt: str = '', token_counts: str = ''):
"""持久化指定session"""
# 检查是否已经有了此name和create_timestamp的session
@@ -102,20 +109,20 @@ class DatabaseManager:
if count == 0:
sql = """
insert into `sessions` (`name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `default_prompt`)
values (?, ?, ?, ?, ?, ?, ?)
insert into `sessions` (`name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `default_prompt`, `token_counts`)
values (?, ?, ?, ?, ?, ?, ?, ?)
"""
self.__execute__(sql,
("{}_{}".format(subject_type, subject_number), subject_type, subject_number, create_timestamp,
last_interact_timestamp, prompt, default_prompt))
last_interact_timestamp, prompt, default_prompt, token_counts))
else:
sql = """
update `sessions` set `last_interact_timestamp` = ?, `prompt` = ?
update `sessions` set `last_interact_timestamp` = ?, `prompt` = ?, `token_counts` = ?
where `type` = ? and `number` = ? and `create_timestamp` = ?
"""
self.__execute__(sql, (last_interact_timestamp, prompt, subject_type,
self.__execute__(sql, (last_interact_timestamp, prompt, token_counts, subject_type,
subject_number, create_timestamp))
# 显式关闭一个session
@@ -140,7 +147,7 @@ class DatabaseManager:
# 从数据库中加载所有还没过期的session
config = pkg.utils.context.get_config()
self.__execute__("""
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`, `default_prompt`
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`, `default_prompt`, `token_counts`
from `sessions` where `last_interact_timestamp` > {}
""".format(int(time.time()) - config.session_expire_time))
results = self.cursor.fetchall()
@@ -154,6 +161,7 @@ class DatabaseManager:
prompt = result[5]
status = result[6]
default_prompt = result[7]
token_counts = result[8]
# 当且仅当最后一个该对象的会话是on_going状态时才会被加载
if status == 'on_going':
@@ -163,7 +171,8 @@ class DatabaseManager:
'create_timestamp': create_timestamp,
'last_interact_timestamp': last_interact_timestamp,
'prompt': prompt,
'default_prompt': default_prompt
'default_prompt': default_prompt,
'token_counts': token_counts
}
else:
if session_name in sessions:
@@ -175,7 +184,7 @@ class DatabaseManager:
def last_session(self, session_name: str, cursor_timestamp: int):
self.__execute__("""
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`, `default_prompt`
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`, `default_prompt`, `token_counts`
from `sessions` where `name` = '{}' and `last_interact_timestamp` < {} order by `last_interact_timestamp` desc
limit 1
""".format(session_name, cursor_timestamp))
@@ -192,6 +201,7 @@ class DatabaseManager:
prompt = result[5]
status = result[6]
default_prompt = result[7]
token_counts = result[8]
return {
'subject_type': subject_type,
@@ -199,14 +209,15 @@ class DatabaseManager:
'create_timestamp': create_timestamp,
'last_interact_timestamp': last_interact_timestamp,
'prompt': prompt,
'default_prompt': default_prompt
'default_prompt': default_prompt,
'token_counts': token_counts
}
# 获取此session_name后一个session的数据
def next_session(self, session_name: str, cursor_timestamp: int):
self.__execute__("""
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`, `default_prompt`
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`, `default_prompt`, `token_counts`
from `sessions` where `name` = '{}' and `last_interact_timestamp` > {} order by `last_interact_timestamp` asc
limit 1
""".format(session_name, cursor_timestamp))
@@ -223,6 +234,7 @@ class DatabaseManager:
prompt = result[5]
status = result[6]
default_prompt = result[7]
token_counts = result[8]
return {
'subject_type': subject_type,
@@ -230,13 +242,14 @@ class DatabaseManager:
'create_timestamp': create_timestamp,
'last_interact_timestamp': last_interact_timestamp,
'prompt': prompt,
'default_prompt': default_prompt
'default_prompt': default_prompt,
'token_counts': token_counts
}
# 列出与某个对象的所有对话session
def list_history(self, session_name: str, capacity: int, page: int):
self.__execute__("""
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`, `default_prompt`
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`, `default_prompt`, `token_counts`
from `sessions` where `name` = '{}' order by `last_interact_timestamp` desc limit {} offset {}
""".format(session_name, capacity, capacity * page))
results = self.cursor.fetchall()
@@ -250,6 +263,7 @@ class DatabaseManager:
prompt = result[5]
status = result[6]
default_prompt = result[7]
token_counts = result[8]
sessions.append({
'subject_type': subject_type,
@@ -257,7 +271,8 @@ class DatabaseManager:
'create_timestamp': create_timestamp,
'last_interact_timestamp': last_interact_timestamp,
'prompt': prompt,
'default_prompt': default_prompt
'default_prompt': default_prompt,
'token_counts': token_counts
})
return sessions

View File

@@ -34,7 +34,7 @@ class OpenAIInteract:
pkg.utils.context.set_openai_manager(self)
# 请求OpenAI Completion
def request_completion(self, prompts) -> str:
def request_completion(self, prompts) -> tuple[str, int]:
"""请求补全接口回复
Parameters:
@@ -60,14 +60,18 @@ class OpenAIInteract:
logging.debug("OpenAI response: %s", response)
# 记录使用量
current_round_token = 0
if 'model' in config.completion_api_params:
self.audit_mgr.report_text_model_usage(config.completion_api_params['model'],
ai.get_total_tokens())
current_round_token = ai.get_total_tokens()
elif 'engine' in config.completion_api_params:
self.audit_mgr.report_text_model_usage(config.completion_api_params['engine'],
response['usage']['total_tokens'])
current_round_token = response['usage']['total_tokens']
return ai.get_message()
return ai.get_message(), current_round_token
def request_image(self, prompt) -> dict:
"""请求图片接口回复

View File

@@ -21,6 +21,10 @@ COMPLETION_MODELS = {
CHAT_COMPLETION_MODELS = {
'gpt-3.5-turbo',
'gpt-3.5-turbo-0301',
'gpt-4',
'gpt-4-0314',
'gpt-4-32k',
'gpt-4-32k-0314'
}
EDIT_MODELS = {

View File

@@ -72,6 +72,7 @@ def load_sessions():
temp_session.last_interact_timestamp = session_data[session_name]['last_interact_timestamp']
try:
temp_session.prompt = json.loads(session_data[session_name]['prompt'])
temp_session.token_counts = json.loads(session_data[session_name]['token_counts'])
except Exception:
temp_session.prompt = reset_session_prompt(session_name, session_data[session_name]['prompt'])
temp_session.persistence()
@@ -106,6 +107,9 @@ class Session:
prompt = []
"""使用list来保存会话中的回合"""
token_counts = []
"""每个回合的token数量"""
default_prompt = []
"""本session的默认prompt"""
@@ -146,6 +150,8 @@ class Session:
self.name = name
self.create_timestamp = int(time.time())
self.last_interact_timestamp = int(time.time())
self.prompt = []
self.token_counts = []
self.schedule()
self.response_lock = threading.Lock()
@@ -209,9 +215,16 @@ class Session:
config = pkg.utils.context.get_config()
max_length = config.prompt_submit_length if hasattr(config, "prompt_submit_length") else 1024
prompts, counts = self.cut_out(text, max_length)
# 计算请求前的prompt数量
total_token_before_query = 0
for token_count in counts:
total_token_before_query += token_count
# 向API请求补全
message = pkg.utils.context.get_openai_manager().request_completion(
self.cut_out(text, max_length),
message, total_token = pkg.utils.context.get_openai_manager().request_completion(
prompts,
)
# 成功获取,处理回复
@@ -228,6 +241,10 @@ class Session:
self.prompt.append({'role': 'user', 'content': text})
self.prompt.append({'role': 'assistant', 'content': res_ans})
# 向token_counts中添加本回合的token数量
self.token_counts.append(total_token-total_token_before_query)
logging.debug("本回合使用token: {}, session counts: {}".format(total_token-total_token_before_query, self.token_counts))
if self.just_switched_to_exist_session:
self.just_switched_to_exist_session = False
self.set_ongoing()
@@ -244,39 +261,65 @@ class Session:
question = self.prompt[-2]['content']
self.prompt = self.prompt[:-2]
self.token_counts = self.token_counts[:-1]
# 返回上一回合的问题
return question
# 构建对话体
def cut_out(self, msg: str, max_tokens: int) -> list:
"""将现有prompt进行切割处理使得新的prompt长度不超过max_tokens"""
# 如果用户消息长度超过max_tokens直接返回
temp_prompt: list = []
temp_prompt += self.default_prompt
temp_prompt.append(
def cut_out(self, msg: str, max_tokens: int) -> tuple[list, list]:
"""将现有prompt进行切割处理使得新的prompt长度不超过max_tokens
:return: (新的prompt, 新的token_counts)
"""
# 最终由三个部分组成
# - default_prompt 情景预设固定值
# - changable_prompts 可变部分, 此会话中的历史对话回合
# - current_question 当前问题
# 包装目前的对话回合内容
changable_prompts = []
changable_counts = []
# 倒着来, 遍历prompt的步长为2, 遍历tokens_counts的步长为1
changable_index = len(self.prompt) - 1
token_count_index = len(self.token_counts) - 1
packed_tokens = 0
print(self.prompt)
while changable_index >= 0 and token_count_index >= 0:
if packed_tokens + self.token_counts[token_count_index] > max_tokens:
break
changable_prompts.insert(0, self.prompt[changable_index])
changable_prompts.insert(0, self.prompt[changable_index - 1])
changable_counts.insert(0, self.token_counts[token_count_index])
packed_tokens += self.token_counts[token_count_index]
changable_index -= 2
token_count_index -= 1
# 将default_prompt和changable_prompts合并
result_prompt = self.default_prompt + changable_prompts
print(changable_prompts)
# 添加当前问题
result_prompt.append(
{
'role': 'user',
'content': msg
}
)
token_count = 0
for item in temp_prompt:
token_count += len(item['content'])
logging.debug('cut_out: {}\nchangable section tokens: {}\npacked counts: {}\nsession counts: {}'.format(json.dumps(result_prompt, ensure_ascii=False, indent=4),
packed_tokens,
changable_counts,
self.token_counts))
# 倒序遍历prompt
for i in range(len(self.prompt) - 1, -1, -1):
if token_count >= max_tokens:
break
# 将prompt加到temp_prompt倒数第二个位置
temp_prompt.insert(len(self.default_prompt), self.prompt[i])
token_count += len(self.prompt[i]['content'])
logging.debug('cut_out: {}'.format(json.dumps(temp_prompt, ensure_ascii=False, indent=4)))
return temp_prompt
return result_prompt, changable_counts
# 持久化session
def persistence(self):
@@ -291,7 +334,7 @@ class Session:
subject_number = int(name_spt[1])
db_inst.persistence_session(subject_type, subject_number, self.create_timestamp, self.last_interact_timestamp,
json.dumps(self.prompt), json.dumps(self.default_prompt))
json.dumps(self.prompt), json.dumps(self.default_prompt), json.dumps(self.token_counts))
# 重置session
def reset(self, explicit: bool = False, expired: bool = False, schedule_new: bool = True, use_prompt: str = None):
@@ -314,6 +357,7 @@ class Session:
self.default_prompt = self.get_default_prompt(use_prompt)
self.prompt = []
self.token_counts = []
self.create_timestamp = int(time.time())
self.last_interact_timestamp = int(time.time())
self.just_switched_to_exist_session = False
@@ -339,6 +383,7 @@ class Session:
self.last_interact_timestamp = last_one['last_interact_timestamp']
try:
self.prompt = json.loads(last_one['prompt'])
self.token_counts = json.loads(last_one['token_counts'])
except json.decoder.JSONDecodeError:
self.prompt = reset_session_prompt(self.name, last_one['prompt'])
self.persistence()
@@ -359,6 +404,7 @@ class Session:
self.last_interact_timestamp = next_one['last_interact_timestamp']
try:
self.prompt = json.loads(next_one['prompt'])
self.token_counts = json.loads(next_one['token_counts'])
except json.decoder.JSONDecodeError:
self.prompt = reset_session_prompt(self.name, next_one['prompt'])
self.persistence()

View File

@@ -1,5 +1,4 @@
# 长消息处理相关
import logging
import os
import time
import base64
@@ -67,7 +66,7 @@ def check_text(text: str) -> list:
"""检查文本是否为长消息,并转换成该使用的消息链组件"""
if not hasattr(config, 'blob_message_threshold'):
return [text]
if len(text) > config.blob_message_threshold:
if not hasattr(config, 'blob_message_strategy'):
raise AttributeError('未定义长消息处理策略')

View File

@@ -4,6 +4,7 @@ import json
import datetime
import os
import threading
import traceback
import pkg.openai.session
import pkg.openai.manager
@@ -283,7 +284,8 @@ def process_command(session_name: str, text_message: str, mgr, config,
int(image_count))
# 获取此key的额度
try:
credit_data = credit.fetch_credit_data(api_keys[key_name])
http_proxy = config.openai_config["http_proxy"] if "http_proxy" in config.openai_config else None
credit_data = credit.fetch_credit_data(api_keys[key_name], http_proxy)
reply_str += " - 使用额度:{:.2f}/{:.2f}\n".format(credit_data['total_used'],credit_data['total_granted'])
except Exception as e:
logging.warning("获取额度失败:{}".format(e))
@@ -362,6 +364,7 @@ def process_command(session_name: str, text_message: str, mgr, config,
else:
pkg.utils.context.get_qqbot_manager().notify_admin("无新版本")
except Exception as e0:
traceback.print_exc()
pkg.utils.context.get_qqbot_manager().notify_admin("更新失败:{}".format(e0))
return

View File

@@ -1,6 +1,5 @@
# 普通消息处理模块
import logging
import time
import openai
import pkg.utils.context
import pkg.openai.session
@@ -64,7 +63,7 @@ def process_normal_message(text_message: str, mgr, config, launcher_type: str,
reply = event.get_return_value("reply")
if not event.is_prevented_default():
reply = blob.check_text(prefix + text)
reply = [prefix + text]
except openai.error.APIConnectionError as e:
err_msg = str(e)
if err_msg.__contains__('Error communicating with OpenAI'):
@@ -117,8 +116,7 @@ def process_normal_message(text_message: str, mgr, config, launcher_type: str,
reply = handle_exception("{}会话调用API失败:{}".format(session_name, e),
"[bot]err:RateLimitError,请重试或联系作者,或等待修复")
except openai.error.InvalidRequestError as e:
reply = handle_exception("{}API调用参数错误:{}\n\n这可能是由于config.py中的prompt_submit_length参数或"
"completion_api_params中的max_tokens参数数值过大导致的请尝试将其降低".format(
reply = handle_exception("{}API调用参数错误:{}\n".format(
session_name, e), "[bot]err:API调用参数错误请联系管理员或等待修复")
except openai.error.ServiceUnavailableError as e:
reply = handle_exception("{}API调用服务不可用:{}".format(session_name, e), "[bot]err:API调用服务不可用请重试或联系管理员或等待修复")

View File

@@ -26,6 +26,7 @@ import pkg.plugin.host as plugin_host
import pkg.plugin.models as plugin_models
import pkg.qqbot.ignore as ignore
import pkg.qqbot.banlist as banlist
import pkg.qqbot.blob as blob
processing = []
@@ -157,6 +158,7 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
reply[0][:min(100, len(reply[0]))] + (
"..." if len(reply[0]) > 100 else "")))
reply = [mgr.reply_filter.process(reply[0])]
reply = blob.check_text(reply[0])
else:
logging.info("回复[{}]消息".format(session_name))

44
pkg/utils/announcement.py Normal file
View File

@@ -0,0 +1,44 @@
import base64
import os
import requests
def read_latest() -> str:
resp = requests.get(
url="https://api.github.com/repos/RockChinQ/QChatGPT/contents/res/announcement",
)
obj_json = resp.json()
b64_content = obj_json["content"]
# 解码
content = base64.b64decode(b64_content).decode("utf-8")
return content
def read_saved() -> str:
# 已保存的在res/announcement_saved
# 检查是否存在
if not os.path.exists("res/announcement_saved"):
with open("res/announcement_saved", "w") as f:
f.write("")
with open("res/announcement_saved", "r") as f:
content = f.read()
return content
def write_saved(content: str):
# 已保存的在res/announcement_saved
with open("res/announcement_saved", "w") as f:
f.write(content)
def fetch_new() -> str:
latest = read_latest()
saved = read_saved()
if latest.replace(saved, "").strip() == "":
return ""
else:
write_saved(latest)
return latest.replace(saved, "").strip()

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,19 @@
# OpenAI账号免费额度剩余查询
import requests
def fetch_credit_data(api_key: str) -> dict:
def fetch_credit_data(api_key: str, http_proxy: str) -> dict:
"""OpenAI账号免费额度剩余查询"""
proxies = {
"http":http_proxy,
"https":http_proxy
} if http_proxy is not None else None
resp = requests.get(
url="https://api.openai.com/dashboard/billing/credit_grants",
headers={
"Authorization": "Bearer {}".format(api_key),
}
},
proxies=proxies
)
return resp.json()

View File

@@ -126,6 +126,15 @@ def update_all(cli: bool = False) -> bool:
dst = src.replace(source_root, ".")
if os.path.exists(dst):
os.remove(dst)
# 检查目标文件夹是否存在
if not os.path.exists(os.path.dirname(dst)):
os.makedirs(os.path.dirname(dst))
# 检查目标文件是否存在
if not os.path.exists(dst):
# 创建目标文件
open(dst, "w").close()
shutil.copy(src, dst)
# 把current_tag写入文件

View File

@@ -1,5 +1,5 @@
requests~=2.28.1
openai~=0.27.0
openai~=0.27.2
dulwich~=0.21.3
colorlog~=6.6.0
yiri-mirai~=0.2.6.1

1
res/announcement Normal file
View File

@@ -0,0 +1 @@

View File

View File