mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 12:05:54 +00:00
Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf23c5d31c | ||
|
|
84418a296b | ||
|
|
5f83cc6bb7 | ||
|
|
cde168c93c | ||
|
|
fed24c0748 | ||
|
|
b45d11b3c3 | ||
|
|
84d9af69bb | ||
|
|
684d356646 | ||
|
|
975300c9fc | ||
|
|
ca349e33fc | ||
|
|
ccf62fe95c | ||
|
|
d056cb6769 | ||
|
|
b0016eebf9 | ||
|
|
0490ad9207 | ||
|
|
4a20ae236b | ||
|
|
9be1c7fc6f | ||
|
|
5621d32b30 | ||
|
|
b7642fe876 | ||
|
|
c842485d33 | ||
|
|
341444ef1c | ||
|
|
66f5a219d2 | ||
|
|
002919fffe | ||
|
|
087d097204 | ||
|
|
ca4eeda6f0 | ||
|
|
94543a4708 | ||
|
|
d4738dfb46 | ||
|
|
3bdf6810aa | ||
|
|
f489c2f3b4 | ||
|
|
a724bfe155 | ||
|
|
179a372bfe | ||
|
|
651d765ab0 | ||
|
|
7ddc853f63 | ||
|
|
1bd1bfc725 | ||
|
|
f6ec0fda7a | ||
|
|
7be368ae8c | ||
|
|
f67db2617b | ||
|
|
ed5bf8100f | ||
|
|
0ef8a1c9ae | ||
|
|
32460cbf78 | ||
|
|
6f6c9c222c | ||
|
|
438d0ed1ea | ||
|
|
3ef1c71cad | ||
|
|
aaadf6b8ba | ||
|
|
6af614f319 | ||
|
|
c75dbd67df | ||
|
|
dc3d186e2a | ||
|
|
44550feddd | ||
|
|
a0810d5f63 | ||
|
|
cfc97fb22d | ||
|
|
d67dbe8062 | ||
|
|
e89035e11c | ||
|
|
2ea711e629 | ||
|
|
a716f071be | ||
|
|
3450a91824 | ||
|
|
d2c2b457e5 | ||
|
|
9cd7e49804 | ||
|
|
e9155e836f | ||
|
|
ed248539c7 | ||
|
|
54cc75506f | ||
|
|
4269c7927e | ||
|
|
064ac7f603 | ||
|
|
48ccf15273 | ||
|
|
b920ced6d4 | ||
|
|
69610a674c | ||
|
|
1828e34190 | ||
|
|
d53f4e3917 | ||
|
|
01706d5b4e | ||
|
|
8916b8a450 | ||
|
|
ed33af5638 | ||
|
|
c94a9e1ae6 | ||
|
|
e2e93afd06 | ||
|
|
a810158d5b | ||
|
|
5a5ebb95fc | ||
|
|
61dd9e29c0 | ||
|
|
ac65d81ba1 | ||
|
|
7288d3cb15 | ||
|
|
7477c7c67f | ||
|
|
453952859e | ||
|
|
85d46089e3 | ||
|
|
3b55f706de | ||
|
|
f448276423 | ||
|
|
830ee704da | ||
|
|
393369e446 | ||
|
|
2cc6a09905 | ||
|
|
d7d9d88e16 | ||
|
|
357d6aaf75 | ||
|
|
8059c422e3 | ||
|
|
b336e1334d | ||
|
|
12a0942ddb | ||
|
|
7e5a77f77e | ||
|
|
2933d4843f | ||
|
|
c5de978098 | ||
|
|
8b9cfab072 | ||
|
|
ea5f3c222f | ||
|
|
36bcbca15b | ||
|
|
2b2060e71b | ||
|
|
451688f2df | ||
|
|
d993852de7 | ||
|
|
9d73770a4e | ||
|
|
2541acf9d2 | ||
|
|
a1bfbad24e | ||
|
|
8af4918048 | ||
|
|
49f4ab0ec8 | ||
|
|
85c623fb0f | ||
|
|
9e28298250 | ||
|
|
7a04ef0985 | ||
|
|
83005e9ba9 | ||
|
|
f0c78f0529 | ||
|
|
3f638adcf9 | ||
|
|
d9405d8d5d | ||
|
|
606713a418 | ||
|
|
52102f0d0a | ||
|
|
df30931aad | ||
|
|
59877bf71d | ||
|
|
d2800ac58b |
34
.devcontainer/devcontainer.json
Normal file
34
.devcontainer/devcontainer.json
Normal 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
22
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
### 概述
|
||||||
|
|
||||||
|
实现/解决/优化的内容:
|
||||||
|
|
||||||
|
### 事务
|
||||||
|
|
||||||
|
- [ ] 已阅读仓库[贡献指引](../CONTRIBUTING.md)
|
||||||
|
- [ ] 已与维护者在issues或其他平台沟通此PR大致内容
|
||||||
|
|
||||||
|
### 功能
|
||||||
|
|
||||||
|
- [ ] 已编写完善的配置文件字段说明(若有新增)
|
||||||
|
- [ ] 已测试新功能
|
||||||
|
|
||||||
|
### 兼容性
|
||||||
|
|
||||||
|
- [ ] 已处理版本兼容性
|
||||||
|
- [ ] 已处理插件兼容问题
|
||||||
|
|
||||||
|
### 风险
|
||||||
|
|
||||||
|
可能导致或已知的问题:
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -11,4 +11,9 @@ prompts/
|
|||||||
logs/
|
logs/
|
||||||
sensitive.json
|
sensitive.json
|
||||||
temp/
|
temp/
|
||||||
current_tag
|
current_tag
|
||||||
|
scenario/
|
||||||
|
!scenario/default-template.json
|
||||||
|
override.json
|
||||||
|
cookies.json
|
||||||
|
res/announcement_saved
|
||||||
38
README.md
38
README.md
@@ -1,12 +1,13 @@
|
|||||||
# QChatGPT🤖
|
# 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/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)可了解项目详细信息
|
- 到[项目Wiki](https://github.com/RockChinQ/QChatGPT/wiki)可了解项目详细信息
|
||||||
- 由bilibili TheLazy制作的[视频教程](https://www.bilibili.com/video/BV15v4y1X7aP)
|
- 由bilibili TheLazy制作的[视频教程](https://www.bilibili.com/video/BV15v4y1X7aP)
|
||||||
- 交流、答疑群: ~~204785790~~(已满)、691226829、656285629
|
- 交流、答疑群: ~~204785790~~(已满)、~~691226829~~(已满)、656285629
|
||||||
- **进群提问前请您`确保`已经找遍文档和issue均无法解决**
|
- **进群提问前请您`确保`已经找遍文档和issue均无法解决**
|
||||||
- QQ频道机器人见[QQChannelChatGPT](https://github.com/Soulter/QQChannelChatGPT)
|
- QQ频道机器人见[QQChannelChatGPT](https://github.com/Soulter/QQChannelChatGPT)
|
||||||
|
|
||||||
@@ -17,8 +18,11 @@
|
|||||||
### 文字对话
|
### 文字对话
|
||||||
|
|
||||||
- OpenAI GPT-3.5模型(ChatGPT API), 本项目原生支持, 默认使用
|
- OpenAI GPT-3.5模型(ChatGPT API), 本项目原生支持, 默认使用
|
||||||
- OpenAI GPT-3模型, 本项目原生支持, 部署完成后前往config.py切换
|
- OpenAI GPT-3模型, 本项目原生支持, 部署完成后前往`config.py`切换
|
||||||
- ChatGPT网页版逆向API, 由[插件](https://github.com/RockChinQ/revLibs)接入
|
- 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)接入
|
- 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中的相关配置
|
- 详细请查看config.py中的相关配置
|
||||||
</details>
|
</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)
|
详情请查看[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)
|
> [手把手教你如何注册ChatGPT,超级详细](https://guxiaobei.com/51461)
|
||||||
|
|
||||||
注册成功后请前往[个人中心查看](https://beta.openai.com/account/api-keys)api_key
|
注册成功后请前往[个人中心查看](https://beta.openai.com/account/api-keys)api_key
|
||||||
@@ -162,8 +173,7 @@ cd QChatGPT
|
|||||||
2. 安装依赖
|
2. 安装依赖
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip3 install yiri-mirai openai colorlog func_timeout
|
pip3 install yiri-mirai openai colorlog func_timeout dulwich Pillow
|
||||||
pip3 install dulwich
|
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 运行一次主程序,生成配置文件
|
3. 运行一次主程序,生成配置文件
|
||||||
@@ -218,17 +228,19 @@ python3 main.py
|
|||||||
- [hello_plugin](https://github.com/RockChinQ/hello_plugin) - `hello_plugin` 的储存库形式,插件开发模板
|
- [hello_plugin](https://github.com/RockChinQ/hello_plugin) - `hello_plugin` 的储存库形式,插件开发模板
|
||||||
- [dominoar/QChatPlugins](https://github.com/dominoar/QchatPlugins) - dominoar编写的诸多新功能插件(语言输出、Ranimg、屏蔽词规则等)
|
- [dominoar/QChatPlugins](https://github.com/dominoar/QchatPlugins) - dominoar编写的诸多新功能插件(语言输出、Ranimg、屏蔽词规则等)
|
||||||
- [dominoar/QCP-NovelAi](https://github.com/dominoar/QCP-NovelAi) - NovelAI 故事叙述与绘画
|
- [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)
|
- [@the-lazy-me](https://github.com/the-lazy-me) 为本项目制作[视频教程](https://www.bilibili.com/video/BV15v4y1X7aP)
|
||||||
- [@mikumifa](https://github.com/mikumifa) 本项目Docker部署仓库开发者
|
- [@mikumifa](https://github.com/mikumifa) 本项目Docker部署仓库开发者
|
||||||
- [@dominoar](https://github.com/dominoar) 为本项目开发多种插件
|
- [@dominoar](https://github.com/dominoar) 为本项目开发多种插件
|
||||||
- [@hissincn](https://github.com/hissincn) 本项目贡献者
|
- [@万神的星空](https://github.com/qq255204159) 整合包发行
|
||||||
- [@LINSTCL](https://github.com/LINSTCL) GPT-3.5官方模型适配贡献者
|
- [@ljcduo](https://github.com/ljcduo) GPT-4 API内测账号提供
|
||||||
- [@Haibersut](https://github.com/Haibersut) 本项目贡献者
|
|
||||||
|
|
||||||
以及其他所有为本项目提供支持的朋友们。
|
以及所有[贡献者](https://github.com/RockChinQ/QChatGPT/graphs/contributors)和其他为本项目提供支持的朋友们。
|
||||||
|
|
||||||
## 👍赞赏
|
## 👍赞赏
|
||||||
|
|
||||||
|
|||||||
@@ -79,15 +79,47 @@ default_prompt = {
|
|||||||
"default": "如果我之后想获取帮助,请你说“输入!help获取帮助”",
|
"default": "如果我之后想获取帮助,请你说“输入!help获取帮助”",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 情景预设格式
|
||||||
|
# 参考值:旧版本方式: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"
|
||||||
|
|
||||||
# 群内响应规则
|
# 群内响应规则
|
||||||
# 符合此消息的群内消息即使不包含at机器人也会响应
|
# 符合此消息的群内消息即使不包含at机器人也会响应
|
||||||
# 支持消息前缀匹配及正则表达式匹配
|
# 支持消息前缀匹配及正则表达式匹配
|
||||||
|
# 支持设置是否响应at消息、随机响应概率
|
||||||
# 注意:由消息前缀(prefix)匹配的消息中将会删除此前缀,正则表达式(regexp)匹配的消息不会删除匹配的部分
|
# 注意:由消息前缀(prefix)匹配的消息中将会删除此前缀,正则表达式(regexp)匹配的消息不会删除匹配的部分
|
||||||
# 前缀匹配优先级高于正则表达式匹配
|
# 前缀匹配优先级高于正则表达式匹配
|
||||||
# 正则表达式简明教程:https://www.runoob.com/regexp/regexp-tutorial.html
|
# 正则表达式简明教程:https://www.runoob.com/regexp/regexp-tutorial.html
|
||||||
response_rules = {
|
response_rules = {
|
||||||
|
"at": True, # 是否响应at机器人的消息
|
||||||
"prefix": ["/ai", "!ai", "!ai", "ai"],
|
"prefix": ["/ai", "!ai", "!ai", "ai"],
|
||||||
"regexp": [] # "为什么.*", "怎么?样.*", "怎么.*", "如何.*", "[Hh]ow to.*", "[Ww]hy not.*", "[Ww]hat is.*", ".*怎么办", ".*咋办"
|
"regexp": [], # "为什么.*", "怎么?样.*", "怎么.*", "如何.*", "[Hh]ow to.*", "[Ww]hy not.*", "[Ww]hat is.*", ".*怎么办", ".*咋办"
|
||||||
|
"random_rate": 0.0, # 随机响应概率,0.0-1.0,0.0为不随机响应,1.0为响应所有消息, 仅在前几项判断不通过时生效
|
||||||
}
|
}
|
||||||
|
|
||||||
# 消息忽略规则
|
# 消息忽略规则
|
||||||
@@ -130,12 +162,16 @@ encourage_sponsor_at_start = True
|
|||||||
# 每次向OpenAI接口发送对话记录上下文的字符数
|
# 每次向OpenAI接口发送对话记录上下文的字符数
|
||||||
# 最大不超过(4096 - max_tokens)个字符,max_tokens为下方completion_api_params中的max_tokens
|
# 最大不超过(4096 - max_tokens)个字符,max_tokens为下方completion_api_params中的max_tokens
|
||||||
# 注意:较大的prompt_submit_length会导致OpenAI账户额度消耗更快
|
# 注意:较大的prompt_submit_length会导致OpenAI账户额度消耗更快
|
||||||
prompt_submit_length = 1024
|
prompt_submit_length = 2048
|
||||||
|
|
||||||
# OpenAI补全API的参数
|
# OpenAI补全API的参数
|
||||||
# 请在下方填写模型,程序自动选择接口
|
# 请在下方填写模型,程序自动选择接口
|
||||||
# 现已支持的模型有:
|
# 现已支持的模型有:
|
||||||
#
|
#
|
||||||
|
# 'gpt-4'
|
||||||
|
# 'gpt-4-0314'
|
||||||
|
# 'gpt-4-32k'
|
||||||
|
# 'gpt-4-32k-0314'
|
||||||
# 'gpt-3.5-turbo'
|
# 'gpt-3.5-turbo'
|
||||||
# 'gpt-3.5-turbo-0301'
|
# 'gpt-3.5-turbo-0301'
|
||||||
# 'text-davinci-003'
|
# 'text-davinci-003'
|
||||||
@@ -147,10 +183,10 @@ prompt_submit_length = 1024
|
|||||||
# 'text-ada-001'
|
# 'text-ada-001'
|
||||||
#
|
#
|
||||||
# 具体请查看OpenAI的文档: https://beta.openai.com/docs/api-reference/completions/create
|
# 具体请查看OpenAI的文档: https://beta.openai.com/docs/api-reference/completions/create
|
||||||
|
# 请将内容修改到config.py中,请勿修改此文件
|
||||||
completion_api_params = {
|
completion_api_params = {
|
||||||
"model": "gpt-3.5-turbo",
|
"model": "gpt-3.5-turbo",
|
||||||
"temperature": 0.9, # 数值越低得到的回答越理性,取值范围[0, 1]
|
"temperature": 0.9, # 数值越低得到的回答越理性,取值范围[0, 1]
|
||||||
"max_tokens": 1024, # 每次获取OpenAI接口响应的文字量上限, 不高于4096
|
|
||||||
"top_p": 1, # 生成的文本的文本与要求的符合度, 取值范围[0, 1]
|
"top_p": 1, # 生成的文本的文本与要求的符合度, 取值范围[0, 1]
|
||||||
"frequency_penalty": 0.2,
|
"frequency_penalty": 0.2,
|
||||||
"presence_penalty": 1.0,
|
"presence_penalty": 1.0,
|
||||||
@@ -202,6 +238,11 @@ hide_exce_info_to_user = False
|
|||||||
# 设置为空字符串时,不发送提示信息
|
# 设置为空字符串时,不发送提示信息
|
||||||
alter_tip_message = '出错了,请稍后再试'
|
alter_tip_message = '出错了,请稍后再试'
|
||||||
|
|
||||||
|
# 机器人线程池大小
|
||||||
|
# 该参数决定机器人可以同时处理几个人的消息,超出线程池数量的请求会被阻塞,不会被丢弃
|
||||||
|
# 如果你不清楚该参数的意义,请不要更改
|
||||||
|
pool_num = 10
|
||||||
|
|
||||||
# 每个会话的过期时间,单位为秒
|
# 每个会话的过期时间,单位为秒
|
||||||
# 默认值20分钟
|
# 默认值20分钟
|
||||||
session_expire_time = 60 * 20
|
session_expire_time = 60 * 20
|
||||||
@@ -241,11 +282,4 @@ help_message = """此机器人通过调用OpenAI的GPT-3大型语言模型生成
|
|||||||
每次会话最后一次交互后{}分钟后会自动结束,结束后将开启新会话,如需继续前一次会话请发送 !last 重新开启
|
每次会话最后一次交互后{}分钟后会自动结束,结束后将开启新会话,如需继续前一次会话请发送 !last 重新开启
|
||||||
欢迎到github.com/RockChinQ/QChatGPT 给个star
|
欢迎到github.com/RockChinQ/QChatGPT 给个star
|
||||||
|
|
||||||
帮助信息:
|
指令帮助信息请查看: 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""".format(session_expire_time // 60)
|
||||||
!help - 显示帮助
|
|
||||||
!reset - 重置会话
|
|
||||||
!last - 切换到前一次的对话
|
|
||||||
!next - 切换到后一次的对话
|
|
||||||
!prompt - 显示当前对话所有内容
|
|
||||||
!list - 列出所有历史会话
|
|
||||||
!usage - 列出各个api-key的使用量""".format(session_expire_time // 60)
|
|
||||||
|
|||||||
23
generate_override_all.py
Normal file
23
generate_override_all.py
Normal 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)
|
||||||
108
main.py
108
main.py
@@ -1,4 +1,5 @@
|
|||||||
import importlib
|
import importlib
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import threading
|
import threading
|
||||||
@@ -12,8 +13,8 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
# 尝试安装
|
# 尝试安装
|
||||||
import pkg.utils.pkgmgr as pkgmgr
|
import pkg.utils.pkgmgr as pkgmgr
|
||||||
pkgmgr.install_requirements("requirements.txt")
|
|
||||||
try:
|
try:
|
||||||
|
pkgmgr.install_requirements("requirements.txt")
|
||||||
import colorlog
|
import colorlog
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("依赖不满足,请查看 https://github.com/RockChinQ/qcg-installer/issues/15")
|
print("依赖不满足,请查看 https://github.com/RockChinQ/qcg-installer/issues/15")
|
||||||
@@ -32,7 +33,7 @@ log_colors_config = {
|
|||||||
'INFO': 'white',
|
'INFO': 'white',
|
||||||
'WARNING': 'yellow',
|
'WARNING': 'yellow',
|
||||||
'ERROR': 'red',
|
'ERROR': 'red',
|
||||||
'CRITICAL': 'bold_red',
|
'CRITICAL': 'cyan',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +46,9 @@ def init_db():
|
|||||||
|
|
||||||
def ensure_dependencies():
|
def ensure_dependencies():
|
||||||
import pkg.utils.pkgmgr as pkgmgr
|
import pkg.utils.pkgmgr as pkgmgr
|
||||||
pkgmgr.run_pip(["install", "openai", "Pillow", "--upgrade"])
|
pkgmgr.run_pip(["install", "openai", "Pillow", "--upgrade",
|
||||||
|
"-i", "https://pypi.douban.com/simple/",
|
||||||
|
"--trusted-host", "pypi.douban.com"])
|
||||||
|
|
||||||
|
|
||||||
known_exception_caught = False
|
known_exception_caught = False
|
||||||
@@ -105,6 +108,8 @@ def reset_logging():
|
|||||||
|
|
||||||
|
|
||||||
def main(first_time_init=False):
|
def main(first_time_init=False):
|
||||||
|
"""启动流程,reload之后会被执行"""
|
||||||
|
|
||||||
global known_exception_caught
|
global known_exception_caught
|
||||||
|
|
||||||
import config
|
import config
|
||||||
@@ -127,13 +132,39 @@ def main(first_time_init=False):
|
|||||||
|
|
||||||
config = importlib.import_module('config')
|
config = importlib.import_module('config')
|
||||||
|
|
||||||
import pkg.utils.context
|
|
||||||
pkg.utils.context.set_config(config)
|
|
||||||
|
|
||||||
init_runtime_log_file()
|
init_runtime_log_file()
|
||||||
|
|
||||||
sh = reset_logging()
|
sh = reset_logging()
|
||||||
|
|
||||||
|
# 配置完整性校验
|
||||||
|
is_integrity = True
|
||||||
|
config_template = importlib.import_module('config-template')
|
||||||
|
for key in dir(config_template):
|
||||||
|
if not key.startswith("__") and not hasattr(config, key):
|
||||||
|
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
|
||||||
|
pkg.utils.context.set_config(config)
|
||||||
|
|
||||||
# 检查是否设置了管理员
|
# 检查是否设置了管理员
|
||||||
if not (hasattr(config, 'admin_qq') and config.admin_qq != 0):
|
if not (hasattr(config, 'admin_qq') and config.admin_qq != 0):
|
||||||
# logging.warning("未设置管理员QQ,管理员权限指令及运行告警将无法使用,如需设置请修改config.py中的admin_qq字段")
|
# logging.warning("未设置管理员QQ,管理员权限指令及运行告警将无法使用,如需设置请修改config.py中的admin_qq字段")
|
||||||
@@ -165,6 +196,7 @@ def main(first_time_init=False):
|
|||||||
import pkg.openai.dprompt
|
import pkg.openai.dprompt
|
||||||
|
|
||||||
pkg.openai.dprompt.read_prompt_from_file()
|
pkg.openai.dprompt.read_prompt_from_file()
|
||||||
|
pkg.openai.dprompt.read_scenario_from_file()
|
||||||
|
|
||||||
pkg.utils.context.context['logger_handler'] = sh
|
pkg.utils.context.context['logger_handler'] = sh
|
||||||
# 主启动流程
|
# 主启动流程
|
||||||
@@ -180,7 +212,7 @@ def main(first_time_init=False):
|
|||||||
# 初始化qq机器人
|
# 初始化qq机器人
|
||||||
qqbot = pkg.qqbot.manager.QQBotManager(mirai_http_api_config=config.mirai_http_api_config,
|
qqbot = pkg.qqbot.manager.QQBotManager(mirai_http_api_config=config.mirai_http_api_config,
|
||||||
timeout=config.process_message_timeout, retry=config.retry_times,
|
timeout=config.process_message_timeout, retry=config.retry_times,
|
||||||
first_time_init=first_time_init)
|
first_time_init=first_time_init, pool_num=config.pool_num)
|
||||||
|
|
||||||
# 加载插件
|
# 加载插件
|
||||||
import pkg.plugin.host
|
import pkg.plugin.host
|
||||||
@@ -188,7 +220,7 @@ def main(first_time_init=False):
|
|||||||
|
|
||||||
pkg.plugin.host.initialize_plugins()
|
pkg.plugin.host.initialize_plugins()
|
||||||
|
|
||||||
if first_time_init: # 不是热重载之后的启动,则不启动新的bot线程
|
if first_time_init: # 不是热重载之后的启动,则启动新的bot线程
|
||||||
|
|
||||||
import mirai.exceptions
|
import mirai.exceptions
|
||||||
|
|
||||||
@@ -238,6 +270,11 @@ def main(first_time_init=False):
|
|||||||
qq_bot_thread = threading.Thread(target=run_bot_wrapper, args=(), daemon=True)
|
qq_bot_thread = threading.Thread(target=run_bot_wrapper, args=(), daemon=True)
|
||||||
qq_bot_thread.start()
|
qq_bot_thread.start()
|
||||||
finally:
|
finally:
|
||||||
|
# 判断若是Windows,输出选择模式可能会暂停程序的警告
|
||||||
|
if os.name == 'nt':
|
||||||
|
time.sleep(2)
|
||||||
|
logging.info("您正在使用Windows系统,若命令行窗口处于“选择”模式,程序可能会被暂停,此时请右键点击窗口空白区域使其取消选择模式。")
|
||||||
|
|
||||||
time.sleep(12)
|
time.sleep(12)
|
||||||
if first_time_init:
|
if first_time_init:
|
||||||
if not known_exception_caught:
|
if not known_exception_caught:
|
||||||
@@ -270,24 +307,22 @@ def main(first_time_init=False):
|
|||||||
import pkg.utils.updater
|
import pkg.utils.updater
|
||||||
try:
|
try:
|
||||||
if pkg.utils.updater.is_new_version_available():
|
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:
|
else:
|
||||||
logging.info("当前已是最新版本")
|
logging.info("当前已是最新版本")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning("检查更新失败:{}".format(e))
|
logging.warning("检查更新失败:{}".format(e))
|
||||||
|
|
||||||
while True:
|
try:
|
||||||
try:
|
import pkg.utils.announcement as announcement
|
||||||
time.sleep(10)
|
new_announcement = announcement.fetch_new()
|
||||||
if qqbot != pkg.utils.context.get_qqbot_manager(): # 已经reload了
|
if new_announcement != "":
|
||||||
logging.info("以前的main流程由于reload退出")
|
logging.critical("[公告] {}".format(new_announcement))
|
||||||
break
|
except Exception as e:
|
||||||
except KeyboardInterrupt:
|
logging.warning("获取公告失败:{}".format(e))
|
||||||
stop()
|
|
||||||
|
|
||||||
print("程序退出")
|
return qqbot
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
def stop():
|
def stop():
|
||||||
@@ -325,6 +360,10 @@ if __name__ == '__main__':
|
|||||||
if not os.path.exists("sensitive.json"):
|
if not os.path.exists("sensitive.json"):
|
||||||
shutil.copy("sensitive-template.json", "sensitive.json")
|
shutil.copy("sensitive-template.json", "sensitive.json")
|
||||||
|
|
||||||
|
# 检查是否有scenario/default.json
|
||||||
|
if not os.path.exists("scenario/default.json"):
|
||||||
|
shutil.copy("scenario/default-template.json", "scenario/default.json")
|
||||||
|
|
||||||
# 检查temp目录
|
# 检查temp目录
|
||||||
if not os.path.exists("temp/"):
|
if not os.path.exists("temp/"):
|
||||||
os.mkdir("temp/")
|
os.mkdir("temp/")
|
||||||
@@ -340,24 +379,21 @@ if __name__ == '__main__':
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
elif len(sys.argv) > 1 and sys.argv[1] == 'update':
|
elif len(sys.argv) > 1 and sys.argv[1] == 'update':
|
||||||
try:
|
print("正在进行程序更新...")
|
||||||
try:
|
import pkg.utils.updater as updater
|
||||||
import pkg.utils.pkgmgr
|
updater.update_all(cli=True)
|
||||||
pkg.utils.pkgmgr.ensure_dulwich()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from dulwich import porcelain
|
|
||||||
|
|
||||||
repo = porcelain.open_repo('.')
|
|
||||||
porcelain.pull(repo)
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
print("dulwich模块未安装,请查看 https://github.com/RockChinQ/QChatGPT/issues/77")
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# import pkg.utils.configmgr
|
|
||||||
#
|
|
||||||
# pkg.utils.configmgr.set_config_and_reload("quote_origin", False)
|
|
||||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||||
|
|
||||||
main(True)
|
qqbot = main(True)
|
||||||
|
|
||||||
|
import pkg.utils.context
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
time.sleep(10)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
stop()
|
||||||
|
|
||||||
|
print("程序退出")
|
||||||
|
sys.exit(0)
|
||||||
|
|||||||
75
override-all.json
Normal file
75
override-all.json
Normal 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"
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ class DatabaseManager:
|
|||||||
|
|
||||||
def __execute__(self, *args, **kwargs) -> Cursor:
|
def __execute__(self, *args, **kwargs) -> Cursor:
|
||||||
# logging.debug('SQL: {}'.format(sql))
|
# logging.debug('SQL: {}'.format(sql))
|
||||||
|
logging.debug('SQL: {}'.format(args))
|
||||||
c = self.cursor.execute(*args, **kwargs)
|
c = self.cursor.execute(*args, **kwargs)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
return c
|
return c
|
||||||
@@ -52,10 +53,30 @@ class DatabaseManager:
|
|||||||
`create_timestamp` bigint not null,
|
`create_timestamp` bigint not null,
|
||||||
`last_interact_timestamp` bigint not null,
|
`last_interact_timestamp` bigint not null,
|
||||||
`status` varchar(255) not null default 'on_going',
|
`status` varchar(255) not null default 'on_going',
|
||||||
`prompt` text not null
|
`default_prompt` text not null default '',
|
||||||
|
`prompt` text not null,
|
||||||
|
`token_counts` text not null default '[]'
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
# 检查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__("""
|
self.__execute__("""
|
||||||
create table if not exists `account_fee`(
|
create table if not exists `account_fee`(
|
||||||
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
|
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -75,7 +96,7 @@ class DatabaseManager:
|
|||||||
|
|
||||||
# session持久化
|
# session持久化
|
||||||
def persistence_session(self, subject_type: str, subject_number: int, create_timestamp: int,
|
def persistence_session(self, subject_type: str, subject_number: int, create_timestamp: int,
|
||||||
last_interact_timestamp: int, prompt: str):
|
last_interact_timestamp: int, prompt: str, default_prompt: str = '', token_counts: str = ''):
|
||||||
"""持久化指定session"""
|
"""持久化指定session"""
|
||||||
|
|
||||||
# 检查是否已经有了此name和create_timestamp的session
|
# 检查是否已经有了此name和create_timestamp的session
|
||||||
@@ -88,20 +109,20 @@ class DatabaseManager:
|
|||||||
if count == 0:
|
if count == 0:
|
||||||
|
|
||||||
sql = """
|
sql = """
|
||||||
insert into `sessions` (`name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`)
|
insert into `sessions` (`name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `default_prompt`, `token_counts`)
|
||||||
values (?, ?, ?, ?, ?, ?)
|
values (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.__execute__(sql,
|
self.__execute__(sql,
|
||||||
("{}_{}".format(subject_type, subject_number), subject_type, subject_number, create_timestamp,
|
("{}_{}".format(subject_type, subject_number), subject_type, subject_number, create_timestamp,
|
||||||
last_interact_timestamp, prompt))
|
last_interact_timestamp, prompt, default_prompt, token_counts))
|
||||||
else:
|
else:
|
||||||
sql = """
|
sql = """
|
||||||
update `sessions` set `last_interact_timestamp` = ?, `prompt` = ?
|
update `sessions` set `last_interact_timestamp` = ?, `prompt` = ?, `token_counts` = ?
|
||||||
where `type` = ? and `number` = ? and `create_timestamp` = ?
|
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))
|
subject_number, create_timestamp))
|
||||||
|
|
||||||
# 显式关闭一个session
|
# 显式关闭一个session
|
||||||
@@ -126,7 +147,7 @@ class DatabaseManager:
|
|||||||
# 从数据库中加载所有还没过期的session
|
# 从数据库中加载所有还没过期的session
|
||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
self.__execute__("""
|
self.__execute__("""
|
||||||
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`
|
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`, `default_prompt`, `token_counts`
|
||||||
from `sessions` where `last_interact_timestamp` > {}
|
from `sessions` where `last_interact_timestamp` > {}
|
||||||
""".format(int(time.time()) - config.session_expire_time))
|
""".format(int(time.time()) - config.session_expire_time))
|
||||||
results = self.cursor.fetchall()
|
results = self.cursor.fetchall()
|
||||||
@@ -139,6 +160,8 @@ class DatabaseManager:
|
|||||||
last_interact_timestamp = result[4]
|
last_interact_timestamp = result[4]
|
||||||
prompt = result[5]
|
prompt = result[5]
|
||||||
status = result[6]
|
status = result[6]
|
||||||
|
default_prompt = result[7]
|
||||||
|
token_counts = result[8]
|
||||||
|
|
||||||
# 当且仅当最后一个该对象的会话是on_going状态时,才会被加载
|
# 当且仅当最后一个该对象的会话是on_going状态时,才会被加载
|
||||||
if status == 'on_going':
|
if status == 'on_going':
|
||||||
@@ -147,7 +170,9 @@ class DatabaseManager:
|
|||||||
'subject_number': subject_number,
|
'subject_number': subject_number,
|
||||||
'create_timestamp': create_timestamp,
|
'create_timestamp': create_timestamp,
|
||||||
'last_interact_timestamp': last_interact_timestamp,
|
'last_interact_timestamp': last_interact_timestamp,
|
||||||
'prompt': prompt
|
'prompt': prompt,
|
||||||
|
'default_prompt': default_prompt,
|
||||||
|
'token_counts': token_counts
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
if session_name in sessions:
|
if session_name in sessions:
|
||||||
@@ -159,7 +184,7 @@ class DatabaseManager:
|
|||||||
def last_session(self, session_name: str, cursor_timestamp: int):
|
def last_session(self, session_name: str, cursor_timestamp: int):
|
||||||
|
|
||||||
self.__execute__("""
|
self.__execute__("""
|
||||||
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`
|
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
|
from `sessions` where `name` = '{}' and `last_interact_timestamp` < {} order by `last_interact_timestamp` desc
|
||||||
limit 1
|
limit 1
|
||||||
""".format(session_name, cursor_timestamp))
|
""".format(session_name, cursor_timestamp))
|
||||||
@@ -175,20 +200,24 @@ class DatabaseManager:
|
|||||||
last_interact_timestamp = result[4]
|
last_interact_timestamp = result[4]
|
||||||
prompt = result[5]
|
prompt = result[5]
|
||||||
status = result[6]
|
status = result[6]
|
||||||
|
default_prompt = result[7]
|
||||||
|
token_counts = result[8]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'subject_type': subject_type,
|
'subject_type': subject_type,
|
||||||
'subject_number': subject_number,
|
'subject_number': subject_number,
|
||||||
'create_timestamp': create_timestamp,
|
'create_timestamp': create_timestamp,
|
||||||
'last_interact_timestamp': last_interact_timestamp,
|
'last_interact_timestamp': last_interact_timestamp,
|
||||||
'prompt': prompt
|
'prompt': prompt,
|
||||||
|
'default_prompt': default_prompt,
|
||||||
|
'token_counts': token_counts
|
||||||
}
|
}
|
||||||
|
|
||||||
# 获取此session_name后一个session的数据
|
# 获取此session_name后一个session的数据
|
||||||
def next_session(self, session_name: str, cursor_timestamp: int):
|
def next_session(self, session_name: str, cursor_timestamp: int):
|
||||||
|
|
||||||
self.__execute__("""
|
self.__execute__("""
|
||||||
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`
|
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
|
from `sessions` where `name` = '{}' and `last_interact_timestamp` > {} order by `last_interact_timestamp` asc
|
||||||
limit 1
|
limit 1
|
||||||
""".format(session_name, cursor_timestamp))
|
""".format(session_name, cursor_timestamp))
|
||||||
@@ -204,19 +233,23 @@ class DatabaseManager:
|
|||||||
last_interact_timestamp = result[4]
|
last_interact_timestamp = result[4]
|
||||||
prompt = result[5]
|
prompt = result[5]
|
||||||
status = result[6]
|
status = result[6]
|
||||||
|
default_prompt = result[7]
|
||||||
|
token_counts = result[8]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'subject_type': subject_type,
|
'subject_type': subject_type,
|
||||||
'subject_number': subject_number,
|
'subject_number': subject_number,
|
||||||
'create_timestamp': create_timestamp,
|
'create_timestamp': create_timestamp,
|
||||||
'last_interact_timestamp': last_interact_timestamp,
|
'last_interact_timestamp': last_interact_timestamp,
|
||||||
'prompt': prompt
|
'prompt': prompt,
|
||||||
|
'default_prompt': default_prompt,
|
||||||
|
'token_counts': token_counts
|
||||||
}
|
}
|
||||||
|
|
||||||
# 列出与某个对象的所有对话session
|
# 列出与某个对象的所有对话session
|
||||||
def list_history(self, session_name: str, capacity: int, page: int):
|
def list_history(self, session_name: str, capacity: int, page: int):
|
||||||
self.__execute__("""
|
self.__execute__("""
|
||||||
select `name`, `type`, `number`, `create_timestamp`, `last_interact_timestamp`, `prompt`, `status`
|
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 {}
|
from `sessions` where `name` = '{}' order by `last_interact_timestamp` desc limit {} offset {}
|
||||||
""".format(session_name, capacity, capacity * page))
|
""".format(session_name, capacity, capacity * page))
|
||||||
results = self.cursor.fetchall()
|
results = self.cursor.fetchall()
|
||||||
@@ -229,17 +262,42 @@ class DatabaseManager:
|
|||||||
last_interact_timestamp = result[4]
|
last_interact_timestamp = result[4]
|
||||||
prompt = result[5]
|
prompt = result[5]
|
||||||
status = result[6]
|
status = result[6]
|
||||||
|
default_prompt = result[7]
|
||||||
|
token_counts = result[8]
|
||||||
|
|
||||||
sessions.append({
|
sessions.append({
|
||||||
'subject_type': subject_type,
|
'subject_type': subject_type,
|
||||||
'subject_number': subject_number,
|
'subject_number': subject_number,
|
||||||
'create_timestamp': create_timestamp,
|
'create_timestamp': create_timestamp,
|
||||||
'last_interact_timestamp': last_interact_timestamp,
|
'last_interact_timestamp': last_interact_timestamp,
|
||||||
'prompt': prompt
|
'prompt': prompt,
|
||||||
|
'default_prompt': default_prompt,
|
||||||
|
'token_counts': token_counts
|
||||||
})
|
})
|
||||||
|
|
||||||
return sessions
|
return sessions
|
||||||
|
|
||||||
|
def delete_history(self, session_name: str, index: int) -> bool:
|
||||||
|
# 删除倒序第index个session
|
||||||
|
# 查找其id再删除
|
||||||
|
self.__execute__("""
|
||||||
|
delete from `sessions` where `id` in (select `id` from `sessions` where `name` = '{}' order by `last_interact_timestamp` desc limit 1 offset {})
|
||||||
|
""".format(session_name, index))
|
||||||
|
|
||||||
|
return self.cursor.rowcount == 1
|
||||||
|
|
||||||
|
def delete_all_history(self, session_name: str) -> bool:
|
||||||
|
self.__execute__("""
|
||||||
|
delete from `sessions` where `name` = '{}'
|
||||||
|
""".format(session_name))
|
||||||
|
return self.cursor.rowcount > 0
|
||||||
|
|
||||||
|
def delete_all_session_history(self) -> bool:
|
||||||
|
self.__execute__("""
|
||||||
|
delete from `sessions`
|
||||||
|
""")
|
||||||
|
return self.cursor.rowcount > 0
|
||||||
|
|
||||||
# 将apikey的使用量存进数据库
|
# 将apikey的使用量存进数据库
|
||||||
def dump_api_key_usage(self, api_keys: dict, usage: dict):
|
def dump_api_key_usage(self, api_keys: dict, usage: dict):
|
||||||
logging.debug('dumping api key usage...')
|
logging.debug('dumping api key usage...')
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
# 多情景预设值管理
|
# 多情景预设值管理
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
__current__ = "default"
|
__current__ = "default"
|
||||||
"""当前默认使用的情景预设的名称
|
"""当前默认使用的情景预设的名称
|
||||||
@@ -9,8 +11,10 @@ __current__ = "default"
|
|||||||
__prompts_from_files__ = {}
|
__prompts_from_files__ = {}
|
||||||
"""从文件中读取的情景预设值"""
|
"""从文件中读取的情景预设值"""
|
||||||
|
|
||||||
|
__scenario_from_files__ = {}
|
||||||
|
|
||||||
def read_prompt_from_file() -> str:
|
|
||||||
|
def read_prompt_from_file():
|
||||||
"""从文件读取预设值"""
|
"""从文件读取预设值"""
|
||||||
# 读取prompts/目录下的所有文件,以文件名为键,文件内容为值
|
# 读取prompts/目录下的所有文件,以文件名为键,文件内容为值
|
||||||
# 保存在__prompts_from_files__中
|
# 保存在__prompts_from_files__中
|
||||||
@@ -23,6 +27,19 @@ def read_prompt_from_file() -> str:
|
|||||||
__prompts_from_files__[file] = f.read()
|
__prompts_from_files__[file] = f.read()
|
||||||
|
|
||||||
|
|
||||||
|
def read_scenario_from_file():
|
||||||
|
"""从JSON文件读取情景预设"""
|
||||||
|
global __scenario_from_files__
|
||||||
|
import os
|
||||||
|
|
||||||
|
__scenario_from_files__ = {}
|
||||||
|
for file in os.listdir("scenario"):
|
||||||
|
if file == "default-template.json":
|
||||||
|
continue
|
||||||
|
with open(os.path.join("scenario", file), encoding="utf-8") as f:
|
||||||
|
__scenario_from_files__[file] = json.load(f)
|
||||||
|
|
||||||
|
|
||||||
def get_prompt_dict() -> dict:
|
def get_prompt_dict() -> dict:
|
||||||
"""获取预设值字典"""
|
"""获取预设值字典"""
|
||||||
import config
|
import config
|
||||||
@@ -65,15 +82,40 @@ def set_to_default():
|
|||||||
__current__ = list(default_dict.keys())[0]
|
__current__ = list(default_dict.keys())[0]
|
||||||
|
|
||||||
|
|
||||||
def get_prompt(name: str = None) -> str:
|
def get_prompt(name: str = None) -> list:
|
||||||
|
global __scenario_from_files__
|
||||||
|
import config
|
||||||
|
preset_mode = config.preset_mode
|
||||||
|
|
||||||
"""获取预设值"""
|
"""获取预设值"""
|
||||||
if name is None:
|
if name is None:
|
||||||
name = get_current()
|
name = get_current()
|
||||||
|
|
||||||
default_dict = get_prompt_dict()
|
# JSON预设方式
|
||||||
|
if preset_mode == 'full_scenario':
|
||||||
|
import os
|
||||||
|
|
||||||
for key in default_dict:
|
for key in __scenario_from_files__:
|
||||||
if key.lower().startswith(name.lower()):
|
if key.lower().startswith(name.lower()):
|
||||||
return default_dict[key]
|
logging.debug('成功加载情景预设从JSON文件: {}'.format(key))
|
||||||
|
return __scenario_from_files__[key]['prompt']
|
||||||
|
|
||||||
|
# 默认预设方式
|
||||||
|
elif preset_mode == 'default':
|
||||||
|
|
||||||
raise KeyError("未找到情景预设: " + name)
|
default_dict = get_prompt_dict()
|
||||||
|
|
||||||
|
for key in default_dict:
|
||||||
|
if key.lower().startswith(name.lower()):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": default_dict[key]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "好的。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
raise KeyError("未找到默认情景预设: " + name)
|
||||||
|
|||||||
@@ -88,4 +88,4 @@ class KeysManager:
|
|||||||
for key_name in self.api_key:
|
for key_name in self.api_key:
|
||||||
if self.api_key[key_name] == api_key:
|
if self.api_key[key_name] == api_key:
|
||||||
return key_name
|
return key_name
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class OpenAIInteract:
|
|||||||
pkg.utils.context.set_openai_manager(self)
|
pkg.utils.context.set_openai_manager(self)
|
||||||
|
|
||||||
# 请求OpenAI Completion
|
# 请求OpenAI Completion
|
||||||
def request_completion(self, prompts) -> str:
|
def request_completion(self, prompts) -> tuple[str, int]:
|
||||||
"""请求补全接口回复
|
"""请求补全接口回复
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
@@ -60,14 +60,18 @@ class OpenAIInteract:
|
|||||||
|
|
||||||
logging.debug("OpenAI response: %s", response)
|
logging.debug("OpenAI response: %s", response)
|
||||||
|
|
||||||
|
# 记录使用量
|
||||||
|
current_round_token = 0
|
||||||
if 'model' in config.completion_api_params:
|
if 'model' in config.completion_api_params:
|
||||||
self.audit_mgr.report_text_model_usage(config.completion_api_params['model'],
|
self.audit_mgr.report_text_model_usage(config.completion_api_params['model'],
|
||||||
ai.get_total_tokens())
|
ai.get_total_tokens())
|
||||||
|
current_round_token = ai.get_total_tokens()
|
||||||
elif 'engine' in config.completion_api_params:
|
elif 'engine' in config.completion_api_params:
|
||||||
self.audit_mgr.report_text_model_usage(config.completion_api_params['engine'],
|
self.audit_mgr.report_text_model_usage(config.completion_api_params['engine'],
|
||||||
response['usage']['total_tokens'])
|
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:
|
def request_image(self, prompt) -> dict:
|
||||||
"""请求图片接口回复
|
"""请求图片接口回复
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ COMPLETION_MODELS = {
|
|||||||
CHAT_COMPLETION_MODELS = {
|
CHAT_COMPLETION_MODELS = {
|
||||||
'gpt-3.5-turbo',
|
'gpt-3.5-turbo',
|
||||||
'gpt-3.5-turbo-0301',
|
'gpt-3.5-turbo-0301',
|
||||||
|
'gpt-4',
|
||||||
|
'gpt-4-0314',
|
||||||
|
'gpt-4-32k',
|
||||||
|
'gpt-4-32k-0314'
|
||||||
}
|
}
|
||||||
|
|
||||||
EDIT_MODELS = {
|
EDIT_MODELS = {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ def reset_session_prompt(session_name, prompt):
|
|||||||
prompt = [
|
prompt = [
|
||||||
{
|
{
|
||||||
'role': 'system',
|
'role': 'system',
|
||||||
'content': config.default_prompt['default']
|
'content': config.default_prompt['default'] if type(config.default_prompt) == dict else config.default_prompt
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
# 警告
|
# 警告
|
||||||
@@ -72,9 +72,12 @@ def load_sessions():
|
|||||||
temp_session.last_interact_timestamp = session_data[session_name]['last_interact_timestamp']
|
temp_session.last_interact_timestamp = session_data[session_name]['last_interact_timestamp']
|
||||||
try:
|
try:
|
||||||
temp_session.prompt = json.loads(session_data[session_name]['prompt'])
|
temp_session.prompt = json.loads(session_data[session_name]['prompt'])
|
||||||
|
temp_session.token_counts = json.loads(session_data[session_name]['token_counts'])
|
||||||
except Exception:
|
except Exception:
|
||||||
temp_session.prompt = reset_session_prompt(session_name, session_data[session_name]['prompt'])
|
temp_session.prompt = reset_session_prompt(session_name, session_data[session_name]['prompt'])
|
||||||
temp_session.persistence()
|
temp_session.persistence()
|
||||||
|
temp_session.default_prompt = json.loads(session_data[session_name]['default_prompt']) if \
|
||||||
|
session_data[session_name]['default_prompt'] else []
|
||||||
|
|
||||||
sessions[session_name] = temp_session
|
sessions[session_name] = temp_session
|
||||||
|
|
||||||
@@ -104,6 +107,12 @@ class Session:
|
|||||||
prompt = []
|
prompt = []
|
||||||
"""使用list来保存会话中的回合"""
|
"""使用list来保存会话中的回合"""
|
||||||
|
|
||||||
|
token_counts = []
|
||||||
|
"""每个回合的token数量"""
|
||||||
|
|
||||||
|
default_prompt = []
|
||||||
|
"""本session的默认prompt"""
|
||||||
|
|
||||||
create_timestamp = 0
|
create_timestamp = 0
|
||||||
"""会话创建时间"""
|
"""会话创建时间"""
|
||||||
|
|
||||||
@@ -129,33 +138,26 @@ class Session:
|
|||||||
|
|
||||||
# 从配置文件获取会话预设信息
|
# 从配置文件获取会话预设信息
|
||||||
def get_default_prompt(self, use_default: str = None):
|
def get_default_prompt(self, use_default: str = None):
|
||||||
config = pkg.utils.context.get_config()
|
|
||||||
|
|
||||||
import pkg.openai.dprompt as dprompt
|
import pkg.openai.dprompt as dprompt
|
||||||
|
|
||||||
if use_default is None:
|
if use_default is None:
|
||||||
current_default_prompt = dprompt.get_prompt(dprompt.get_current())
|
use_default = dprompt.get_current()
|
||||||
else:
|
|
||||||
current_default_prompt = dprompt.get_prompt(use_default)
|
|
||||||
|
|
||||||
return [
|
current_default_prompt = dprompt.get_prompt(use_default)
|
||||||
{
|
return current_default_prompt
|
||||||
'role': 'user',
|
|
||||||
'content': current_default_prompt
|
|
||||||
}, {
|
|
||||||
'role': 'assistant',
|
|
||||||
'content': 'ok'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, name: str):
|
def __init__(self, name: str):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.create_timestamp = int(time.time())
|
self.create_timestamp = int(time.time())
|
||||||
self.last_interact_timestamp = int(time.time())
|
self.last_interact_timestamp = int(time.time())
|
||||||
|
self.prompt = []
|
||||||
|
self.token_counts = []
|
||||||
self.schedule()
|
self.schedule()
|
||||||
|
|
||||||
self.response_lock = threading.Lock()
|
self.response_lock = threading.Lock()
|
||||||
self.prompt = self.get_default_prompt()
|
|
||||||
|
self.default_prompt = self.get_default_prompt()
|
||||||
|
logging.debug("prompt is: {}".format(self.default_prompt))
|
||||||
|
|
||||||
# 设定检查session最后一次对话是否超过过期时间的计时器
|
# 设定检查session最后一次对话是否超过过期时间的计时器
|
||||||
def schedule(self):
|
def schedule(self):
|
||||||
@@ -199,11 +201,11 @@ class Session:
|
|||||||
self.last_interact_timestamp = int(time.time())
|
self.last_interact_timestamp = int(time.time())
|
||||||
|
|
||||||
# 触发插件事件
|
# 触发插件事件
|
||||||
if self.prompt == self.get_default_prompt():
|
if not self.prompt:
|
||||||
args = {
|
args = {
|
||||||
'session_name': self.name,
|
'session_name': self.name,
|
||||||
'session': self,
|
'session': self,
|
||||||
'default_prompt': self.prompt,
|
'default_prompt': self.default_prompt,
|
||||||
}
|
}
|
||||||
|
|
||||||
event = pkg.plugin.host.emit(plugin_models.SessionFirstMessageReceived, **args)
|
event = pkg.plugin.host.emit(plugin_models.SessionFirstMessageReceived, **args)
|
||||||
@@ -213,9 +215,16 @@ class Session:
|
|||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
max_length = config.prompt_submit_length if hasattr(config, "prompt_submit_length") else 1024
|
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请求补全
|
# 向API请求补全
|
||||||
message = pkg.utils.context.get_openai_manager().request_completion(
|
message, total_token = pkg.utils.context.get_openai_manager().request_completion(
|
||||||
self.cut_out(text, max_length),
|
prompts,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 成功获取,处理回复
|
# 成功获取,处理回复
|
||||||
@@ -232,6 +241,10 @@ class Session:
|
|||||||
self.prompt.append({'role': 'user', 'content': text})
|
self.prompt.append({'role': 'user', 'content': text})
|
||||||
self.prompt.append({'role': 'assistant', 'content': res_ans})
|
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:
|
if self.just_switched_to_exist_session:
|
||||||
self.just_switched_to_exist_session = False
|
self.just_switched_to_exist_session = False
|
||||||
self.set_ongoing()
|
self.set_ongoing()
|
||||||
@@ -248,35 +261,65 @@ class Session:
|
|||||||
|
|
||||||
question = self.prompt[-2]['content']
|
question = self.prompt[-2]['content']
|
||||||
self.prompt = self.prompt[:-2]
|
self.prompt = self.prompt[:-2]
|
||||||
|
self.token_counts = self.token_counts[:-1]
|
||||||
|
|
||||||
# 返回上一回合的问题
|
# 返回上一回合的问题
|
||||||
return question
|
return question
|
||||||
|
|
||||||
# 构建对话体
|
# 构建对话体
|
||||||
def cut_out(self, msg: str, max_tokens: int) -> list:
|
def cut_out(self, msg: str, max_tokens: int) -> tuple[list, list]:
|
||||||
"""将现有prompt进行切割处理,使得新的prompt长度不超过max_tokens"""
|
"""将现有prompt进行切割处理,使得新的prompt长度不超过max_tokens
|
||||||
# 如果用户消息长度超过max_tokens,直接返回
|
|
||||||
|
|
||||||
temp_prompt = [
|
: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',
|
'role': 'user',
|
||||||
'content': msg
|
'content': msg
|
||||||
}
|
}
|
||||||
]
|
)
|
||||||
|
|
||||||
token_count = len(msg)
|
logging.debug('cut_out: {}\nchangable section tokens: {}\npacked counts: {}\nsession counts: {}'.format(json.dumps(result_prompt, ensure_ascii=False, indent=4),
|
||||||
# 倒序遍历prompt
|
packed_tokens,
|
||||||
for i in range(len(self.prompt) - 1, -1, -1):
|
changable_counts,
|
||||||
if token_count >= max_tokens:
|
self.token_counts))
|
||||||
break
|
|
||||||
|
|
||||||
# 将prompt加到temp_prompt头部
|
return result_prompt, changable_counts
|
||||||
temp_prompt.insert(0, self.prompt[i])
|
|
||||||
token_count += len(self.prompt[i]['content'])
|
|
||||||
|
|
||||||
logging.debug('cut_out: {}'.format(str(temp_prompt)))
|
|
||||||
|
|
||||||
return temp_prompt
|
|
||||||
|
|
||||||
# 持久化session
|
# 持久化session
|
||||||
def persistence(self):
|
def persistence(self):
|
||||||
@@ -291,11 +334,11 @@ class Session:
|
|||||||
subject_number = int(name_spt[1])
|
subject_number = int(name_spt[1])
|
||||||
|
|
||||||
db_inst.persistence_session(subject_type, subject_number, self.create_timestamp, self.last_interact_timestamp,
|
db_inst.persistence_session(subject_type, subject_number, self.create_timestamp, self.last_interact_timestamp,
|
||||||
json.dumps(self.prompt))
|
json.dumps(self.prompt), json.dumps(self.default_prompt), json.dumps(self.token_counts))
|
||||||
|
|
||||||
# 重置session
|
# 重置session
|
||||||
def reset(self, explicit: bool = False, expired: bool = False, schedule_new: bool = True, use_prompt: str = None):
|
def reset(self, explicit: bool = False, expired: bool = False, schedule_new: bool = True, use_prompt: str = None):
|
||||||
if self.prompt[-1]['role'] != "system":
|
if self.prompt:
|
||||||
self.persistence()
|
self.persistence()
|
||||||
if explicit:
|
if explicit:
|
||||||
# 触发插件事件
|
# 触发插件事件
|
||||||
@@ -311,7 +354,10 @@ class Session:
|
|||||||
|
|
||||||
if expired:
|
if expired:
|
||||||
pkg.utils.context.get_database_manager().set_session_expired(self.name, self.create_timestamp)
|
pkg.utils.context.get_database_manager().set_session_expired(self.name, self.create_timestamp)
|
||||||
self.prompt = self.get_default_prompt(use_prompt)
|
|
||||||
|
self.default_prompt = self.get_default_prompt(use_prompt)
|
||||||
|
self.prompt = []
|
||||||
|
self.token_counts = []
|
||||||
self.create_timestamp = int(time.time())
|
self.create_timestamp = int(time.time())
|
||||||
self.last_interact_timestamp = int(time.time())
|
self.last_interact_timestamp = int(time.time())
|
||||||
self.just_switched_to_exist_session = False
|
self.just_switched_to_exist_session = False
|
||||||
@@ -337,9 +383,11 @@ class Session:
|
|||||||
self.last_interact_timestamp = last_one['last_interact_timestamp']
|
self.last_interact_timestamp = last_one['last_interact_timestamp']
|
||||||
try:
|
try:
|
||||||
self.prompt = json.loads(last_one['prompt'])
|
self.prompt = json.loads(last_one['prompt'])
|
||||||
|
self.token_counts = json.loads(last_one['token_counts'])
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
self.prompt = reset_session_prompt(self.name, last_one['prompt'])
|
self.prompt = reset_session_prompt(self.name, last_one['prompt'])
|
||||||
self.persistence()
|
self.persistence()
|
||||||
|
self.default_prompt = json.loads(last_one['default_prompt']) if last_one['default_prompt'] else []
|
||||||
|
|
||||||
self.just_switched_to_exist_session = True
|
self.just_switched_to_exist_session = True
|
||||||
return self
|
return self
|
||||||
@@ -356,9 +404,11 @@ class Session:
|
|||||||
self.last_interact_timestamp = next_one['last_interact_timestamp']
|
self.last_interact_timestamp = next_one['last_interact_timestamp']
|
||||||
try:
|
try:
|
||||||
self.prompt = json.loads(next_one['prompt'])
|
self.prompt = json.loads(next_one['prompt'])
|
||||||
|
self.token_counts = json.loads(next_one['token_counts'])
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
self.prompt = reset_session_prompt(self.name, next_one['prompt'])
|
self.prompt = reset_session_prompt(self.name, next_one['prompt'])
|
||||||
self.persistence()
|
self.persistence()
|
||||||
|
self.default_prompt = json.loads(next_one['default_prompt']) if next_one['default_prompt'] else []
|
||||||
|
|
||||||
self.just_switched_to_exist_session = True
|
self.just_switched_to_exist_session = True
|
||||||
return self
|
return self
|
||||||
@@ -366,5 +416,11 @@ class Session:
|
|||||||
def list_history(self, capacity: int = 10, page: int = 0):
|
def list_history(self, capacity: int = 10, page: int = 0):
|
||||||
return pkg.utils.context.get_database_manager().list_history(self.name, capacity, page)
|
return pkg.utils.context.get_database_manager().list_history(self.name, capacity, page)
|
||||||
|
|
||||||
|
def delete_history(self, index: int) -> bool:
|
||||||
|
return pkg.utils.context.get_database_manager().delete_history(self.name, index)
|
||||||
|
|
||||||
|
def delete_all_history(self) -> bool:
|
||||||
|
return pkg.utils.context.get_database_manager().delete_all_history(self.name)
|
||||||
|
|
||||||
def draw_image(self, prompt: str):
|
def draw_image(self, prompt: str):
|
||||||
return pkg.utils.context.get_openai_manager().request_image(prompt)
|
return pkg.utils.context.get_openai_manager().request_image(prompt)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
# 长消息处理相关
|
# 长消息处理相关
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import base64
|
import base64
|
||||||
@@ -67,7 +66,7 @@ def check_text(text: str) -> list:
|
|||||||
"""检查文本是否为长消息,并转换成该使用的消息链组件"""
|
"""检查文本是否为长消息,并转换成该使用的消息链组件"""
|
||||||
if not hasattr(config, 'blob_message_threshold'):
|
if not hasattr(config, 'blob_message_threshold'):
|
||||||
return [text]
|
return [text]
|
||||||
|
|
||||||
if len(text) > config.blob_message_threshold:
|
if len(text) > config.blob_message_threshold:
|
||||||
if not hasattr(config, 'blob_message_strategy'):
|
if not hasattr(config, 'blob_message_strategy'):
|
||||||
raise AttributeError('未定义长消息处理策略')
|
raise AttributeError('未定义长消息处理策略')
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import json
|
|||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
import traceback
|
||||||
|
|
||||||
import pkg.openai.session
|
import pkg.openai.session
|
||||||
import pkg.openai.manager
|
import pkg.openai.manager
|
||||||
@@ -234,7 +235,7 @@ def process_command(session_name: str, text_message: str, mgr, config,
|
|||||||
if len(msg) >= 2:
|
if len(msg) >= 2:
|
||||||
reply_str += "#{} 创建:{} {}\n".format(i + page * 10,
|
reply_str += "#{} 创建:{} {}\n".format(i + page * 10,
|
||||||
datetime_obj.strftime("%Y-%m-%d %H:%M:%S"),
|
datetime_obj.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
msg[1]['content'])
|
msg[0]['content'])
|
||||||
else:
|
else:
|
||||||
reply_str += "#{} 创建:{} {}\n".format(i + page * 10,
|
reply_str += "#{} 创建:{} {}\n".format(i + page * 10,
|
||||||
datetime_obj.strftime("%Y-%m-%d %H:%M:%S"),
|
datetime_obj.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
@@ -256,6 +257,20 @@ def process_command(session_name: str, text_message: str, mgr, config,
|
|||||||
|
|
||||||
reply = pkg.qqbot.message.process_normal_message(to_send, mgr, config,
|
reply = pkg.qqbot.message.process_normal_message(to_send, mgr, config,
|
||||||
launcher_type, launcher_id, sender_id)
|
launcher_type, launcher_id, sender_id)
|
||||||
|
elif cmd == 'del': # 删除指定会话历史记录
|
||||||
|
if len(params) == 0:
|
||||||
|
reply = ["[bot]参数不足, 格式: !del <序号>\n可以通过!list查看序号"]
|
||||||
|
else:
|
||||||
|
if params[0] == 'all':
|
||||||
|
pkg.openai.session.get_session(session_name).delete_all_history()
|
||||||
|
reply = ["[bot]已删除所有历史会话"]
|
||||||
|
elif params[0].isdigit():
|
||||||
|
if pkg.openai.session.get_session(session_name).delete_history(int(params[0])):
|
||||||
|
reply = ["[bot]已删除历史会话 #{}".format(params[0])]
|
||||||
|
else:
|
||||||
|
reply = ["[bot]没有历史会话 #{}".format(params[0])]
|
||||||
|
else:
|
||||||
|
reply = ["[bot]参数错误, 格式: !del <序号>\n可以通过!list查看序号"]
|
||||||
elif cmd == 'usage':
|
elif cmd == 'usage':
|
||||||
reply_str = "[bot]各api-key使用情况:\n\n"
|
reply_str = "[bot]各api-key使用情况:\n\n"
|
||||||
|
|
||||||
@@ -269,7 +284,8 @@ def process_command(session_name: str, text_message: str, mgr, config,
|
|||||||
int(image_count))
|
int(image_count))
|
||||||
# 获取此key的额度
|
# 获取此key的额度
|
||||||
try:
|
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'])
|
reply_str += " - 使用额度:{:.2f}/{:.2f}\n".format(credit_data['total_used'],credit_data['total_granted'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning("获取额度失败:{}".format(e))
|
logging.warning("获取额度失败:{}".format(e))
|
||||||
@@ -322,6 +338,18 @@ def process_command(session_name: str, text_message: str, mgr, config,
|
|||||||
reply = ["[bot]err: 未找到情景预设:{}".format(params[0])]
|
reply = ["[bot]err: 未找到情景预设:{}".format(params[0])]
|
||||||
else:
|
else:
|
||||||
reply = ["[bot]err: 仅管理员可设置默认情景预设"]
|
reply = ["[bot]err: 仅管理员可设置默认情景预设"]
|
||||||
|
elif cmd == "delhst" and is_admin:
|
||||||
|
if len(params) == 0:
|
||||||
|
reply = ["[bot]err:请输入要删除的会话名: group_<群号> 或者 person_<QQ号>, 或使用 !delhst all 删除所有会话的历史记录"]
|
||||||
|
else:
|
||||||
|
if params[0] == "all":
|
||||||
|
pkg.utils.context.get_database_manager().delete_all_session_history()
|
||||||
|
reply = ["[bot]已删除所有会话的历史记录"]
|
||||||
|
else:
|
||||||
|
if pkg.utils.context.get_database_manager().delete_all_history(params[0]):
|
||||||
|
reply = ["[bot]已删除会话 {} 的所有历史记录".format(params[0])]
|
||||||
|
else:
|
||||||
|
reply = ["[bot]未找到会话 {} 的历史记录".format(params[0])]
|
||||||
elif cmd == 'reload' and is_admin:
|
elif cmd == 'reload' and is_admin:
|
||||||
def reload_task():
|
def reload_task():
|
||||||
pkg.utils.reloader.reload_all()
|
pkg.utils.reloader.reload_all()
|
||||||
@@ -336,6 +364,7 @@ def process_command(session_name: str, text_message: str, mgr, config,
|
|||||||
else:
|
else:
|
||||||
pkg.utils.context.get_qqbot_manager().notify_admin("无新版本")
|
pkg.utils.context.get_qqbot_manager().notify_admin("无新版本")
|
||||||
except Exception as e0:
|
except Exception as e0:
|
||||||
|
traceback.print_exc()
|
||||||
pkg.utils.context.get_qqbot_manager().notify_admin("更新失败:{}".format(e0))
|
pkg.utils.context.get_qqbot_manager().notify_admin("更新失败:{}".format(e0))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import logging
|
|||||||
|
|
||||||
class ReplyFilter:
|
class ReplyFilter:
|
||||||
sensitive_words = []
|
sensitive_words = []
|
||||||
|
mask = "*"
|
||||||
|
mask_word = ""
|
||||||
|
|
||||||
# 默认值( 兼容性考虑 )
|
# 默认值( 兼容性考虑 )
|
||||||
baidu_check = False
|
baidu_check = False
|
||||||
@@ -14,8 +16,10 @@ class ReplyFilter:
|
|||||||
baidu_secret_key = ""
|
baidu_secret_key = ""
|
||||||
inappropriate_message_tips = "[百度云]请珍惜机器人,当前返回内容不合规"
|
inappropriate_message_tips = "[百度云]请珍惜机器人,当前返回内容不合规"
|
||||||
|
|
||||||
def __init__(self, sensitive_words: list):
|
def __init__(self, sensitive_words: list, mask: str = "*", mask_word: str = ""):
|
||||||
self.sensitive_words = sensitive_words
|
self.sensitive_words = sensitive_words
|
||||||
|
self.mask = mask
|
||||||
|
self.mask_word = mask_word
|
||||||
import config
|
import config
|
||||||
if hasattr(config, 'baidu_check') and hasattr(config, 'baidu_api_key') and hasattr(config, 'baidu_secret_key'):
|
if hasattr(config, 'baidu_check') and hasattr(config, 'baidu_api_key') and hasattr(config, 'baidu_secret_key'):
|
||||||
self.baidu_check = config.baidu_check
|
self.baidu_check = config.baidu_check
|
||||||
@@ -36,7 +40,10 @@ class ReplyFilter:
|
|||||||
match = re.findall(word, message)
|
match = re.findall(word, message)
|
||||||
if len(match) > 0:
|
if len(match) > 0:
|
||||||
for i in range(len(match)):
|
for i in range(len(match)):
|
||||||
message = message.replace(match[i], "*" * len(match[i]))
|
if self.mask_word == "":
|
||||||
|
message = message.replace(match[i], self.mask * len(match[i]))
|
||||||
|
else:
|
||||||
|
message = message.replace(match[i], self.mask_word)
|
||||||
|
|
||||||
# 百度云审核
|
# 百度云审核
|
||||||
if self.baidu_check:
|
if self.baidu_check:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
import mirai.models.bus
|
import mirai.models.bus
|
||||||
from mirai import At, GroupMessage, MessageEvent, Mirai, StrangerMessage, WebSocketAdapter, HTTPAdapter, \
|
from mirai import At, GroupMessage, MessageEvent, Mirai, StrangerMessage, WebSocketAdapter, HTTPAdapter, \
|
||||||
@@ -21,12 +22,6 @@ import pkg.plugin.host as plugin_host
|
|||||||
import pkg.plugin.models as plugin_models
|
import pkg.plugin.models as plugin_models
|
||||||
|
|
||||||
|
|
||||||
# 并行运行
|
|
||||||
def go(func, args=()):
|
|
||||||
thread = threading.Thread(target=func, args=args, daemon=True)
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
|
|
||||||
# 检查消息是否符合泛响应匹配机制
|
# 检查消息是否符合泛响应匹配机制
|
||||||
def check_response_rule(text: str):
|
def check_response_rule(text: str):
|
||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
@@ -51,10 +46,29 @@ def check_response_rule(text: str):
|
|||||||
return False, ""
|
return False, ""
|
||||||
|
|
||||||
|
|
||||||
|
def response_at():
|
||||||
|
config = pkg.utils.context.get_config()
|
||||||
|
if 'at' not in config.response_rules:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return config.response_rules['at']
|
||||||
|
|
||||||
|
|
||||||
|
def random_responding():
|
||||||
|
config = pkg.utils.context.get_config()
|
||||||
|
if 'random_rate' in config.response_rules:
|
||||||
|
import random
|
||||||
|
return random.random() < config.response_rules['random_rate']
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# 控制QQ消息输入输出的类
|
# 控制QQ消息输入输出的类
|
||||||
class QQBotManager:
|
class QQBotManager:
|
||||||
retry = 3
|
retry = 3
|
||||||
|
|
||||||
|
#线程池控制
|
||||||
|
pool = None
|
||||||
|
|
||||||
bot: Mirai = None
|
bot: Mirai = None
|
||||||
|
|
||||||
reply_filter = None
|
reply_filter = None
|
||||||
@@ -64,11 +78,14 @@ class QQBotManager:
|
|||||||
ban_person = []
|
ban_person = []
|
||||||
ban_group = []
|
ban_group = []
|
||||||
|
|
||||||
def __init__(self, mirai_http_api_config: dict, timeout: int = 60, retry: int = 3, first_time_init=True):
|
def __init__(self, mirai_http_api_config: dict, timeout: int = 60, retry: int = 3, pool_num: int = 10, first_time_init=True):
|
||||||
|
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.retry = retry
|
self.retry = retry
|
||||||
|
|
||||||
|
self.pool_num = pool_num
|
||||||
|
self.pool = ThreadPoolExecutor(max_workers=self.pool_num)
|
||||||
|
logging.debug("Registered thread pool Size:{}".format(pool_num))
|
||||||
|
|
||||||
# 加载禁用列表
|
# 加载禁用列表
|
||||||
if os.path.exists("banlist.py"):
|
if os.path.exists("banlist.py"):
|
||||||
import banlist
|
import banlist
|
||||||
@@ -82,7 +99,12 @@ class QQBotManager:
|
|||||||
and config.sensitive_word_filter is not None \
|
and config.sensitive_word_filter is not None \
|
||||||
and config.sensitive_word_filter:
|
and config.sensitive_word_filter:
|
||||||
with open("sensitive.json", "r", encoding="utf-8") as f:
|
with open("sensitive.json", "r", encoding="utf-8") as f:
|
||||||
self.reply_filter = pkg.qqbot.filter.ReplyFilter(json.load(f)['words'])
|
sensitive_json = json.load(f)
|
||||||
|
self.reply_filter = pkg.qqbot.filter.ReplyFilter(
|
||||||
|
sensitive_words=sensitive_json['words'],
|
||||||
|
mask=sensitive_json['mask'] if 'mask' in sensitive_json else '*',
|
||||||
|
mask_word=sensitive_json['mask_word'] if 'mask_word' in sensitive_json else ''
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.reply_filter = pkg.qqbot.filter.ReplyFilter([])
|
self.reply_filter = pkg.qqbot.filter.ReplyFilter([])
|
||||||
|
|
||||||
@@ -116,7 +138,7 @@ class QQBotManager:
|
|||||||
|
|
||||||
self.on_person_message(event)
|
self.on_person_message(event)
|
||||||
|
|
||||||
go(friend_message_handler, (event,))
|
self.go(friend_message_handler, event)
|
||||||
|
|
||||||
@self.bot.on(StrangerMessage)
|
@self.bot.on(StrangerMessage)
|
||||||
async def on_stranger_message(event: StrangerMessage):
|
async def on_stranger_message(event: StrangerMessage):
|
||||||
@@ -136,7 +158,7 @@ class QQBotManager:
|
|||||||
|
|
||||||
self.on_person_message(event)
|
self.on_person_message(event)
|
||||||
|
|
||||||
go(stranger_message_handler, (event,))
|
self.go(stranger_message_handler, event)
|
||||||
|
|
||||||
@self.bot.on(GroupMessage)
|
@self.bot.on(GroupMessage)
|
||||||
async def on_group_message(event: GroupMessage):
|
async def on_group_message(event: GroupMessage):
|
||||||
@@ -156,7 +178,7 @@ class QQBotManager:
|
|||||||
|
|
||||||
self.on_group_message(event)
|
self.on_group_message(event)
|
||||||
|
|
||||||
go(group_message_handler, (event,))
|
self.go(group_message_handler, event)
|
||||||
|
|
||||||
def unsubscribe_all():
|
def unsubscribe_all():
|
||||||
"""取消所有订阅
|
"""取消所有订阅
|
||||||
@@ -173,6 +195,9 @@ class QQBotManager:
|
|||||||
|
|
||||||
self.unsubscribe_all = unsubscribe_all
|
self.unsubscribe_all = unsubscribe_all
|
||||||
|
|
||||||
|
def go(self, func, *args, **kwargs):
|
||||||
|
self.pool.submit(func, *args, **kwargs)
|
||||||
|
|
||||||
def first_time_init(self, mirai_http_api_config: dict):
|
def first_time_init(self, mirai_http_api_config: dict):
|
||||||
"""热重载后不再运行此函数"""
|
"""热重载后不再运行此函数"""
|
||||||
|
|
||||||
@@ -288,14 +313,19 @@ class QQBotManager:
|
|||||||
|
|
||||||
if Image in event.message_chain:
|
if Image in event.message_chain:
|
||||||
pass
|
pass
|
||||||
elif At(self.bot.qq) not in event.message_chain:
|
|
||||||
check, result = check_response_rule(str(event.message_chain).strip())
|
|
||||||
|
|
||||||
if check:
|
|
||||||
reply = process(result.strip())
|
|
||||||
else:
|
else:
|
||||||
# 直接调用
|
if At(self.bot.qq) in event.message_chain and response_at():
|
||||||
reply = process()
|
# 直接调用
|
||||||
|
reply = process()
|
||||||
|
else:
|
||||||
|
check, result = check_response_rule(str(event.message_chain).strip())
|
||||||
|
|
||||||
|
if check:
|
||||||
|
reply = process(result.strip())
|
||||||
|
# 检查是否随机响应
|
||||||
|
elif random_responding():
|
||||||
|
logging.info("随机响应group_{}消息".format(event.group.id))
|
||||||
|
reply = process()
|
||||||
|
|
||||||
if reply:
|
if reply:
|
||||||
return self.send(event, reply)
|
return self.send(event, reply)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# 普通消息处理模块
|
# 普通消息处理模块
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
import openai
|
import openai
|
||||||
import pkg.utils.context
|
import pkg.utils.context
|
||||||
import pkg.openai.session
|
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")
|
reply = event.get_return_value("reply")
|
||||||
|
|
||||||
if not event.is_prevented_default():
|
if not event.is_prevented_default():
|
||||||
reply = blob.check_text(prefix + text)
|
reply = [prefix + text]
|
||||||
except openai.error.APIConnectionError as e:
|
except openai.error.APIConnectionError as e:
|
||||||
err_msg = str(e)
|
err_msg = str(e)
|
||||||
if err_msg.__contains__('Error communicating with OpenAI'):
|
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),
|
reply = handle_exception("{}会话调用API失败:{}".format(session_name, e),
|
||||||
"[bot]err:RateLimitError,请重试或联系作者,或等待修复")
|
"[bot]err:RateLimitError,请重试或联系作者,或等待修复")
|
||||||
except openai.error.InvalidRequestError as e:
|
except openai.error.InvalidRequestError as e:
|
||||||
reply = handle_exception("{}API调用参数错误:{}\n\n这可能是由于config.py中的prompt_submit_length参数或"
|
reply = handle_exception("{}API调用参数错误:{}\n".format(
|
||||||
"completion_api_params中的max_tokens参数数值过大导致的,请尝试将其降低".format(
|
|
||||||
session_name, e), "[bot]err:API调用参数错误,请联系管理员,或等待修复")
|
session_name, e), "[bot]err:API调用参数错误,请联系管理员,或等待修复")
|
||||||
except openai.error.ServiceUnavailableError as e:
|
except openai.error.ServiceUnavailableError as e:
|
||||||
reply = handle_exception("{}API调用服务不可用:{}".format(session_name, e), "[bot]err:API调用服务不可用,请重试或联系管理员,或等待修复")
|
reply = handle_exception("{}API调用服务不可用:{}".format(session_name, e), "[bot]err:API调用服务不可用,请重试或联系管理员,或等待修复")
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import pkg.plugin.host as plugin_host
|
|||||||
import pkg.plugin.models as plugin_models
|
import pkg.plugin.models as plugin_models
|
||||||
import pkg.qqbot.ignore as ignore
|
import pkg.qqbot.ignore as ignore
|
||||||
import pkg.qqbot.banlist as banlist
|
import pkg.qqbot.banlist as banlist
|
||||||
|
import pkg.qqbot.blob as blob
|
||||||
|
|
||||||
processing = []
|
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]))] + (
|
reply[0][:min(100, len(reply[0]))] + (
|
||||||
"..." if len(reply[0]) > 100 else "")))
|
"..." if len(reply[0]) > 100 else "")))
|
||||||
reply = [mgr.reply_filter.process(reply[0])]
|
reply = [mgr.reply_filter.process(reply[0])]
|
||||||
|
reply = blob.check_text(reply[0])
|
||||||
else:
|
else:
|
||||||
logging.info("回复[{}]消息".format(session_name))
|
logging.info("回复[{}]消息".format(session_name))
|
||||||
|
|
||||||
|
|||||||
44
pkg/utils/announcement.py
Normal file
44
pkg/utils/announcement.py
Normal 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
@@ -1,13 +1,19 @@
|
|||||||
# OpenAI账号免费额度剩余查询
|
# OpenAI账号免费额度剩余查询
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
def fetch_credit_data(api_key: str, http_proxy: str) -> dict:
|
||||||
def fetch_credit_data(api_key: str) -> dict:
|
|
||||||
"""OpenAI账号免费额度剩余查询"""
|
"""OpenAI账号免费额度剩余查询"""
|
||||||
|
proxies = {
|
||||||
|
"http":http_proxy,
|
||||||
|
"https":http_proxy
|
||||||
|
} if http_proxy is not None else None
|
||||||
|
|
||||||
resp = requests.get(
|
resp = requests.get(
|
||||||
url="https://api.openai.com/dashboard/billing/credit_grants",
|
url="https://api.openai.com/dashboard/billing/credit_grants",
|
||||||
headers={
|
headers={
|
||||||
"Authorization": "Bearer {}".format(api_key),
|
"Authorization": "Bearer {}".format(api_key),
|
||||||
}
|
},
|
||||||
|
proxies=proxies
|
||||||
)
|
)
|
||||||
|
|
||||||
return resp.json()
|
return resp.json()
|
||||||
@@ -54,7 +54,7 @@ def get_current_tag() -> str:
|
|||||||
return current_tag
|
return current_tag
|
||||||
|
|
||||||
|
|
||||||
def update_all() -> bool:
|
def update_all(cli: bool = False) -> bool:
|
||||||
"""检查更新并下载源码"""
|
"""检查更新并下载源码"""
|
||||||
current_tag = get_current_tag()
|
current_tag = get_current_tag()
|
||||||
|
|
||||||
@@ -69,12 +69,19 @@ def update_all() -> bool:
|
|||||||
|
|
||||||
if latest_rls == {}:
|
if latest_rls == {}:
|
||||||
latest_rls = rls
|
latest_rls = rls
|
||||||
logging.info("更新日志: {}".format(rls_notes))
|
if not cli:
|
||||||
|
logging.info("更新日志: {}".format(rls_notes))
|
||||||
|
else:
|
||||||
|
print("更新日志: {}".format(rls_notes))
|
||||||
|
|
||||||
if latest_rls == {}: # 没有新版本
|
if latest_rls == {}: # 没有新版本
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 下载最新版本的zip到temp目录
|
# 下载最新版本的zip到temp目录
|
||||||
logging.info("开始下载最新版本: {}".format(latest_rls['zipball_url']))
|
if not cli:
|
||||||
|
logging.info("开始下载最新版本: {}".format(latest_rls['zipball_url']))
|
||||||
|
else:
|
||||||
|
print("开始下载最新版本: {}".format(latest_rls['zipball_url']))
|
||||||
zip_url = latest_rls['zipball_url']
|
zip_url = latest_rls['zipball_url']
|
||||||
zip_resp = requests.get(url=zip_url)
|
zip_resp = requests.get(url=zip_url)
|
||||||
zip_data = zip_resp.content
|
zip_data = zip_resp.content
|
||||||
@@ -87,7 +94,10 @@ def update_all() -> bool:
|
|||||||
with open("temp/updater/{}.zip".format(latest_rls['tag_name']), "wb") as f:
|
with open("temp/updater/{}.zip".format(latest_rls['tag_name']), "wb") as f:
|
||||||
f.write(zip_data)
|
f.write(zip_data)
|
||||||
|
|
||||||
logging.info("下载最新版本完成: {}".format("temp/updater/{}.zip".format(latest_rls['tag_name'])))
|
if not cli:
|
||||||
|
logging.info("下载最新版本完成: {}".format("temp/updater/{}.zip".format(latest_rls['tag_name'])))
|
||||||
|
else:
|
||||||
|
print("下载最新版本完成: {}".format("temp/updater/{}.zip".format(latest_rls['tag_name'])))
|
||||||
|
|
||||||
# 解压zip到temp/updater/<tag_name>/
|
# 解压zip到temp/updater/<tag_name>/
|
||||||
import zipfile
|
import zipfile
|
||||||
@@ -116,6 +126,15 @@ def update_all() -> bool:
|
|||||||
dst = src.replace(source_root, ".")
|
dst = src.replace(source_root, ".")
|
||||||
if os.path.exists(dst):
|
if os.path.exists(dst):
|
||||||
os.remove(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)
|
shutil.copy(src, dst)
|
||||||
|
|
||||||
# 把current_tag写入文件
|
# 把current_tag写入文件
|
||||||
@@ -124,8 +143,11 @@ def update_all() -> bool:
|
|||||||
f.write(current_tag)
|
f.write(current_tag)
|
||||||
|
|
||||||
# 通知管理员
|
# 通知管理员
|
||||||
import pkg.utils.context
|
if not cli:
|
||||||
pkg.utils.context.get_qqbot_manager().notify_admin("已更新到最新版本: {}\n更新日志:\n{}\n新功能通常可以在config-template.py中看到,完整的更新日志请前往 https://github.com/RockChinQ/QChatGPT/releases 查看".format(current_tag, "\n".join(rls_notes)))
|
import pkg.utils.context
|
||||||
|
pkg.utils.context.get_qqbot_manager().notify_admin("已更新到最新版本: {}\n更新日志:\n{}\n新功能通常可以在config-template.py中看到,完整的更新日志请前往 https://github.com/RockChinQ/QChatGPT/releases 查看".format(current_tag, "\n".join(rls_notes)))
|
||||||
|
else:
|
||||||
|
print("已更新到最新版本: {}\n更新日志:\n{}\n新功能通常可以在config-template.py中看到,完整的更新日志请前往 https://github.com/RockChinQ/QChatGPT/releases 查看".format(current_tag, "\n".join(rls_notes)))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
requests~=2.28.1
|
requests~=2.28.1
|
||||||
openai~=0.27.0
|
openai~=0.27.2
|
||||||
dulwich~=0.21.3
|
dulwich~=0.21.3
|
||||||
colorlog~=6.6.0
|
colorlog~=6.6.0
|
||||||
yiri-mirai~=0.2.6.1
|
yiri-mirai~=0.2.6.1
|
||||||
|
|||||||
1
res/announcement
Normal file
1
res/announcement
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
12
scenario/default-template.json
Normal file
12
scenario/default-template.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"prompt": [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "You are a helpful assistant. 如果我需要帮助,你要说“输入!help获得帮助”"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "好的,我是一个能干的AI助手。 如果你需要帮助,我会说“输入!help获得帮助”"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"说明": "mask将替换敏感词中的每一个字,若mask_word值不为空,则将敏感词整个替换为mask_word的值",
|
||||||
|
"mask": "*",
|
||||||
|
"mask_word": "",
|
||||||
"words": [
|
"words": [
|
||||||
"习近平",
|
"习近平",
|
||||||
"胡锦涛",
|
"胡锦涛",
|
||||||
|
|||||||
0
tests/token_test/__init__.py
Normal file
0
tests/token_test/__init__.py
Normal file
0
tests/token_test/token_count.py
Normal file
0
tests/token_test/token_count.py
Normal file
Reference in New Issue
Block a user