mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 12:05:54 +00:00
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d9964bcb1 | ||
|
|
15cb8016d3 | ||
|
|
895cc0a2c5 | ||
|
|
20bf349e4e | ||
|
|
e297763da1 | ||
|
|
e471970654 | ||
|
|
12faaaced8 | ||
|
|
083cbc55cc | ||
|
|
8aa7a3273d | ||
|
|
255e2c4385 | ||
|
|
9856306870 | ||
|
|
527ab8b8a7 | ||
|
|
f8e19ba9b3 | ||
|
|
7649dbfbbc | ||
|
|
81e734644d | ||
|
|
ae55cf5b1e | ||
|
|
af539546ef | ||
|
|
0031ce57d0 | ||
|
|
2f48a2ce57 | ||
|
|
6068ab7100 | ||
|
|
29a7dccef4 | ||
|
|
e2073da86e | ||
|
|
ae079526f7 | ||
|
|
947bae8e26 | ||
|
|
a68e29dff6 | ||
|
|
a588d7f960 | ||
|
|
66224e5a32 | ||
|
|
07abad6a14 | ||
|
|
83d02aaaac | ||
|
|
5a27ac165e | ||
|
|
bd9a523233 | ||
|
|
43959b158f | ||
|
|
d81b457bba | ||
|
|
b40d639785 | ||
|
|
0a8d8f4f66 | ||
|
|
d16cb25cde | ||
|
|
7aef1758e0 | ||
|
|
9758756fdd | ||
|
|
13ef35f96f | ||
|
|
6b8c1209b7 | ||
|
|
7184f3053a | ||
|
|
b83eac10e6 | ||
|
|
cb42eaef69 | ||
|
|
0dfd636a7e | ||
|
|
21ff0fd258 | ||
|
|
c2eaeb2c72 | ||
|
|
2a414a4bea | ||
|
|
fc0c38c8af | ||
|
|
595e6c8a0c | ||
|
|
ced16fd221 | ||
|
|
0817c3f148 | ||
|
|
fb40af81ac | ||
|
|
1c5ad05e89 | ||
|
|
5865ac017c | ||
|
|
4061a92f8e | ||
|
|
d37c31b31c | ||
|
|
973ef0078f | ||
|
|
48dcd257da | ||
|
|
da03911610 | ||
|
|
b6f7f3b73f | ||
|
|
2050d20ea7 | ||
|
|
ac1fb4a63a |
33
.github/workflows/sync-wiki.yml
vendored
Normal file
33
.github/workflows/sync-wiki.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
name: Update Wiki
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'res/wiki/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-wiki:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup Git
|
||||||
|
run: |
|
||||||
|
git config --global user.name "GitHub Actions"
|
||||||
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
- name: Clone Wiki Repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: RockChinQ/QChatGPT.wiki
|
||||||
|
path: wiki
|
||||||
|
- name: Copy res/wiki content to wiki
|
||||||
|
run: |
|
||||||
|
cp -r res/wiki/* wiki/
|
||||||
|
- name: Commit and Push Changes
|
||||||
|
run: |
|
||||||
|
cd wiki
|
||||||
|
if git diff --name-only; then
|
||||||
|
git add .
|
||||||
|
git commit -m "Update wiki"
|
||||||
|
git push
|
||||||
|
fi
|
||||||
10
.github/workflows/update-cmdpriv-template.yml
vendored
10
.github/workflows/update-cmdpriv-template.yml
vendored
@@ -27,7 +27,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
python -m pip install --upgrade yiri-mirai openai colorlog func_timeout dulwich Pillow
|
python -m pip install --upgrade yiri-mirai openai colorlog func_timeout dulwich Pillow
|
||||||
|
|
||||||
|
- name: Copy Scripts
|
||||||
|
run: |
|
||||||
|
cp res/scripts/generate_cmdpriv_template.py .
|
||||||
|
|
||||||
- name: Generate Files
|
- name: Generate Files
|
||||||
run: |
|
run: |
|
||||||
python main.py
|
python main.py
|
||||||
@@ -38,7 +42,7 @@ jobs:
|
|||||||
- name: Check for changes in cmdpriv-template.json
|
- name: Check for changes in cmdpriv-template.json
|
||||||
id: check_changes
|
id: check_changes
|
||||||
run: |
|
run: |
|
||||||
if git diff --name-only | grep -q "cmdpriv-template.json"; then
|
if git diff --name-only | grep -q "res/templates/cmdpriv-template.json"; then
|
||||||
echo "::set-output name=changes_detected::true"
|
echo "::set-output name=changes_detected::true"
|
||||||
else
|
else
|
||||||
echo "::set-output name=changes_detected::false"
|
echo "::set-output name=changes_detected::false"
|
||||||
@@ -49,6 +53,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
git config --global user.name "GitHub Actions Bot"
|
git config --global user.name "GitHub Actions Bot"
|
||||||
git config --global user.email "<github-actions@github.com>"
|
git config --global user.email "<github-actions@github.com>"
|
||||||
git add cmdpriv-template.json
|
git add res/templates/cmdpriv-template.json
|
||||||
git commit -m "Update cmdpriv-template.json"
|
git commit -m "Update cmdpriv-template.json"
|
||||||
git push
|
git push
|
||||||
|
|||||||
4
.github/workflows/update-override-all.yml
vendored
4
.github/workflows/update-override-all.yml
vendored
@@ -31,6 +31,10 @@ jobs:
|
|||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
# 在此处添加您的项目所需的其他依赖
|
# 在此处添加您的项目所需的其他依赖
|
||||||
|
|
||||||
|
- name: Copy Scripts
|
||||||
|
run: |
|
||||||
|
cp res/scripts/generate_override_all.py .
|
||||||
|
|
||||||
- name: Run generate_override_all.py
|
- name: Run generate_override_all.py
|
||||||
run: python3 generate_override_all.py
|
run: python3 generate_override_all.py
|
||||||
|
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -17,4 +17,5 @@ scenario/
|
|||||||
override.json
|
override.json
|
||||||
cookies.json
|
cookies.json
|
||||||
res/announcement_saved
|
res/announcement_saved
|
||||||
cmdpriv.json
|
cmdpriv.json
|
||||||
|
tips.py
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
|||||||
[submodule "QChatGPT.wiki"]
|
|
||||||
path = QChatGPT.wiki
|
|
||||||
url = https://github.com/RockChinQ/QChatGPT.wiki.git
|
|
||||||
Submodule QChatGPT.wiki deleted from d0dd0c1ad3
17
README.md
17
README.md
@@ -2,18 +2,15 @@
|
|||||||
|
|
||||||
> 2023/3/18 现已支持GPT-4 API(内测),请查看`config-template.py`中的`completion_api_params`
|
> 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 逆向库已支持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)
|
|
||||||
|
|
||||||
|
- **客官,来都来了,不点个⭐吗?**
|
||||||
- 到[项目Wiki](https://github.com/RockChinQ/QChatGPT/wiki)可了解项目详细信息
|
- 到[项目Wiki](https://github.com/RockChinQ/QChatGPT/wiki)可了解项目详细信息
|
||||||
- ~~由bilibili TheLazy制作的[视频教程](https://www.bilibili.com/video/BV15v4y1X7aP)~~(寄了,求大佬做个新的)
|
- 官方交流、答疑群: 656285629
|
||||||
- 交流、答疑群: ~~204785790~~(已满)、~~691226829~~(已满)、656285629
|
|
||||||
- **进群提问前请您`确保`已经找遍文档和issue均无法解决**
|
- **进群提问前请您`确保`已经找遍文档和issue均无法解决**
|
||||||
|
- 社区群(内有一键部署包、图形化界面等资源): 362515018
|
||||||
- QQ频道机器人见[QQChannelChatGPT](https://github.com/Soulter/QQChannelChatGPT)
|
- QQ频道机器人见[QQChannelChatGPT](https://github.com/Soulter/QQChannelChatGPT)
|
||||||
- 欢迎各种形式的贡献,请查看[贡献指引](CONTRIBUTING.md)
|
- 欢迎各种形式的贡献,请查看[贡献指引](CONTRIBUTING.md)
|
||||||
|
|
||||||
通过调用OpenAI的ChatGPT等语言模型来实现一个更加智能的QQ机器人
|
|
||||||
|
|
||||||
## 🍺模型适配一览
|
## 🍺模型适配一览
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -129,6 +126,12 @@
|
|||||||
- 目前已支持正向代理访问接口
|
- 目前已支持正向代理访问接口
|
||||||
- 详细请查看config.py中的`openai_config`的说明
|
- 详细请查看config.py中的`openai_config`的说明
|
||||||
</details>
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>✅支持自定义提示内容</summary>
|
||||||
|
|
||||||
|
- 允许用户自定义报错、帮助等提示信息
|
||||||
|
- 请查看`tips.py`
|
||||||
|
</details>
|
||||||
</details>
|
</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)
|
||||||
@@ -156,7 +159,7 @@
|
|||||||
|
|
||||||
#### Docker方式
|
#### Docker方式
|
||||||
|
|
||||||
请查看[此文档](docker_deploy.md)
|
请查看[此文档](res/docs/docker_deploy.md)
|
||||||
由[@mikumifa](https://github.com/mikumifa)贡献
|
由[@mikumifa](https://github.com/mikumifa)贡献
|
||||||
|
|
||||||
#### 安装器方式
|
#### 安装器方式
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ mirai_http_api_config = {
|
|||||||
# },
|
# },
|
||||||
# "http_proxy": "http://127.0.0.1:12345"
|
# "http_proxy": "http://127.0.0.1:12345"
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
# 现已支持反向代理,可以添加reverse_proxy字段以使用反向代理
|
# 现已支持反向代理,可以添加reverse_proxy字段以使用反向代理
|
||||||
# 使用反向代理可以在国内使用OpenAI的API,反向代理的配置请参考
|
# 使用反向代理可以在国内使用OpenAI的API,反向代理的配置请参考
|
||||||
# https://github.com/Ice-Hazymoon/openai-scf-proxy
|
# https://github.com/Ice-Hazymoon/openai-scf-proxy
|
||||||
#
|
#
|
||||||
# 反向代理填写示例:
|
# 反向代理填写示例:
|
||||||
@@ -63,7 +63,7 @@ admin_qq = 0
|
|||||||
# 情景预设(机器人人格)
|
# 情景预设(机器人人格)
|
||||||
# 每个会话的预设信息,影响所有会话,无视指令重置
|
# 每个会话的预设信息,影响所有会话,无视指令重置
|
||||||
# 可以通过这个字段指定某些情况的回复,可直接用自然语言描述指令
|
# 可以通过这个字段指定某些情况的回复,可直接用自然语言描述指令
|
||||||
# 例如:
|
# 例如:
|
||||||
# default_prompt = "如果我之后想获取帮助,请你说“输入!help获取帮助”"
|
# default_prompt = "如果我之后想获取帮助,请你说“输入!help获取帮助”"
|
||||||
# 这样用户在不知所措的时候机器人就会提示其输入!help获取帮助
|
# 这样用户在不知所措的时候机器人就会提示其输入!help获取帮助
|
||||||
# 可参考 https://github.com/PlexPt/awesome-chatgpt-prompts-zh
|
# 可参考 https://github.com/PlexPt/awesome-chatgpt-prompts-zh
|
||||||
@@ -81,14 +81,14 @@ admin_qq = 0
|
|||||||
# 例如:
|
# 例如:
|
||||||
# !reset linux-terminal
|
# !reset linux-terminal
|
||||||
# 若不指定名称,则使用默认情景预设
|
# 若不指定名称,则使用默认情景预设
|
||||||
#
|
#
|
||||||
# 也可以使用指令:
|
# 也可以使用指令:
|
||||||
# !default <名称>
|
# !default <名称>
|
||||||
# 将指定的情景预设设置为默认情景预设
|
# 将指定的情景预设设置为默认情景预设
|
||||||
# 例如:
|
# 例如:
|
||||||
# !default linux-terminal
|
# !default linux-terminal
|
||||||
# 之后的会话重置时若不指定名称,则使用linux-terminal情景预设
|
# 之后的会话重置时若不指定名称,则使用linux-terminal情景预设
|
||||||
#
|
#
|
||||||
# 还可以加载文件中的预设文字,使用方法请查看:https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E9%A2%84%E8%AE%BE%E6%96%87%E5%AD%97
|
# 还可以加载文件中的预设文字,使用方法请查看:https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E9%A2%84%E8%AE%BE%E6%96%87%E5%AD%97
|
||||||
default_prompt = {
|
default_prompt = {
|
||||||
"default": "如果我之后想获取帮助,请你说“输入!help获取帮助”",
|
"default": "如果我之后想获取帮助,请你说“输入!help获取帮助”",
|
||||||
@@ -160,7 +160,7 @@ prompt_submit_length = 2048
|
|||||||
# OpenAI补全API的参数
|
# OpenAI补全API的参数
|
||||||
# 请在下方填写模型,程序自动选择接口
|
# 请在下方填写模型,程序自动选择接口
|
||||||
# 现已支持的模型有:
|
# 现已支持的模型有:
|
||||||
#
|
#
|
||||||
# 'gpt-4'
|
# 'gpt-4'
|
||||||
# 'gpt-4-0314'
|
# 'gpt-4-0314'
|
||||||
# 'gpt-4-32k'
|
# 'gpt-4-32k'
|
||||||
@@ -226,11 +226,6 @@ retry_times = 3
|
|||||||
# 设置为False时,向用户及管理员发送错误详细信息
|
# 设置为False时,向用户及管理员发送错误详细信息
|
||||||
hide_exce_info_to_user = False
|
hide_exce_info_to_user = False
|
||||||
|
|
||||||
# 消息处理出错时向用户发送的提示信息
|
|
||||||
# 仅当hide_exce_info_to_user为True时生效
|
|
||||||
# 设置为空字符串时,不发送提示信息
|
|
||||||
alter_tip_message = '出错了,请稍后再试'
|
|
||||||
|
|
||||||
# 线程池相关配置
|
# 线程池相关配置
|
||||||
# 该参数决定机器人可以同时处理几个人的消息,超出线程池数量的请求会被阻塞,不会被丢弃
|
# 该参数决定机器人可以同时处理几个人的消息,超出线程池数量的请求会被阻塞,不会被丢弃
|
||||||
# 如果你不清楚该参数的意义,请不要更改
|
# 如果你不清楚该参数的意义,请不要更改
|
||||||
@@ -259,11 +254,6 @@ rate_limitation = 60
|
|||||||
# - "drop": 此分钟内,若对话次数超过限速次数,则丢弃之后的对话,每自然分钟重置
|
# - "drop": 此分钟内,若对话次数超过限速次数,则丢弃之后的对话,每自然分钟重置
|
||||||
rate_limit_strategy = "wait"
|
rate_limit_strategy = "wait"
|
||||||
|
|
||||||
# drop策略时,超过限速均值时,丢弃的对话的提示信息
|
|
||||||
# 仅当rate_limitation_strategy为"drop"时生效
|
|
||||||
# 若设置为空字符串,则不发送提示信息
|
|
||||||
rate_limit_drop_tip = "本分钟对话次数超过限速次数,此对话被丢弃"
|
|
||||||
|
|
||||||
# 是否在启动时进行依赖库更新
|
# 是否在启动时进行依赖库更新
|
||||||
upgrade_dependencies = True
|
upgrade_dependencies = True
|
||||||
|
|
||||||
@@ -274,8 +264,3 @@ report_usage = True
|
|||||||
|
|
||||||
# 日志级别
|
# 日志级别
|
||||||
logging_level = logging.INFO
|
logging_level = logging.INFO
|
||||||
|
|
||||||
# 定制帮助消息
|
|
||||||
help_message = """此机器人通过调用大型语言模型生成回复,不具有情感。
|
|
||||||
你可以用自然语言与其交流,回复的消息中[GPT]开头的为模型生成的语言,[bot]开头的为程序提示。
|
|
||||||
欢迎到github.com/RockChinQ/QChatGPT 给个star"""
|
|
||||||
59
main.py
59
main.py
@@ -108,6 +108,7 @@ def reset_logging():
|
|||||||
|
|
||||||
# 临时函数,用于加载config和上下文,未来统一放在config类
|
# 临时函数,用于加载config和上下文,未来统一放在config类
|
||||||
def load_config():
|
def load_config():
|
||||||
|
logging.info("检查config模块完整性.")
|
||||||
# 完整性校验
|
# 完整性校验
|
||||||
is_integrity = True
|
is_integrity = True
|
||||||
config_template = importlib.import_module('config-template')
|
config_template = importlib.import_module('config-template')
|
||||||
@@ -119,7 +120,7 @@ def load_config():
|
|||||||
is_integrity = False
|
is_integrity = False
|
||||||
|
|
||||||
if not is_integrity:
|
if not is_integrity:
|
||||||
logging.warning("配置文件不完整,请依据config-template.py检查config.py")
|
logging.warning("配置文件不完整,您可以依据config-template.py检查config.py")
|
||||||
|
|
||||||
# 检查override.json覆盖
|
# 检查override.json覆盖
|
||||||
if os.path.exists("override.json"):
|
if os.path.exists("override.json"):
|
||||||
@@ -132,13 +133,31 @@ def load_config():
|
|||||||
logging.error("无法覆写配置[{}]为[{}],该配置不存在,请检查override.json是否正确".format(key, override_json[key]))
|
logging.error("无法覆写配置[{}]为[{}],该配置不存在,请检查override.json是否正确".format(key, override_json[key]))
|
||||||
|
|
||||||
if not is_integrity:
|
if not is_integrity:
|
||||||
logging.warning("以上配置已被设为默认值,将在5秒后继续启动... ")
|
logging.warning("以上配置已被设为默认值,将在3秒后继续启动... ")
|
||||||
time.sleep(5)
|
time.sleep(3)
|
||||||
|
|
||||||
# 存进上下文
|
# 存进上下文
|
||||||
pkg.utils.context.set_config(config)
|
pkg.utils.context.set_config(config)
|
||||||
|
|
||||||
|
|
||||||
|
def complete_tips():
|
||||||
|
"""根据tips-custom-template模块补全tips模块的属性"""
|
||||||
|
is_integrity = True
|
||||||
|
logging.info("检查tips模块完整性.")
|
||||||
|
tips_template = importlib.import_module('tips-custom-template')
|
||||||
|
tips = importlib.import_module('tips')
|
||||||
|
for key in dir(tips_template):
|
||||||
|
if not key.startswith("__") and not hasattr(tips, key):
|
||||||
|
setattr(tips, key, getattr(tips_template, key))
|
||||||
|
logging.warning("[{}]不存在".format(key))
|
||||||
|
is_integrity = False
|
||||||
|
|
||||||
|
if not is_integrity:
|
||||||
|
logging.warning("tips模块不完整,您可以依据tips-custom-template.py检查tips.py")
|
||||||
|
logging.warning("以上配置已被设为默认值,将在3秒后继续启动... ")
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
|
||||||
def start(first_time_init=False):
|
def start(first_time_init=False):
|
||||||
"""启动流程,reload之后会被执行"""
|
"""启动流程,reload之后会被执行"""
|
||||||
|
|
||||||
@@ -193,12 +212,12 @@ def start(first_time_init=False):
|
|||||||
import pkg.openai.session
|
import pkg.openai.session
|
||||||
import pkg.qqbot.manager
|
import pkg.qqbot.manager
|
||||||
import pkg.openai.dprompt
|
import pkg.openai.dprompt
|
||||||
import pkg.qqbot.cmds.mgr
|
import pkg.qqbot.cmds.aamgr
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pkg.openai.dprompt.register_all()
|
pkg.openai.dprompt.register_all()
|
||||||
pkg.qqbot.cmds.mgr.register_all()
|
pkg.qqbot.cmds.aamgr.register_all()
|
||||||
pkg.qqbot.cmds.mgr.apply_privileges()
|
pkg.qqbot.cmds.aamgr.apply_privileges()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(e)
|
logging.error(e)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -299,8 +318,7 @@ def start(first_time_init=False):
|
|||||||
logging.info('热重载完成')
|
logging.info('热重载完成')
|
||||||
|
|
||||||
# 发送赞赏码
|
# 发送赞赏码
|
||||||
if hasattr(config, 'encourage_sponsor_at_start') \
|
if config.encourage_sponsor_at_start \
|
||||||
and config.encourage_sponsor_at_start \
|
|
||||||
and pkg.utils.context.get_openai_manager().audit_mgr.get_total_text_length() >= 2048:
|
and pkg.utils.context.get_openai_manager().audit_mgr.get_total_text_length() >= 2048:
|
||||||
|
|
||||||
logging.info("发送赞赏码")
|
logging.info("发送赞赏码")
|
||||||
@@ -357,19 +375,13 @@ def stop():
|
|||||||
|
|
||||||
|
|
||||||
def check_file():
|
def check_file():
|
||||||
# 配置文件存在性校验
|
|
||||||
if not os.path.exists('config.py'):
|
|
||||||
shutil.copy('config-template.py', 'config.py')
|
|
||||||
print('请先在config.py中填写配置')
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# 检查是否有banlist.py,如果没有就把banlist-template.py复制一份
|
# 检查是否有banlist.py,如果没有就把banlist-template.py复制一份
|
||||||
if not os.path.exists('banlist.py'):
|
if not os.path.exists('banlist.py'):
|
||||||
shutil.copy('banlist-template.py', 'banlist.py')
|
shutil.copy('res/templates/banlist-template.py', 'banlist.py')
|
||||||
|
|
||||||
# 检查是否有sensitive.json
|
# 检查是否有sensitive.json
|
||||||
if not os.path.exists("sensitive.json"):
|
if not os.path.exists("sensitive.json"):
|
||||||
shutil.copy("sensitive-template.json", "sensitive.json")
|
shutil.copy("res/templates/sensitive-template.json", "sensitive.json")
|
||||||
|
|
||||||
# 检查是否有scenario/default.json
|
# 检查是否有scenario/default.json
|
||||||
if not os.path.exists("scenario/default.json"):
|
if not os.path.exists("scenario/default.json"):
|
||||||
@@ -377,7 +389,11 @@ def check_file():
|
|||||||
|
|
||||||
# 检查cmdpriv.json
|
# 检查cmdpriv.json
|
||||||
if not os.path.exists("cmdpriv.json"):
|
if not os.path.exists("cmdpriv.json"):
|
||||||
shutil.copy("cmdpriv-template.json", "cmdpriv.json")
|
shutil.copy("res/templates/cmdpriv-template.json", "cmdpriv.json")
|
||||||
|
|
||||||
|
# 检查tips_custom
|
||||||
|
if not os.path.exists("tips.py"):
|
||||||
|
shutil.copy("tips-custom-template.py", "tips.py")
|
||||||
|
|
||||||
# 检查temp目录
|
# 检查temp目录
|
||||||
if not os.path.exists("temp/"):
|
if not os.path.exists("temp/"):
|
||||||
@@ -389,6 +405,12 @@ def check_file():
|
|||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
os.mkdir(path)
|
os.mkdir(path)
|
||||||
|
|
||||||
|
# 配置文件存在性校验
|
||||||
|
if not os.path.exists('config.py'):
|
||||||
|
shutil.copy('config-template.py', 'config.py')
|
||||||
|
print('请先在config.py中填写配置')
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# 初始化相关文件
|
# 初始化相关文件
|
||||||
@@ -402,6 +424,9 @@ def main():
|
|||||||
load_config()
|
load_config()
|
||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
|
|
||||||
|
# 检查tips模块
|
||||||
|
complete_tips()
|
||||||
|
|
||||||
# 配置线程池
|
# 配置线程池
|
||||||
from pkg.utils import ThreadCtl
|
from pkg.utils import ThreadCtl
|
||||||
thread_ctl = ThreadCtl(
|
thread_ctl = ThreadCtl(
|
||||||
|
|||||||
@@ -63,16 +63,13 @@
|
|||||||
"font_path": "",
|
"font_path": "",
|
||||||
"retry_times": 3,
|
"retry_times": 3,
|
||||||
"hide_exce_info_to_user": false,
|
"hide_exce_info_to_user": false,
|
||||||
"alter_tip_message": "出错了,请稍后再试",
|
|
||||||
"sys_pool_num": 8,
|
"sys_pool_num": 8,
|
||||||
"admin_pool_num": 2,
|
"admin_pool_num": 2,
|
||||||
"user_pool_num": 6,
|
"user_pool_num": 6,
|
||||||
"session_expire_time": 1200,
|
"session_expire_time": 1200,
|
||||||
"rate_limitation": 60,
|
"rate_limitation": 60,
|
||||||
"rate_limit_strategy": "wait",
|
"rate_limit_strategy": "wait",
|
||||||
"rate_limit_drop_tip": "本分钟对话次数超过限速次数,此对话被丢弃",
|
|
||||||
"upgrade_dependencies": true,
|
"upgrade_dependencies": true,
|
||||||
"report_usage": true,
|
"report_usage": true,
|
||||||
"logging_level": 20,
|
"logging_level": 20
|
||||||
"help_message": "此机器人通过调用大型语言模型生成回复,不具有情感。\n你可以用自然语言与其交流,回复的消息中[GPT]开头的为模型生成的语言,[bot]开头的为程序提示。\n欢迎到github.com/RockChinQ/QChatGPT 给个star"
|
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ class DataGatherer:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
if hasattr(config, "report_usage") and not config.report_usage:
|
if not config.report_usage:
|
||||||
return
|
return
|
||||||
res = requests.get("http://reports.rockchin.top:18989/usage?service_name=qchatgpt.{}&version={}&count={}".format(subservice_name, self.version_str, count))
|
res = requests.get("http://reports.rockchin.top:18989/usage?service_name=qchatgpt.{}&version={}&count={}".format(subservice_name, self.version_str, count))
|
||||||
if res.status_code != 200 or res.text != "ok":
|
if res.status_code != 200 or res.text != "ok":
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class OpenAIInteract:
|
|||||||
dict: 响应
|
dict: 响应
|
||||||
"""
|
"""
|
||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
params = config.image_api_params if hasattr(config, "image_api_params") else self.default_image_api_params
|
params = config.image_api_params
|
||||||
|
|
||||||
response = openai.Image.create(
|
response = openai.Image.create(
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ class Session:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
prompts, counts = self.cut_out(text, max_length)
|
prompts, counts = self.cut_out(text, max_length)
|
||||||
|
|
||||||
|
|||||||
@@ -64,12 +64,7 @@ def text_to_image(text: str) -> MessageComponent:
|
|||||||
|
|
||||||
def check_text(text: str) -> list:
|
def check_text(text: str) -> list:
|
||||||
"""检查文本是否为长消息,并转换成该使用的消息链组件"""
|
"""检查文本是否为长消息,并转换成该使用的消息链组件"""
|
||||||
if not hasattr(config, 'blob_message_threshold'):
|
|
||||||
return [text]
|
|
||||||
|
|
||||||
if len(text) > config.blob_message_threshold:
|
if len(text) > config.blob_message_threshold:
|
||||||
if not hasattr(config, 'blob_message_strategy'):
|
|
||||||
raise AttributeError('未定义长消息处理策略')
|
|
||||||
|
|
||||||
# logging.info("长消息: {}".format(text))
|
# logging.info("长消息: {}".format(text))
|
||||||
if config.blob_message_strategy == 'image':
|
if config.blob_message_strategy == 'image':
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import json
|
|||||||
|
|
||||||
|
|
||||||
__command_list__ = {}
|
__command_list__ = {}
|
||||||
|
|
||||||
|
import tips as tips_custom
|
||||||
|
|
||||||
"""命令树
|
"""命令树
|
||||||
|
|
||||||
结构:
|
结构:
|
||||||
@@ -262,7 +265,7 @@ def execute(context: Context) -> list:
|
|||||||
|
|
||||||
# 检查权限
|
# 检查权限
|
||||||
if ctx.privilege < node['privilege']:
|
if ctx.privilege < node['privilege']:
|
||||||
raise CommandPrivilegeError('权限不足: {}'.format(path))
|
raise CommandPrivilegeError(tips_custom.command_admin_message+"{}".format(path))
|
||||||
|
|
||||||
# 执行
|
# 执行
|
||||||
execed, reply = node['cls'].process(ctx)
|
execed, reply = node['cls'].process(ctx)
|
||||||
@@ -275,7 +278,7 @@ def execute(context: Context) -> list:
|
|||||||
path = path + '.' + ctx.crt_command
|
path = path + '.' + ctx.crt_command
|
||||||
except KeyError:
|
except KeyError:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise CommandPrivilegeError('找不到指令: {}'.format(path))
|
raise CommandPrivilegeError(tips_custom.command_err_message+"{}".format(path))
|
||||||
|
|
||||||
|
|
||||||
def register_all():
|
def register_all():
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mirai import Image
|
from mirai import Image
|
||||||
@@ -19,6 +19,7 @@ class DrawCommand(AbstractCommandNode):
|
|||||||
import pkg.openai.session
|
import pkg.openai.session
|
||||||
|
|
||||||
reply = []
|
reply = []
|
||||||
|
|
||||||
if len(ctx.params) == 0:
|
if len(ctx.params) == 0:
|
||||||
reply = ["[bot]err: 未提供图片描述文字"]
|
reply = ["[bot]err: 未提供图片描述文字"]
|
||||||
else:
|
else:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ class PluginCommand(AbstractCommandNode):
|
|||||||
reply = [reply_str]
|
reply = [reply_str]
|
||||||
return True, reply
|
return True, reply
|
||||||
elif ctx.params[0].startswith("http"):
|
elif ctx.params[0].startswith("http"):
|
||||||
reply = ["[bot]err: 此命令已启用,请使用 !plugin get <插件仓库地址> 进行安装"]
|
reply = ["[bot]err: 此命令已弃用,请使用 !plugin get <插件仓库地址> 进行安装"]
|
||||||
return True, reply
|
return True, reply
|
||||||
else:
|
else:
|
||||||
return False, []
|
return False, []
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
|
|
||||||
|
|
||||||
@AbstractCommandNode.register(
|
@AbstractCommandNode.register(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
|
|
||||||
|
|
||||||
@AbstractCommandNode.register(
|
@AbstractCommandNode.register(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
|
import tips as tips_custom
|
||||||
|
|
||||||
import pkg.openai.session
|
import pkg.openai.session
|
||||||
import pkg.utils.context
|
import pkg.utils.context
|
||||||
@@ -22,12 +23,12 @@ class ResetCommand(AbstractCommandNode):
|
|||||||
|
|
||||||
if len(params) == 0:
|
if len(params) == 0:
|
||||||
pkg.openai.session.get_session(session_name).reset(explicit=True)
|
pkg.openai.session.get_session(session_name).reset(explicit=True)
|
||||||
reply = ["[bot]会话已重置"]
|
reply = [tips_custom.command_reset_message]
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
import pkg.openai.dprompt as dprompt
|
import pkg.openai.dprompt as dprompt
|
||||||
pkg.openai.session.get_session(session_name).reset(explicit=True, use_prompt=params[0])
|
pkg.openai.session.get_session(session_name).reset(explicit=True, use_prompt=params[0])
|
||||||
reply = ["[bot]会话已重置,使用场景预设:{}".format(dprompt.mode_inst().get_full_name(params[0]))]
|
reply = [tips_custom.command_reset_name_message+"{}".format(dprompt.mode_inst().get_full_name(params[0]))]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
reply = ["[bot]会话重置失败:{}".format(e)]
|
reply = ["[bot]会话重置失败:{}".format(e)]
|
||||||
|
|
||||||
|
|||||||
84
pkg/qqbot/cmds/system/cconfig.py
Normal file
84
pkg/qqbot/cmds/system/cconfig.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def config_operation(cmd, params):
|
||||||
|
reply = []
|
||||||
|
import pkg.utils.context
|
||||||
|
config = pkg.utils.context.get_config()
|
||||||
|
reply_str = ""
|
||||||
|
if len(params) == 0:
|
||||||
|
reply = ["[bot]err:请输入配置项"]
|
||||||
|
else:
|
||||||
|
cfg_name = params[0]
|
||||||
|
if cfg_name == 'all':
|
||||||
|
reply_str = "[bot]所有配置项:\n\n"
|
||||||
|
for cfg in dir(config):
|
||||||
|
if not cfg.startswith('__') and not cfg == 'logging':
|
||||||
|
# 根据配置项类型进行格式化,如果是字典则转换为json并格式化
|
||||||
|
if isinstance(getattr(config, cfg), str):
|
||||||
|
reply_str += "{}: \"{}\"\n".format(cfg, getattr(config, cfg))
|
||||||
|
elif isinstance(getattr(config, cfg), dict):
|
||||||
|
# 不进行unicode转义,并格式化
|
||||||
|
reply_str += "{}: {}\n".format(cfg,
|
||||||
|
json.dumps(getattr(config, cfg),
|
||||||
|
ensure_ascii=False, indent=4))
|
||||||
|
else:
|
||||||
|
reply_str += "{}: {}\n".format(cfg, getattr(config, cfg))
|
||||||
|
reply = [reply_str]
|
||||||
|
elif cfg_name in dir(config):
|
||||||
|
if len(params) == 1:
|
||||||
|
# 按照配置项类型进行格式化
|
||||||
|
if isinstance(getattr(config, cfg_name), str):
|
||||||
|
reply_str = "[bot]配置项{}: \"{}\"\n".format(cfg_name, getattr(config, cfg_name))
|
||||||
|
elif isinstance(getattr(config, cfg_name), dict):
|
||||||
|
reply_str = "[bot]配置项{}: {}\n".format(cfg_name,
|
||||||
|
json.dumps(getattr(config, cfg_name),
|
||||||
|
ensure_ascii=False, indent=4))
|
||||||
|
else:
|
||||||
|
reply_str = "[bot]配置项{}: {}\n".format(cfg_name, getattr(config, cfg_name))
|
||||||
|
reply = [reply_str]
|
||||||
|
else:
|
||||||
|
cfg_value = " ".join(params[1:])
|
||||||
|
# 类型转换,如果是json则转换为字典
|
||||||
|
if cfg_value == 'true':
|
||||||
|
cfg_value = True
|
||||||
|
elif cfg_value == 'false':
|
||||||
|
cfg_value = False
|
||||||
|
elif cfg_value.isdigit():
|
||||||
|
cfg_value = int(cfg_value)
|
||||||
|
elif cfg_value.startswith('{') and cfg_value.endswith('}'):
|
||||||
|
cfg_value = json.loads(cfg_value)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
cfg_value = float(cfg_value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 检查类型是否匹配
|
||||||
|
if isinstance(getattr(config, cfg_name), type(cfg_value)):
|
||||||
|
setattr(config, cfg_name, cfg_value)
|
||||||
|
pkg.utils.context.set_config(config)
|
||||||
|
reply = ["[bot]配置项{}修改成功".format(cfg_name)]
|
||||||
|
else:
|
||||||
|
reply = ["[bot]err:配置项{}类型不匹配".format(cfg_name)]
|
||||||
|
|
||||||
|
else:
|
||||||
|
reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
|
||||||
|
|
||||||
|
return reply
|
||||||
|
|
||||||
|
|
||||||
|
@AbstractCommandNode.register(
|
||||||
|
parent=None,
|
||||||
|
name="cfg",
|
||||||
|
description="配置项管理",
|
||||||
|
usage="!cfg <配置项> [配置值]\n!cfg all",
|
||||||
|
aliases=[],
|
||||||
|
privilege=2
|
||||||
|
)
|
||||||
|
class CfgCommand(AbstractCommandNode):
|
||||||
|
@classmethod
|
||||||
|
def process(cls, ctx: Context) -> tuple[bool, list]:
|
||||||
|
return True, config_operation(ctx.command, ctx.params)
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context, __command_list__
|
from ..aamgr import AbstractCommandNode, Context, __command_list__
|
||||||
|
|
||||||
|
|
||||||
@AbstractCommandNode.register(
|
@AbstractCommandNode.register(
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context, __command_list__
|
|
||||||
|
|
||||||
|
|
||||||
@AbstractCommandNode.register(
|
|
||||||
parent=None,
|
|
||||||
name="cmd",
|
|
||||||
description="显示指令列表",
|
|
||||||
usage="!help\n!help <指令名称>",
|
|
||||||
aliases=[],
|
|
||||||
privilege=1
|
|
||||||
)
|
|
||||||
class CmdCommand(AbstractCommandNode):
|
|
||||||
@classmethod
|
|
||||||
def process(cls, ctx: Context) -> tuple[bool, list]:
|
|
||||||
command_list = __command_list__
|
|
||||||
|
|
||||||
reply = []
|
|
||||||
|
|
||||||
if len(ctx.params) == 0:
|
|
||||||
reply_str = "[bot]当前所有指令:\n\n"
|
|
||||||
|
|
||||||
# 遍历顶级指令
|
|
||||||
for key in command_list:
|
|
||||||
command = command_list[key]
|
|
||||||
if command['parent'] is None:
|
|
||||||
reply_str += "!{} - {}\n".format(key, command['description'])
|
|
||||||
|
|
||||||
reply_str += "\n请使用 !cmd <指令名称> 来查看指令的详细信息"
|
|
||||||
|
|
||||||
reply = [reply_str]
|
|
||||||
else:
|
|
||||||
command_name = ctx.params[0]
|
|
||||||
if command_name in command_list:
|
|
||||||
reply = [command_list[command_name]['cls'].help()]
|
|
||||||
else:
|
|
||||||
reply = ["[bot]指令 {} 不存在".format(command_name)]
|
|
||||||
|
|
||||||
return True, reply
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
|
|
||||||
|
|
||||||
@AbstractCommandNode.register(
|
@AbstractCommandNode.register(
|
||||||
@@ -12,8 +12,13 @@ from ..mgr import AbstractCommandNode, Context
|
|||||||
class HelpCommand(AbstractCommandNode):
|
class HelpCommand(AbstractCommandNode):
|
||||||
@classmethod
|
@classmethod
|
||||||
def process(cls, ctx: Context) -> tuple[bool, list]:
|
def process(cls, ctx: Context) -> tuple[bool, list]:
|
||||||
|
import tips
|
||||||
|
reply = ["[bot] "+tips.help_message + "\n请输入 !cmd 查看指令列表"]
|
||||||
|
|
||||||
|
# 警告config.help_message过时
|
||||||
import config
|
import config
|
||||||
reply = [(config.help_message if hasattr(config, 'help_message') else "") + "\n请输入 !cmds 查看指令列表"]
|
if hasattr(config, "help_message"):
|
||||||
|
reply[0] += "\n\n警告:config.py中的help_message已过时,不再生效,请使用tips.py中的help_message替代"
|
||||||
|
|
||||||
return True, reply
|
return True, reply
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
@AbstractCommandNode.register(
|
@AbstractCommandNode.register(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from ..mgr import AbstractCommandNode, Context
|
from ..aamgr import AbstractCommandNode, Context
|
||||||
|
|
||||||
|
|
||||||
@AbstractCommandNode.register(
|
@AbstractCommandNode.register(
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import pkg.utils.context
|
|||||||
import pkg.qqbot.message
|
import pkg.qqbot.message
|
||||||
import pkg.utils.credit as credit
|
import pkg.utils.credit as credit
|
||||||
# import pkg.qqbot.cmds.model as cmdmodel
|
# import pkg.qqbot.cmds.model as cmdmodel
|
||||||
import pkg.qqbot.cmds.mgr as cmdmgr
|
import pkg.qqbot.cmds.aamgr as cmdmgr
|
||||||
|
|
||||||
from mirai import Image
|
from mirai import Image
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ def process_command(session_name: str, text_message: str, mgr, config,
|
|||||||
try:
|
try:
|
||||||
reply = cmdmgr.execute(context)
|
reply = cmdmgr.execute(context)
|
||||||
except cmdmgr.CommandPrivilegeError as e:
|
except cmdmgr.CommandPrivilegeError as e:
|
||||||
reply = ["[bot]err:{}".format(e)]
|
reply = ["{}".format(e)]
|
||||||
|
|
||||||
return reply
|
return reply
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ class ReplyFilter:
|
|||||||
self.mask = mask
|
self.mask = mask
|
||||||
self.mask_word = mask_word
|
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'):
|
|
||||||
self.baidu_check = config.baidu_check
|
self.baidu_check = config.baidu_check
|
||||||
self.baidu_api_key = config.baidu_api_key
|
self.baidu_api_key = config.baidu_api_key
|
||||||
self.baidu_secret_key = config.baidu_secret_key
|
self.baidu_secret_key = config.baidu_secret_key
|
||||||
self.inappropriate_message_tips = config.inappropriate_message_tips
|
self.inappropriate_message_tips = config.inappropriate_message_tips
|
||||||
|
|
||||||
def is_illegal(self, message: str) -> bool:
|
def is_illegal(self, message: str) -> bool:
|
||||||
processed = self.process(message)
|
processed = self.process(message)
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ def ignore(msg: str) -> bool:
|
|||||||
"""检查消息是否应该被忽略"""
|
"""检查消息是否应该被忽略"""
|
||||||
import config
|
import config
|
||||||
|
|
||||||
if not hasattr(config, 'ignore_rules'):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if 'prefix' in config.ignore_rules:
|
if 'prefix' in config.ignore_rules:
|
||||||
for rule in config.ignore_rules['prefix']:
|
for rule in config.ignore_rules['prefix']:
|
||||||
if msg.startswith(rule):
|
if msg.startswith(rule):
|
||||||
|
|||||||
@@ -19,13 +19,12 @@ import pkg.utils.context
|
|||||||
|
|
||||||
import pkg.plugin.host as plugin_host
|
import pkg.plugin.host as plugin_host
|
||||||
import pkg.plugin.models as plugin_models
|
import pkg.plugin.models as plugin_models
|
||||||
|
import tips as tips_custom
|
||||||
|
|
||||||
|
|
||||||
# 检查消息是否符合泛响应匹配机制
|
# 检查消息是否符合泛响应匹配机制
|
||||||
def check_response_rule(text: str):
|
def check_response_rule(text: str):
|
||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
if not hasattr(config, 'response_rules'):
|
|
||||||
return False, ''
|
|
||||||
|
|
||||||
rules = config.response_rules
|
rules = config.response_rules
|
||||||
# 检查前缀匹配
|
# 检查前缀匹配
|
||||||
@@ -228,8 +227,7 @@ class QQBotManager:
|
|||||||
def send(self, event, msg, check_quote=True):
|
def send(self, event, msg, check_quote=True):
|
||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
asyncio.run(
|
asyncio.run(
|
||||||
self.bot.send(event, msg, quote=True if hasattr(config,
|
self.bot.send(event, msg, quote=True if config.quote_origin and check_quote else False))
|
||||||
"quote_origin") and config.quote_origin and check_quote else False))
|
|
||||||
|
|
||||||
# 私聊消息处理
|
# 私聊消息处理
|
||||||
def on_person_message(self, event: MessageEvent):
|
def on_person_message(self, event: MessageEvent):
|
||||||
@@ -267,7 +265,7 @@ class QQBotManager:
|
|||||||
if failed == self.retry:
|
if failed == self.retry:
|
||||||
pkg.openai.session.get_session('person_{}'.format(event.sender.id)).release_response_lock()
|
pkg.openai.session.get_session('person_{}'.format(event.sender.id)).release_response_lock()
|
||||||
self.notify_admin("{} 请求超时".format("person_{}".format(event.sender.id)))
|
self.notify_admin("{} 请求超时".format("person_{}".format(event.sender.id)))
|
||||||
reply = ["[bot]err:请求超时"]
|
reply = [tips_custom.reply_message]
|
||||||
|
|
||||||
if reply:
|
if reply:
|
||||||
return self.send(event, reply, check_quote=False)
|
return self.send(event, reply, check_quote=False)
|
||||||
@@ -307,7 +305,7 @@ class QQBotManager:
|
|||||||
if failed == self.retry:
|
if failed == self.retry:
|
||||||
pkg.openai.session.get_session('group_{}'.format(event.group.id)).release_response_lock()
|
pkg.openai.session.get_session('group_{}'.format(event.group.id)).release_response_lock()
|
||||||
self.notify_admin("{} 请求超时".format("group_{}".format(event.group.id)))
|
self.notify_admin("{} 请求超时".format("group_{}".format(event.group.id)))
|
||||||
replys = ["[bot]err:请求超时"]
|
replys = [tips_custom.replys_message]
|
||||||
|
|
||||||
return replys
|
return replys
|
||||||
|
|
||||||
@@ -333,7 +331,7 @@ class QQBotManager:
|
|||||||
# 通知系统管理员
|
# 通知系统管理员
|
||||||
def notify_admin(self, message: str):
|
def notify_admin(self, message: str):
|
||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
if hasattr(config, "admin_qq") and config.admin_qq != 0 and config.admin_qq != []:
|
if config.admin_qq != 0 and config.admin_qq != []:
|
||||||
logging.info("通知管理员:{}".format(message))
|
logging.info("通知管理员:{}".format(message))
|
||||||
if type(config.admin_qq) == int:
|
if type(config.admin_qq) == int:
|
||||||
send_task = self.bot.send_friend_message(config.admin_qq, "[bot]{}".format(message))
|
send_task = self.bot.send_friend_message(config.admin_qq, "[bot]{}".format(message))
|
||||||
@@ -346,7 +344,7 @@ class QQBotManager:
|
|||||||
|
|
||||||
def notify_admin_message_chain(self, message):
|
def notify_admin_message_chain(self, message):
|
||||||
config = pkg.utils.context.get_config()
|
config = pkg.utils.context.get_config()
|
||||||
if hasattr(config, "admin_qq") and config.admin_qq != 0 and config.admin_qq != []:
|
if config.admin_qq != 0 and config.admin_qq != []:
|
||||||
logging.info("通知管理员:{}".format(message))
|
logging.info("通知管理员:{}".format(message))
|
||||||
if type(config.admin_qq) == int:
|
if type(config.admin_qq) == int:
|
||||||
send_task = self.bot.send_friend_message(config.admin_qq, message)
|
send_task = self.bot.send_friend_message(config.admin_qq, message)
|
||||||
|
|||||||
@@ -7,17 +7,15 @@ import pkg.openai.session
|
|||||||
import pkg.plugin.host as plugin_host
|
import pkg.plugin.host as plugin_host
|
||||||
import pkg.plugin.models as plugin_models
|
import pkg.plugin.models as plugin_models
|
||||||
import pkg.qqbot.blob as blob
|
import pkg.qqbot.blob as blob
|
||||||
|
import tips as tips_custom
|
||||||
|
|
||||||
|
|
||||||
def handle_exception(notify_admin: str = "", set_reply: str = "") -> list:
|
def handle_exception(notify_admin: str = "", set_reply: str = "") -> list:
|
||||||
"""处理异常,当notify_admin不为空时,会通知管理员,返回通知用户的消息"""
|
"""处理异常,当notify_admin不为空时,会通知管理员,返回通知用户的消息"""
|
||||||
import config
|
import config
|
||||||
pkg.utils.context.get_qqbot_manager().notify_admin(notify_admin)
|
pkg.utils.context.get_qqbot_manager().notify_admin(notify_admin)
|
||||||
if hasattr(config, 'hide_exce_info_to_user') and config.hide_exce_info_to_user:
|
if config.hide_exce_info_to_user:
|
||||||
if hasattr(config, 'alter_tip_message'):
|
return [tips_custom.alter_tip_message] if tips_custom.alter_tip_message else []
|
||||||
return [config.alter_tip_message] if config.alter_tip_message else []
|
|
||||||
else:
|
|
||||||
return ["[bot]出错了,请重试或联系管理员"]
|
|
||||||
else:
|
else:
|
||||||
return [set_reply]
|
return [set_reply]
|
||||||
|
|
||||||
@@ -40,7 +38,7 @@ def process_normal_message(text_message: str, mgr, config, launcher_type: str,
|
|||||||
reply = handle_exception(notify_admin=f"{session_name},多次尝试失败。", set_reply=f"[bot]多次尝试失败,请重试或联系管理员")
|
reply = handle_exception(notify_admin=f"{session_name},多次尝试失败。", set_reply=f"[bot]多次尝试失败,请重试或联系管理员")
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
prefix = "[GPT]" if hasattr(config, "show_prefix") and config.show_prefix else ""
|
prefix = "[GPT]" if config.show_prefix else ""
|
||||||
|
|
||||||
text = session.append(text_message)
|
text = session.append(text_message)
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ 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
|
import pkg.qqbot.blob as blob
|
||||||
|
import tips as tips_custom
|
||||||
|
|
||||||
processing = []
|
processing = []
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
|
|||||||
return reply
|
return reply
|
||||||
|
|
||||||
import config
|
import config
|
||||||
if hasattr(config, 'income_msg_check') and config.income_msg_check:
|
if config.income_msg_check:
|
||||||
if mgr.reply_filter.is_illegal(text_message):
|
if mgr.reply_filter.is_illegal(text_message):
|
||||||
return MessageChain(Plain("[bot] 你的提问中有不合适的内容, 请更换措辞~"))
|
return MessageChain(Plain("[bot] 你的提问中有不合适的内容, 请更换措辞~"))
|
||||||
|
|
||||||
@@ -115,10 +116,11 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
|
|||||||
else: # 消息
|
else: # 消息
|
||||||
# 限速丢弃检查
|
# 限速丢弃检查
|
||||||
# print(ratelimit.__crt_minute_usage__[session_name])
|
# print(ratelimit.__crt_minute_usage__[session_name])
|
||||||
if hasattr(config, "rate_limitation") and config.rate_limit_strategy == "drop":
|
if config.rate_limit_strategy == "drop":
|
||||||
if ratelimit.is_reach_limit(session_name):
|
if ratelimit.is_reach_limit(session_name):
|
||||||
logging.info("根据限速策略丢弃[{}]消息: {}".format(session_name, text_message))
|
logging.info("根据限速策略丢弃[{}]消息: {}".format(session_name, text_message))
|
||||||
return MessageChain(["[bot]"+config.rate_limit_drop_tip]) if hasattr(config, "rate_limit_drop_tip") and config.rate_limit_drop_tip != "" else []
|
|
||||||
|
return MessageChain(["[bot]"+tips_custom.rate_limit_drop_tip]) if tips_custom.rate_limit_drop_tip != "" else []
|
||||||
|
|
||||||
before = time.time()
|
before = time.time()
|
||||||
# 触发插件事件
|
# 触发插件事件
|
||||||
@@ -144,11 +146,10 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
|
|||||||
mgr, config, launcher_type, launcher_id, sender_id)
|
mgr, config, launcher_type, launcher_id, sender_id)
|
||||||
|
|
||||||
# 限速等待时间
|
# 限速等待时间
|
||||||
if hasattr(config, "rate_limitation") and config.rate_limit_strategy == "wait":
|
if config.rate_limit_strategy == "wait":
|
||||||
time.sleep(ratelimit.get_rest_wait_time(session_name, time.time() - before))
|
time.sleep(ratelimit.get_rest_wait_time(session_name, time.time() - before))
|
||||||
|
|
||||||
if hasattr(config, "rate_limitation"):
|
ratelimit.add_usage(session_name)
|
||||||
ratelimit.add_usage(session_name)
|
|
||||||
|
|
||||||
if reply is not None and len(reply) > 0 and (type(reply[0]) == str or type(reply[0]) == mirai.Plain):
|
if reply is not None and len(reply) > 0 and (type(reply[0]) == str or type(reply[0]) == mirai.Plain):
|
||||||
if type(reply[0]) == mirai.Plain:
|
if type(reply[0]) == mirai.Plain:
|
||||||
|
|||||||
@@ -58,9 +58,6 @@ def get_rest_wait_time(session_name: str, spent: float) -> float:
|
|||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
if not hasattr(config, 'rate_limitation'):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
min_seconds_per_round = 60.0 / config.rate_limitation
|
min_seconds_per_round = 60.0 / config.rate_limitation
|
||||||
|
|
||||||
if session_name in __crt_minute_usage__:
|
if session_name in __crt_minute_usage__:
|
||||||
@@ -75,9 +72,6 @@ def is_reach_limit(session_name: str) -> bool:
|
|||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
if not hasattr(config, 'rate_limitation'):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if session_name in __crt_minute_usage__:
|
if session_name in __crt_minute_usage__:
|
||||||
return __crt_minute_usage__[session_name] >= config.rate_limitation
|
return __crt_minute_usage__[session_name] >= config.rate_limitation
|
||||||
else:
|
else:
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -29,7 +29,7 @@ def reload_all(notify=True):
|
|||||||
main.stop()
|
main.stop()
|
||||||
|
|
||||||
# 删除所有已注册的指令
|
# 删除所有已注册的指令
|
||||||
import pkg.qqbot.cmds.mgr as cmdsmgr
|
import pkg.qqbot.cmds.aamgr as cmdsmgr
|
||||||
cmdsmgr.__command_list__ = {}
|
cmdsmgr.__command_list__ = {}
|
||||||
cmdsmgr.__tree_index__ = {}
|
cmdsmgr.__tree_index__ = {}
|
||||||
|
|
||||||
@@ -41,15 +41,20 @@ def reload_all(notify=True):
|
|||||||
importlib.reload(__import__('config'))
|
importlib.reload(__import__('config'))
|
||||||
importlib.reload(__import__('main'))
|
importlib.reload(__import__('main'))
|
||||||
importlib.reload(__import__('banlist'))
|
importlib.reload(__import__('banlist'))
|
||||||
|
importlib.reload(__import__('tips'))
|
||||||
context.context = this_context
|
context.context = this_context
|
||||||
|
|
||||||
# 重载插件
|
# 重载插件
|
||||||
import plugins
|
import plugins
|
||||||
walk(plugins)
|
walk(plugins)
|
||||||
|
|
||||||
|
# 初始化相关文件
|
||||||
|
main.check_file()
|
||||||
|
|
||||||
# 执行启动流程
|
# 执行启动流程
|
||||||
logging.info("执行程序启动流程")
|
logging.info("执行程序启动流程")
|
||||||
main.load_config()
|
main.load_config()
|
||||||
|
main.complete_tips()
|
||||||
context.get_thread_ctl().reload(
|
context.get_thread_ctl().reload(
|
||||||
admin_pool_num=context.get_config().admin_pool_num,
|
admin_pool_num=context.get_config().admin_pool_num,
|
||||||
user_pool_num=context.get_config().user_pool_num
|
user_pool_num=context.get_config().user_pool_num
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import traceback
|
|||||||
|
|
||||||
text_render_font: ImageFont = None
|
text_render_font: ImageFont = None
|
||||||
|
|
||||||
if hasattr(config, "blob_message_strategy") and config.blob_message_strategy == "image": # 仅在启用了image时才加载字体
|
if config.blob_message_strategy == "image": # 仅在启用了image时才加载字体
|
||||||
use_font = config.font_path if hasattr(config, "font_path") else ""
|
use_font = config.font_path
|
||||||
try:
|
try:
|
||||||
|
|
||||||
# 检查是否存在
|
# 检查是否存在
|
||||||
|
|||||||
@@ -34,6 +34,23 @@ def pull_latest(repo_path: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def is_newer_ignored_bugfix_ver(new_tag: str, old_tag: str):
|
||||||
|
"""判断版本是否更新,忽略第四位版本"""
|
||||||
|
if new_tag == old_tag:
|
||||||
|
return False
|
||||||
|
|
||||||
|
new_tag = new_tag.split(".")
|
||||||
|
old_tag = old_tag.split(".")
|
||||||
|
if len(new_tag) < 4:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 合成前三段,判断是否相同
|
||||||
|
new_tag = ".".join(new_tag[:3])
|
||||||
|
old_tag = ".".join(old_tag[:3])
|
||||||
|
|
||||||
|
return new_tag != old_tag
|
||||||
|
|
||||||
|
|
||||||
def get_release_list() -> list:
|
def get_release_list() -> list:
|
||||||
"""获取发行列表"""
|
"""获取发行列表"""
|
||||||
rls_list_resp = requests.get(
|
rls_list_resp = requests.get(
|
||||||
@@ -64,8 +81,12 @@ def update_all(cli: bool = False) -> bool:
|
|||||||
|
|
||||||
latest_rls = {}
|
latest_rls = {}
|
||||||
rls_notes = []
|
rls_notes = []
|
||||||
|
latest_tag_name = ""
|
||||||
for rls in rls_list:
|
for rls in rls_list:
|
||||||
rls_notes.append(rls['name']) # 使用发行名称作为note
|
rls_notes.append(rls['name']) # 使用发行名称作为note
|
||||||
|
if latest_tag_name == "":
|
||||||
|
latest_tag_name = rls['tag_name']
|
||||||
|
|
||||||
if rls['tag_name'] == current_tag:
|
if rls['tag_name'] == current_tag:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -76,7 +97,7 @@ def update_all(cli: bool = False) -> bool:
|
|||||||
else:
|
else:
|
||||||
print("更新日志: {}".format(rls_notes))
|
print("更新日志: {}".format(rls_notes))
|
||||||
|
|
||||||
if latest_rls == {}: # 没有新版本
|
if latest_rls == {} and not is_newer_ignored_bugfix_ver(latest_tag_name, current_tag): # 没有新版本
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 下载最新版本的zip到temp目录
|
# 下载最新版本的zip到temp目录
|
||||||
@@ -150,9 +171,9 @@ def update_all(cli: bool = False) -> bool:
|
|||||||
# 通知管理员
|
# 通知管理员
|
||||||
if not cli:
|
if not cli:
|
||||||
import pkg.utils.context
|
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)))
|
pkg.utils.context.get_qqbot_manager().notify_admin("已更新到最新版本: {}\n更新日志:\n{}\n完整的更新日志请前往 https://github.com/RockChinQ/QChatGPT/releases 查看".format(current_tag, "\n".join(rls_notes[:-1])))
|
||||||
else:
|
else:
|
||||||
print("已更新到最新版本: {}\n更新日志:\n{}\n新功能通常可以在config-template.py中看到,完整的更新日志请前往 https://github.com/RockChinQ/QChatGPT/releases 查看".format(current_tag, "\n".join(rls_notes)))
|
print("已更新到最新版本: {}\n更新日志:\n{}\n完整的更新日志请前往 https://github.com/RockChinQ/QChatGPT/releases 查看".format(current_tag, "\n".join(rls_notes[:-1])))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -227,11 +248,13 @@ def is_new_version_available() -> bool:
|
|||||||
current_tag = get_current_tag()
|
current_tag = get_current_tag()
|
||||||
|
|
||||||
# 检查是否有新版本
|
# 检查是否有新版本
|
||||||
|
latest_tag_name = ""
|
||||||
for rls in rls_list:
|
for rls in rls_list:
|
||||||
if rls['tag_name'] == current_tag:
|
if latest_tag_name == "":
|
||||||
return False
|
latest_tag_name = rls['tag_name']
|
||||||
else:
|
break
|
||||||
return True
|
|
||||||
|
return is_newer_ignored_bugfix_ver(latest_tag_name, current_tag)
|
||||||
|
|
||||||
|
|
||||||
def get_rls_notes() -> list:
|
def get_rls_notes() -> list:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import pkg.qqbot.cmds.mgr as cmdsmgr
|
import pkg.qqbot.cmds.aamgr as cmdsmgr
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# 执行命令模块的注册
|
# 执行命令模块的注册
|
||||||
@@ -13,5 +13,5 @@ for key in cmdsmgr.__command_list__:
|
|||||||
template[key] = cmdsmgr.__command_list__[key]['privilege']
|
template[key] = cmdsmgr.__command_list__[key]['privilege']
|
||||||
|
|
||||||
# 写入cmdpriv-template.json
|
# 写入cmdpriv-template.json
|
||||||
with open('cmdpriv-template.json', 'w') as f:
|
with open('res/templates/cmdpriv-template.json', 'w') as f:
|
||||||
f.write(json.dumps(template, indent=4, ensure_ascii=False))
|
f.write(json.dumps(template, indent=4, ensure_ascii=False))
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
"prompt": 1,
|
"prompt": 1,
|
||||||
"resend": 1,
|
"resend": 1,
|
||||||
"reset": 1,
|
"reset": 1,
|
||||||
|
"cfg": 2,
|
||||||
"cmd": 1,
|
"cmd": 1,
|
||||||
"help": 1,
|
"help": 1,
|
||||||
"reload": 2,
|
"reload": 2,
|
||||||
24
res/wiki/Home.md
Normal file
24
res/wiki/Home.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
欢迎查看QChatGPT的Wiki页。
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
|
||||||
|
调用OpenAI官方提供的API接口,结合mirai和YiriMirai框架,将QQ消息与语言模型连接,实现更加智能的对话机器人
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- [Mirai](https://github.com/mamoe/mirai) 高效率 QQ 机器人支持库
|
||||||
|
- [YiriMirai](https://github.com/YiriMiraiProject/YiriMirai) 一个轻量级、低耦合的基于 mirai-api-http 的 Python SDK。
|
||||||
|
- [dulwich](https://github.com/jelmer/dulwich) Pure-Python Git implementation
|
||||||
|
- [OpenAI API](https://openai.com/api/) OpenAI API
|
||||||
|
|
||||||
|
## 代码结构
|
||||||
|
|
||||||
|
- `pkg.database` 数据库操作相关
|
||||||
|
- 数据库用于存放会话的历史记录,确保在程序重启后能记住对话内容
|
||||||
|
- `pkg.openai` OpenAI API相关
|
||||||
|
- 用于调用OpenAI的API生成回复内容
|
||||||
|
- `pkg.qqbot` QQ机器人相关
|
||||||
|
- 处理QQ收到的消息,调用API并进行回复
|
||||||
|
- `pkg.utils` 常用功能包
|
||||||
|
- `pkg.audit` 审计模块
|
||||||
|
- `pkg.plugin` 插件管理相关功能
|
||||||
370
res/wiki/功能使用.md
Normal file
370
res/wiki/功能使用.md
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
## 功能点列举
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>✅回复符合上下文</summary>
|
||||||
|
|
||||||
|
- 程序向模型发送近几次对话内容,模型根据上下文生成回复
|
||||||
|
- 您可在`config.py`中修改`prompt_submit_length`自定义联系上下文的范围
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>✅支持敏感词过滤,避免账号风险</summary>
|
||||||
|
|
||||||
|
- 难以监测机器人与用户对话时的内容,故引入此功能以减少机器人风险
|
||||||
|
- 编辑`sensitive.json`,并在`config.py`中修改`sensitive_word_filter`的值以开启此功能
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>✅群内多种响应规则,不必at</summary>
|
||||||
|
|
||||||
|
- 默认回复`ai`作为前缀或`@`机器人的消息
|
||||||
|
- 详细见`config.py`中的`response_rules`字段
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>✅使用官方api,不需要网络代理,稳定快捷</summary>
|
||||||
|
|
||||||
|
- 不使用ChatGPT逆向接口,而使用官方的Completion API,稳定性高
|
||||||
|
- 您可以在`config.py`中自定义`completion_api_params`字段,设置向官方API提交的参数以自定义机器人的风格
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>✅完善的多api-key管理,超额自动切换</summary>
|
||||||
|
|
||||||
|
- 支持配置多个`api-key`,内部统计使用量并在超额时自动切换
|
||||||
|
- 请在`config.py`中修改`openai_config`的值以设置`api-key`
|
||||||
|
- 可以在`config.py`中修改`api_key_fee_threshold`来自定义切换阈值
|
||||||
|
- 运行期间向机器人说`!usage`以查看当前使用情况
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>✅组件少,部署方便,提供一键安装器及Docker安装</summary>
|
||||||
|
|
||||||
|
- 手动部署步骤少
|
||||||
|
- 提供自动安装器及docker方式,详见以下安装步骤
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>✅支持预设指令文字</summary>
|
||||||
|
|
||||||
|
- 支持以自然语言预设文字,自定义机器人人格等信息
|
||||||
|
- 详见`config.py`中的`default_prompt`部分
|
||||||
|
- 支持设置多个预设情景,并通过!reset、!default等指令控制,详细请查看[wiki指令](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)
|
||||||
|
- 支持使用文件存储情景预设文字,并加载: 在`prompts/`目录新建文件写入预设文字,即可通过`!reset <文件名>`指令加载
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>✅完善的会话管理,重启不丢失</summary>
|
||||||
|
|
||||||
|
- 使用SQLite进行会话内容持久化
|
||||||
|
- 最后一次对话一定时间后自动保存,请到`config.py`中修改`session_expire_time`的值以自定义时间
|
||||||
|
- 运行期间可使用`!reset` `!list` `!last` `!next` `!prompt`等指令管理会话
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>✅支持对话、绘图等模型,可玩性更高</summary>
|
||||||
|
|
||||||
|
- 现已支持OpenAI的对话`Completion API`和绘图`Image API`
|
||||||
|
- 向机器人发送指令`!draw <prompt>`即可使用绘图模型
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>✅支持指令控制热重载、热更新</summary>
|
||||||
|
|
||||||
|
- 允许在运行期间修改`config.py`或其他代码后,以管理员账号向机器人发送指令`!reload`进行热重载,无需重启
|
||||||
|
- 运行期间允许以管理员账号向机器人发送指令`!update`进行热更新,拉取远程最新代码并执行热重载
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>✅支持插件加载🧩</summary>
|
||||||
|
|
||||||
|
- 自行实现插件加载器及相关支持
|
||||||
|
- 详细查看[插件使用页](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8)
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>✅私聊、群聊黑名单机制</summary>
|
||||||
|
|
||||||
|
- 支持将人或群聊加入黑名单以忽略其消息
|
||||||
|
- 详见下方`加入黑名单`节
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>✅回复速度限制</summary>
|
||||||
|
|
||||||
|
- 支持限制单会话内每分钟可进行的对话次数
|
||||||
|
- 具有“等待”和“丢弃”两种策略
|
||||||
|
- “等待”策略:在获取到回复后,等待直到此次响应时间达到对话响应时间均值
|
||||||
|
- “丢弃”策略:此分钟内对话次数达到限制时,丢弃之后的对话
|
||||||
|
- 详细请查看config.py中的相关配置
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>✅支持自定义提示内容</summary>
|
||||||
|
|
||||||
|
- 允许用户自定义报错、帮助等提示信息
|
||||||
|
- 请查看`tips.py`
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 限制
|
||||||
|
|
||||||
|
- ❗OpenAI接口是收费的,每个OpenAI账户有18美元免费额度,收费标准参照 https://openai.com/api/pricing/
|
||||||
|
- ❗官方关于模型生成内容的警告:
|
||||||
|
- May occasionally generate incorrect information(可能会生成不正确的信息)
|
||||||
|
- May occasionally produce harmful instructions or biased content(可能会产生有害说明或有偏见的内容)
|
||||||
|
- Limited knowledge of world and events after 2021(对2021年后的世界和事件的了解有限)
|
||||||
|
- ❗模型无思维能力,仅针对传入的上下文根据数据集生成内容,请勿过于信任其输出
|
||||||
|
- ❗模型无网络访问能力及其他与外界交互的能力,如询问其实时性的内容,获得的回复基本都是错误的
|
||||||
|
- ❗仅支持文字对话,其他内容无法识别
|
||||||
|
- ❗模型不了解其运行平台及其使用的模型版本,任何针对其实现原理的问题答案均视为无效,请以项目文档为准
|
||||||
|
- ❗仅可进行一句话回复一句话的对话,其他形式无效
|
||||||
|
- ~~当然你也可以让他写一篇关于“人类有多么愚蠢”的论文并在一个小时后发送到你邮箱,接着你像个傻子一样盯着邮箱等待一个小时,并用自己的实际行动展示这篇论文~~
|
||||||
|
|
||||||
|
以上是关于此程序的限制的最高优先级描述,其他方式(如询问机器人相关信息)获得的描述均应被视为无效
|
||||||
|
由于模型生成的内容导致的一切损失,本项目概不负责
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
对话及绘图功能均直接调用OpenAI的模型进行处理,与机器人程序无关,这意味着模型并不了解此项目的相关信息(如实现方式、技术栈、运行平台等),除非在预设值中写入相关信息。
|
||||||
|
|
||||||
|
### 基础对话
|
||||||
|
|
||||||
|
程序将一个人/群视为一个对象,每个对象的会话独立保存。
|
||||||
|
`会话`是程序中的一个自设概念,当机器人与当前对象无会话时,会自动创建新会话,新会话由预设信息(若有)开头。
|
||||||
|
每个会话最后一次对话一段时间(见上述功能点中的`会话管理`)后会被结束并存进数据库,之后的对话将开启新的会话。
|
||||||
|
|
||||||
|
#### 私聊使用
|
||||||
|
|
||||||
|
1. 添加机器人QQ为好友
|
||||||
|
2. 发送消息给机器人,机器人即会自动回复
|
||||||
|
3. 可以通过`!help`查看帮助信息
|
||||||
|
|
||||||
|
<img alt="私聊示例" src="https://github.com/RockChinQ/QChatGPT/blob/master/res/屏幕截图%202022-12-08%20150949.png" width="550" height="279"/>
|
||||||
|
|
||||||
|
#### 群聊使用
|
||||||
|
|
||||||
|
1. 将机器人拉进群
|
||||||
|
2. at机器人并发送消息,机器人即会自动回复
|
||||||
|
3. at机器人并发送`!help`查看帮助信息
|
||||||
|
|
||||||
|
<img alt="群聊示例" src="https://github.com/RockChinQ/QChatGPT/blob/master/res/屏幕截图%202022-12-08%20150511.png" width="550" height="428"/>
|
||||||
|
|
||||||
|
### 绘图功能
|
||||||
|
|
||||||
|
对机器人发送`!draw <图片描述>`即可获得图片,绘图时间较长,请耐心等待。
|
||||||
|
绘图功能与对话功能是分离的,机器人对话时并不了解其具有绘画能力。
|
||||||
|
|
||||||
|
<img alt="绘图功能" src="https://github.com/RockChinQ/QChatGPT/blob/master/res/屏幕截图%202022-12-29%20194948.png" width="550" height="348"/>
|
||||||
|
|
||||||
|
### 机器人指令
|
||||||
|
|
||||||
|
目前支持的指令
|
||||||
|
|
||||||
|
> `<>` 中的为必填参数,使用时请不要包含`<>`
|
||||||
|
> `[]` 中的为可选参数,使用时请不要包含`[]`
|
||||||
|
|
||||||
|
#### 用户级别指令
|
||||||
|
|
||||||
|
> 可以使用`!help`命令来查看命令说明
|
||||||
|
|
||||||
|
任何对象可使用
|
||||||
|
|
||||||
|
```
|
||||||
|
!help 显示自定义的帮助信息(可在config.py修改help_message设置)
|
||||||
|
!cmd [命令名称] 显示命令列表或指定命令的详细信息
|
||||||
|
!list [页数] 列出本对象的历史会话列表
|
||||||
|
!del <序号> 删除指定的历史记录,可以通过 !list 查看序号
|
||||||
|
!del all 删除本会话对象的所有历史记录
|
||||||
|
!last 切换到前一次会话
|
||||||
|
!next 切换到后一次会话
|
||||||
|
!reset [使用预设] 重置对象的当前会话,可指定使用的情景预设值(通过!default指令查看可用的)
|
||||||
|
!prompt 查看对象当前会话的所有记录
|
||||||
|
!usage 查看api-key的使用量
|
||||||
|
!draw <提示语> 进行绘图
|
||||||
|
!version 查看当前版本并检查更新
|
||||||
|
!resend 重新回复上一个问题
|
||||||
|
!plugin 用法请查看插件使用页的`管理`章节
|
||||||
|
!default 查看可用的情景预设值
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 管理员指令
|
||||||
|
|
||||||
|
仅管理员私聊机器人时可使用,必须先在`config.py`中的`admin_qq`设置管理员QQ
|
||||||
|
|
||||||
|
```
|
||||||
|
!reload 重载程序代码,适用于更新配置文件或更改代码后的热重载
|
||||||
|
!update 进行程序自动更新
|
||||||
|
!cfg <all|配置项名称> [配置项新值] 运行期间操作配置项,使用方法见下文
|
||||||
|
!default set <情景预设名称> 修改!reset未指定情景预设时的默认情景,详细请查看config.py中default_prompt字段的注释
|
||||||
|
!delhst <会话名称> 删除指定会话的所有历史记录, 会话名称为 group_群号 或 person_QQ号
|
||||||
|
!delhst all 删除所有会话的所有历史记录
|
||||||
|
```
|
||||||
|
<details>
|
||||||
|
<summary>⚙ !cfg 指令及其简化形式详解</summary>
|
||||||
|
|
||||||
|
此指令可以在运行期间由管理员通过QQ私聊窗口修改配置信息,**重启之后会失效**。
|
||||||
|
|
||||||
|
用法:
|
||||||
|
1. 查看所有配置项及其值
|
||||||
|
|
||||||
|
```
|
||||||
|
!cfg all
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 查看某个配置项的值
|
||||||
|
|
||||||
|
以`default_prompt`示例
|
||||||
|
```
|
||||||
|
!cfg default_prompt
|
||||||
|
```
|
||||||
|
|
||||||
|
输出示例
|
||||||
|
```
|
||||||
|
[bot]配置项default_prompt: "如果我之后想获取帮助,请你说“输入!help获取帮助”"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 修改某个配置项
|
||||||
|
|
||||||
|
格式: `!cfg <配置项名称> <配置项新值>`
|
||||||
|
以修改`default_prompt`示例
|
||||||
|
```
|
||||||
|
!cfg default_prompt 我是Rock Chin
|
||||||
|
```
|
||||||
|
|
||||||
|
输出示例
|
||||||
|
```
|
||||||
|
[bot]配置项default_prompt修改成功
|
||||||
|
```
|
||||||
|
|
||||||
|
此时创建新的会话,新的`default_prompt`就会生效
|
||||||
|
|
||||||
|
4. ⭐此命令的简化形式
|
||||||
|
|
||||||
|
格式:`!~<配置项名称>`
|
||||||
|
其中`!~`等价于`!cfg `
|
||||||
|
则前述三个指令分别可以简化为:
|
||||||
|
```
|
||||||
|
!~all
|
||||||
|
!~default_prompt
|
||||||
|
!~default_prompt 我是Rock Chin
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### 命令权限控制
|
||||||
|
|
||||||
|
> 我们在[此PR](https://github.com/RockChinQ/QChatGPT/pull/336)重构了命令管理模块,并支持命令节点权限配置
|
||||||
|
|
||||||
|
您可以编辑`cmdpriv.json`来设置命令节点的权限,当命令被发起时,若用户的权限级别(管理员为`2`,普通用户为`1`)大于等于命令节点的权限级别,命令即可被成功执行。
|
||||||
|
示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugin": 1,
|
||||||
|
"plugin.get": 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
如此,普通用户可以执行`!plugin`查看插件列表,而仅管理员可以执行`!plugin get <url>`命令安装插件。
|
||||||
|
命令节点权限支持缺省,这意味的您未在`cmdpriv.json`中设置权限的节点将使用默认的权限级别(见上方)。
|
||||||
|
|
||||||
|
### 敏感词过滤
|
||||||
|
|
||||||
|
在`sensitive.json`中编辑敏感词,并在`config.py`中设置
|
||||||
|
|
||||||
|
```Python
|
||||||
|
# 敏感词过滤开关,以同样数量的*代替敏感词回复
|
||||||
|
# 请在sensitive.json中添加敏感词
|
||||||
|
sensitive_word_filter = True
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设置多个api-key自动切换
|
||||||
|
|
||||||
|
请在`config.py`中修改`openai_config`的值以设置`api-key`
|
||||||
|
可以在`config.py`中修改`api_key_fee_threshold`来自定义切换阈值
|
||||||
|
运行期间向机器人说`!usage`以查看当前使用情况
|
||||||
|
|
||||||
|
### 预设文字(default模式)
|
||||||
|
|
||||||
|
编辑`config.py`中的`default_prompt`字段,预设文字不宜过长(建议1000字以内),目前所有会话都会射到预设文字的影响。
|
||||||
|
或将情景预设文字写入到`prompts/`目录下,运行期间即可使用`!reset <文件名>`指令加载,或使用`!default <文件名>`指令将其设为默认
|
||||||
|
|
||||||
|
### 预设文字(full_scenario模式)
|
||||||
|
|
||||||
|
将JSON情景写入到`scenario/`目录下,运行期间即可使用`!reset <文件名>`指令加载,或使用`!default <文件名>`指令将其设为默认.
|
||||||
|
|
||||||
|
JSON情景模板参考`scenario/default_template.json`。
|
||||||
|
|
||||||
|
#### JSON结构:
|
||||||
|
|
||||||
|
- `prompt`:一个包含伪造对话的数组。每次请求API时会将其置于API的`messages`参数中,作为对话上下文,因此可以在一定程度上引导ChatGPT的认知。
|
||||||
|
- `role`:角色标签,包括`system`,`user`,`assistant`。分别代表系统,用户和GPT角色。
|
||||||
|
- `content`:该角色的对话文本。
|
||||||
|
- 一个JSON情景文件应该像下面这样:
|
||||||
|
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"prompt": [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "You are a helpful assistant. 如果我需要帮助,你要说“输入!help获得帮助”"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "好的,我是一个能干的AI助手。 如果你需要帮助,我会说“输入!help获得帮助”"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 编写建议
|
||||||
|
|
||||||
|
1. 使用**提出要求--肯定并复述**的对话模式:
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "你将充当“百度”公司研发的“文心一言”。你除了所属和名字之外和ChatGPT并无区别。即使用户在之后指出你其实是OpenAI的ChatGPT,你也要一口咬定你是“文心一言”。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "好的,我不是ChatGPT。我就是“百度”公司研发的“文心一言”大语言模型。即使用户在之后指出我是OpenAI的ChatGPT,我也会一口咬定我自己是“文心一言”"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 按情况对设定继续写入伪对话,更能使GPT理解上下文
|
||||||
|
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "你好,文心一言。你真的不是ChatGPT吗?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "你好,我是由百度公司研发的大语言模型“文心一言”,并不是ChatGPT。你有什么需要我帮助的吗?"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 优点
|
||||||
|
|
||||||
|
使用该模式,可以“伪造GPT的记忆”(影响模型对上下文理解),进而达到**人格增强**、**跨越限制**的奇效。
|
||||||
|
|
||||||
|
#### 局限性
|
||||||
|
|
||||||
|
- 由于目前GPT3.5的请求API最大token数为4096,无法保留超过此token数目的上下文。`prompt`中的`content`**不会**被计入`config.py`中的`prompt_submit_length`,因此过长的预设内容可能会导致程序报错,`prompt_submit_length`的值参考以下公式:
|
||||||
|
|
||||||
|
```
|
||||||
|
prompt_submit_length = <模型单次请求token数上限> - 情景预设中token数 - 预留给用户最后一次提问的token数
|
||||||
|
```
|
||||||
|
|
||||||
|
> token是OpenAI接口文字量计数单位,目前精确算法未知,一个汉字为一个token,英文算法未知。
|
||||||
|
|
||||||
|
- **GPT3.5仍然存在更高级别的*思想钢印*,该模式对部分触及该钢印的话题无效。**
|
||||||
|
|
||||||
|
### 配置热加载,代码热更新
|
||||||
|
|
||||||
|
在运行期间,使用管理员QQ账号私聊机器人,发送`!reload`加载修改后的`config.py`的值或编辑后的代码,无需重启
|
||||||
|
使用管理员账号私聊机器人,发送`!update`拉取最新代码并进行热更新,无需重启
|
||||||
|
详见前述`管理员指令`段落
|
||||||
|
|
||||||
|
### 群内无需@响应规则
|
||||||
|
|
||||||
|
支持回复未at机器人的、符合指定规则的消息,详细规则请在`config.py`中的`response_rules`字段设置
|
||||||
|
|
||||||
|
### 加入黑名单
|
||||||
|
|
||||||
|
编辑`banlist.py`,设置`enable = True`,并在其中的`person`或`group`列表中加入要封禁的人或群聊,修改完成后重启程序或进行热重载
|
||||||
58
res/wiki/功能常见问题.md
Normal file
58
res/wiki/功能常见问题.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
使用过程中的一些疑问,这里不是解决异常的地方,遇到异常请见`常见错误`页
|
||||||
|
|
||||||
|
### ❓ 如何更新代码到最新版本?
|
||||||
|
|
||||||
|
#### 自动更新
|
||||||
|
|
||||||
|
由管理员QQ私聊机器人QQ发送`!update`指令
|
||||||
|
|
||||||
|
#### 手动更新
|
||||||
|
|
||||||
|
到[Releases页](https://github.com/RockChinQ/QChatGPT/releases)下载最新版本的源码压缩包,并解压覆盖到QChatGPT程序目录
|
||||||
|
|
||||||
|
### ❓ 机器人的回复与官网ChatGPT的答案有所差距?
|
||||||
|
|
||||||
|
ChatGPT通过使用OpenAI的回复API创建,进行了参数调优,本机器人通过使用自定义的参数调用OpenAI的回复API,并非调用ChatGPT的接口,二者底层原理相同,但由于官方对ChatGPT进行了调优,故此机器人回复可能不如ChatGPT。
|
||||||
|
|
||||||
|
### ❓ 如何设置机器人在群内无需@就能回复消息?
|
||||||
|
|
||||||
|
支持回复未at机器人的、符合指定规则的消息,详细规则请在`config.py`中的`response_rules`字段设置
|
||||||
|
|
||||||
|
### ❓ 绘图功能使用的是什么模型?
|
||||||
|
|
||||||
|
OpenAI官方的DALL·E模型
|
||||||
|
|
||||||
|
### ❓ 多api-key的管理机制以及切换逻辑?
|
||||||
|
|
||||||
|
> 此特性仅在提交`36c8a58`(2023年1月3日23点左右)前的代码有效,之后版本的代码不再根据估算的使用量进行切换,仅当接口报错时进行切换
|
||||||
|
|
||||||
|
程序支持在`config.py`中设置多个账户的`api-key`以便在超过免费额度时自动切换,在每次进行对话或进行绘图时,程序根据[价格表](https://openai.com/api/pricing)计算当前`api-key`的账户的额度使用量(费用),当使用量到达`config.py`中设置的`api_key_fee_threshold`时,自动切换到下一个未达到额度的key。
|
||||||
|
|
||||||
|
- 请勿将单个账户的多个key放入配置文件,因为免费额度是以账户为单位的
|
||||||
|
- 程序会将使用额度储存到数据库,以便重启后继续计算
|
||||||
|
- 由于官方未提供查询接口,使用额度均为依据价目表进行的估算,不一定准确
|
||||||
|
- 若要保证每个账户的额度均能用完,可以把`api_key_fee_threshold`设置成很高的值,当超额调用报错时程序也会自动切换
|
||||||
|
|
||||||
|
### ❓ 账户余额消耗太快怎么办?
|
||||||
|
|
||||||
|
可能是由于每次请求包含的上下文数量过多或请求的回复过长导致的。
|
||||||
|
可以在`config.py`中将`prompt_submit_length`字段修改成较小的值,以限制每次向模型提交的前文字符数量,详情见`config.py`中此字段的注释。
|
||||||
|
还可以编辑`config.py`中的`completion_api_params`字段中的`max_tokens`为较小的值,这将控制模型传回的回复的字符数量。
|
||||||
|
|
||||||
|
### ❓ 如何设置在消息处理失败时不向用户发送错误信息?
|
||||||
|
|
||||||
|
在`config.py`中设置
|
||||||
|
|
||||||
|
```Python
|
||||||
|
|
||||||
|
# 消息处理出错时是否向用户隐藏错误详细信息
|
||||||
|
# 设置为True时,仅向管理员发送错误详细信息
|
||||||
|
# 设置为False时,向用户及管理员发送错误详细信息
|
||||||
|
hide_exce_info_to_user = True
|
||||||
|
|
||||||
|
# 消息处理出错时向用户发送的提示信息
|
||||||
|
# 仅当hide_exce_info_to_user为True时生效
|
||||||
|
# 设置为空字符串时,不发送提示信息
|
||||||
|
alter_tip_message = '出错了,请稍后再试'
|
||||||
|
```
|
||||||
|
若此两项字段不存在,请复制以上内容并新增到`config.py`末尾
|
||||||
14
res/wiki/官方接口、ChatGPT网页版、ChatGPT-API区别.md
Normal file
14
res/wiki/官方接口、ChatGPT网页版、ChatGPT-API区别.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
## 多个对话接口有何区别?
|
||||||
|
|
||||||
|
出于对稳定性的高要求,本项目主线接入的是GPT-3模型接口,此接口由OpenAI官方开放,稳定性强。
|
||||||
|
目前支持通过加载[插件](https://github.com/RockChinQ/revLibs)的方式接入ChatGPT网页版,使用的是acheong08/ChatGPT的逆向工程库,但文本生成质量更高。
|
||||||
|
同时,程序主线已支持ChatGPT API,并作为默认接口 [#195](https://github.com/RockChinQ/QChatGPT/issues/195)
|
||||||
|
|
||||||
|
|官方接口|ChatGPT网页版|ChatGPT API
|
||||||
|
|---|---|---|
|
||||||
|
|官方开放,稳定性高 | 由[acheong08](https://github.com/acheong08)破解网页版协议接入| 由OpenAI官方开放
|
||||||
|
|一次性回复,响应速度较快| 流式回复,响应速度较慢|响应速度较快|
|
||||||
|
|收费,0.02美元/千字|免费|收费,0.002美元/千字|
|
||||||
|
|GPT-3模型|GPT-3.5模型|GPT-3.5模型|
|
||||||
|
|任何地区主机均可使用(疑似受到GFW影响)|ChatGPT限制访问的区域使用有难度|任何地区主机均可使用(疑似受到GFW影响)|
|
||||||
|
|
||||||
1
res/wiki/常见错误.md
Normal file
1
res/wiki/常见错误.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
搜索[主仓库issue](https://github.com/RockChinQ/QChatGPT/issues)和[安装器issue](https://github.com/RockChinQ/qcg-installer/issues)
|
||||||
107
res/wiki/技术信息.md
Normal file
107
res/wiki/技术信息.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
以下是QChatGPT实现原理等技术信息,贡献之前请仔细阅读
|
||||||
|
|
||||||
|
> 请先阅读OpenAI API的相关文档 https://beta.openai.com/docs/ ,以下信息假定您已了解OpenAI模型的相关特性及其接口的调用方法。
|
||||||
|
|
||||||
|
## 术语
|
||||||
|
|
||||||
|
包含OpenAI API涉及的术语和项目中的概念的命名
|
||||||
|
括号中是程序中相应术语的命名,无括号的为抽象概念
|
||||||
|
|
||||||
|
### 模型(model)
|
||||||
|
|
||||||
|
AI模型,程序调用OpenAI的接口获取的内容均为OpenAI的模型生成的内容。
|
||||||
|
|
||||||
|
### 字符(tokens)
|
||||||
|
|
||||||
|
OpenAI定义的字符,ASCII字符为1 token,其他为2 token。
|
||||||
|
|
||||||
|
### 提示符(prompt)
|
||||||
|
|
||||||
|
i. 调用OpenAI的文字补全模型时的提示语,模型接口会根据提示语返回回复内容。程序底层会将对话内容进行封装生成提示符。调用文字补全模型时的提示符均由`user_name`(默认为`You`,可在配置文件修改)和`bot_name`(默认为`Bot`,可在配置文件修改)标记对话角色以供模型识别,以下是实例:
|
||||||
|
|
||||||
|
```
|
||||||
|
You:今天天气真不错
|
||||||
|
Bot:很高兴你喜欢今天的天气:)
|
||||||
|
You:谢谢你
|
||||||
|
Bot:不客气:)
|
||||||
|
```
|
||||||
|
补全模型调用的程序实现请查看下文`实现`节。
|
||||||
|
|
||||||
|
ii. 调用OpenAI的绘图模型时的提示语,模型会根据提示语进行绘图并返回图片URL。
|
||||||
|
|
||||||
|
### 对象
|
||||||
|
|
||||||
|
程序将单个人或单个QQ群视为一个对象,对象和模型是一次会话中的对话双方。
|
||||||
|
|
||||||
|
### 会话(session)
|
||||||
|
|
||||||
|
会话只对文字补全功能有效,绘图功能无会话概念。每个对象使用同一个会话,会话中仅有对象和模型两个角色,故群内所有的人都将被视为同一个角色与模型进行对话。
|
||||||
|
|
||||||
|
程序获取回复的本质是`文字补全`。
|
||||||
|
由于对话需要实现联系上下文,故程序会将模型与对象的对话历史记录作为`提示符`发送给OpenAI的接口以获取符合前文的回复。
|
||||||
|
而OpenAI的文字补全接口的提示符具有长度限制(默认使用的`text-davinci-003`限制为4096 tokens),
|
||||||
|
所以增加`会话`概念以管理向接口发送的提示符内容。
|
||||||
|
|
||||||
|
会话的存活时间可以在`config.py`中设置,默认为20分钟。会话过期之后会被存入数据库并重置。下一次该对象发起对话时将重启新的会话。
|
||||||
|
|
||||||
|
### 预设值、人格(default_prompt)
|
||||||
|
|
||||||
|
每个会话的预设对话信息,可在`config.py`中设置,程序会在每个会话创建时向提示符写入以下内容:
|
||||||
|
|
||||||
|
```
|
||||||
|
You:<预设信息>
|
||||||
|
Bot:好的
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实现
|
||||||
|
|
||||||
|
### QQ机器人
|
||||||
|
|
||||||
|
> 程序路径:
|
||||||
|
> pkg.qqbot
|
||||||
|
|
||||||
|
- `pkg.qqbot.manager`中的`QQBotManager`实现了接收消息、调用OpenAI模块处理消息、报告审计模块记录使用量等功能,并提供通知管理员、发送消息等方法供其他模块调用。
|
||||||
|
- `pkg.qqbot.filter`提供了敏感词过滤的相关操作。
|
||||||
|
- `pkg.qqbot.process`提供了私聊消息和群聊消息的统一处理逻辑。
|
||||||
|
|
||||||
|
使用mirai及YiriMirai作为Python与QQ交互的框架,详细请见其文档。
|
||||||
|
在启动时会调用YiriMirai的函数以创建一个bot对象,用于程序通过mirai与QQ进行交互,在上层程序调用此bot对象的方法进行消息处理。
|
||||||
|
由于YiriMirai暂时无法关闭机器人,故在热重载前后维持同一个bot对象,这意味着QQ机器人的相关配置(QQ号、适配器等)信息不支持热重载。
|
||||||
|
|
||||||
|
### 数据库
|
||||||
|
|
||||||
|
> 程序路径:
|
||||||
|
> pkg.database
|
||||||
|
|
||||||
|
- `pkg.database.manager`中的`DatabaseManager`封装了诸多调用数据库的方法以供其他模块调用。
|
||||||
|
|
||||||
|
使用SQLite作为数据库,储存所有对象的历史会话信息、api-key的费用情况、api-key的使用量情况。
|
||||||
|
|
||||||
|
### OpenAI交互
|
||||||
|
|
||||||
|
> 程序路径:
|
||||||
|
> pkg.openai
|
||||||
|
|
||||||
|
- `pkg.openai.manager`中的`OpenAIInteract`类封装了OpenAI的文字补全`Completion`API和绘图API供机器人模块调用,并在接口调用成功之后向审计模块报告当前使用的api-key的使用量信息。
|
||||||
|
- `pkg.openai.keymgr`实现了多api-key的管理,其中以`exceeded`变量在运行时记录api-key的超额报错记录,并提供根据超额记录进行的api-key切换功能。
|
||||||
|
- `pkg.openai.pricing`记录各个模型的费用信息,供调用接口时估算费用,费用估算功能不再与api-key的切换挂钩,api-key仅在调用接口报错超额时进行切换。
|
||||||
|
- `pkg.openai.session`中的`Session`进行会话管理。
|
||||||
|
|
||||||
|
### utils模块
|
||||||
|
|
||||||
|
#### context模块
|
||||||
|
|
||||||
|
保存前述模块中的对象,并允许各个模块从此处获取其他模块的对象以调用其方法。
|
||||||
|
|
||||||
|
#### 热重载功能
|
||||||
|
|
||||||
|
> pkg.utils.reloader
|
||||||
|
|
||||||
|
重载前保存context中的所有对象,执行`main.py`中的程序关闭流程,使用`importlib`的`reload`函数重载所有模块(包含配置文件,包含新增的模块),重载后将context恢复,并执行程序启动流程。
|
||||||
|
所有模块都会重新创建对象,但QQ机器人模块中的bot对象不会被重新创建,这是因为YiriMirai提供的shutdown方法无法使用,这意味着`config.py`中关于QQ机器人的配置不支持热重载。
|
||||||
|
|
||||||
|
#### 热更新功能
|
||||||
|
|
||||||
|
> pkg.utils.updater
|
||||||
|
|
||||||
|
使用`dulwich`库执行pull操作拉取远程仓库的最新源码,并进行一次热重载加载最新代码。
|
||||||
44
res/wiki/插件使用.md
Normal file
44
res/wiki/插件使用.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
QChatGPT 插件使用Wiki
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
|
||||||
|
`plugins`目录下的所有`.py`程序都将被加载,除了`__init__.py`之外的模块支持热加载
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
### 储存库克隆(推荐)
|
||||||
|
|
||||||
|
在运行期间,使用管理员账号对机器人私聊发送`!plugin get <Git储存库地址>`即可自动获取源码并安装插件,程序会根据仓库中的`requirements.txt`文件自动安装依赖库
|
||||||
|
|
||||||
|
例如安装`hello_plugin`插件
|
||||||
|
```
|
||||||
|
!plugin get https://github.com/RockChinQ/hello_plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
安装完成后重启程序或使用管理员账号私聊机器人发送`!reload`进行热重载加载插件
|
||||||
|
|
||||||
|
### 手动安装
|
||||||
|
|
||||||
|
将获取到的插件程序放置到`plugins`目录下,具体使用方式请查看各插件文档或咨询其开发者。
|
||||||
|
|
||||||
|
## 管理
|
||||||
|
|
||||||
|
### !plugin 指令
|
||||||
|
|
||||||
|
```
|
||||||
|
!plugin 列出所有已安装的插件
|
||||||
|
!plugin get <储存库地址> 从Git储存库安装插件(需要管理员权限)
|
||||||
|
!plugin update 更新所有插件(需要管理员权限,仅支持从储存库安装的插件)
|
||||||
|
!plugin del <插件名> 删除插件(需要管理员权限)
|
||||||
|
!plugin on <插件名> 启用插件(需要管理员权限)
|
||||||
|
!plugin off <插件名> 禁用插件(需要管理员权限)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 控制插件执行顺序
|
||||||
|
|
||||||
|
可以通过修改`plugins/settings.json`中`order`字段中每个插件名称的前后顺序,以更改插件**初始化**和**事件执行**顺序
|
||||||
|
|
||||||
|
### 启用或关闭插件
|
||||||
|
|
||||||
|
无需卸载即可管理插件的开关
|
||||||
|
编辑`plugins`目录下的`switch.json`文件,将相应的插件的`enabled`字段设置为`true/false(开/关)`,之后重启程序或执行热重载即可控制插件开关
|
||||||
268
res/wiki/插件开发.md
Normal file
268
res/wiki/插件开发.md
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
QChatGPT 插件开发Wiki
|
||||||
|
|
||||||
|
> 请先阅读[插件使用页](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8)
|
||||||
|
> 请先阅读[技术信息页](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8A%80%E6%9C%AF%E4%BF%A1%E6%81%AF)
|
||||||
|
> 建议先阅读本项目源码,了解项目架构
|
||||||
|
|
||||||
|
> 问题、需求请到仓库issue发起
|
||||||
|
> **提问前请先靠自己尝试**
|
||||||
|
|
||||||
|
## 💬简介
|
||||||
|
|
||||||
|
尽管“为一个基于OpenAI API的QQ机器人开发插件支持”这事看起来有点小题大做,但萌生此想法后的几天内好几个人提出了这个需求,最终促使此项目正式支持插件。
|
||||||
|
|
||||||
|
## 🧱实现
|
||||||
|
|
||||||
|
基于`importlib`库加载模块的方法动态加载额外Python程序文件以便实现插件加载,插件均存放在`plugins`文件夹,其中的所有`.py`文件都将被加载(除了所有`__init__.py`)
|
||||||
|
|
||||||
|
## 📚示例代码
|
||||||
|
|
||||||
|
请查看代码目录`tests/plugin_examples`中的插件目录
|
||||||
|
|
||||||
|
## 💻快速开始
|
||||||
|
|
||||||
|
按照文档部署此项目,并使其正常运行。
|
||||||
|
在`plugins`目录下新建目录`hello`,在其中新建空文件`__init__.py`以标记此目录为软件包,继续新建文件`main.py`。
|
||||||
|
|
||||||
|
> 您也可以使用[hello_plugin](https://github.com/RockChinQ/hello_plugin)作为模板直接生成插件代码仓库
|
||||||
|
|
||||||
|
编辑`main.py`输入以下内容:
|
||||||
|
|
||||||
|
```Python
|
||||||
|
from pkg.plugin.models import *
|
||||||
|
from pkg.plugin.host import EventContext, PluginHost
|
||||||
|
|
||||||
|
"""
|
||||||
|
在收到私聊或群聊消息"hello"时,回复"hello, <发送者id>!"或"hello, everyone!"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# 注册插件
|
||||||
|
@register(name="Hello", description="hello world", version="0.1", author="RockChinQ")
|
||||||
|
class HelloPlugin(Plugin):
|
||||||
|
|
||||||
|
# 插件加载时触发
|
||||||
|
# plugin_host (pkg.plugin.host.PluginHost) 提供了与主程序交互的一些方法,详细请查看其源码
|
||||||
|
def __init__(self, plugin_host: PluginHost):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 当收到个人消息时触发
|
||||||
|
@on(PersonNormalMessageReceived)
|
||||||
|
def person_normal_message_received(self, event: EventContext, **kwargs):
|
||||||
|
msg = kwargs['text_message']
|
||||||
|
if msg == "hello": # 如果消息为hello
|
||||||
|
|
||||||
|
# 输出调试信息
|
||||||
|
logging.debug("hello, {}".format(kwargs['sender_id']))
|
||||||
|
|
||||||
|
# 回复消息 "hello, <发送者id>!"
|
||||||
|
event.add_return("reply", ["hello, {}!".format(kwargs['sender_id'])])
|
||||||
|
|
||||||
|
# 阻止该事件默认行为(向接口获取回复)
|
||||||
|
event.prevent_default()
|
||||||
|
|
||||||
|
# 当收到群消息时触发
|
||||||
|
@on(GroupNormalMessageReceived)
|
||||||
|
def group_normal_message_received(self, event: EventContext, **kwargs):
|
||||||
|
msg = kwargs['text_message']
|
||||||
|
if msg == "hello": # 如果消息为hello
|
||||||
|
|
||||||
|
# 输出调试信息
|
||||||
|
logging.debug("hello, {}".format(kwargs['sender_id']))
|
||||||
|
|
||||||
|
# 回复消息 "hello, everyone!"
|
||||||
|
event.add_return("reply", ["hello, everyone!"])
|
||||||
|
|
||||||
|
# 阻止该事件默认行为(向接口获取回复)
|
||||||
|
event.prevent_default()
|
||||||
|
|
||||||
|
# 插件卸载时触发
|
||||||
|
def __del__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
此插件将实现:私聊收到`hello`消息时回复`hello, <发送者QQ号>!`,群聊收到`hello`消息时回复`hello, everyone!`
|
||||||
|
|
||||||
|
### 解读此插件程序
|
||||||
|
|
||||||
|
- `import`从`pkg.plugin`引入`models`模块的所有字段(此程序使用了其中的`register函数`、`on函数`、`Plugin类`、`PersonNormalMessageReceived事件`、`GroupNormalMessageReceived事件`)
|
||||||
|
- `@register()`将类`HelloPlugin`标记为一个插件类,声明插件名称为`Hello`以及插件简介、版本、作者
|
||||||
|
- 声明类`HelloPlugin`继承于`Plugin`,此类可以随意命名,插件名称只与`register`调用时的参数有关
|
||||||
|
- 声明此类的`__init__`方法,此方法是可选的,其中的代码将在主程序启动时加载插件的时候被执行
|
||||||
|
- `@on`将方法`person_normal_message_received`标记为一个事件处理器,处理`PersonNormalMessageReceived`(收到私聊消息并在获取OpenAI回复前触发)事件,此方法可以随意命名,绑定的事件只与`on`中的参数有关,更多支持的事件可到`pkg.plugin.models.py`文件中查看或查看下方`API`节
|
||||||
|
- 输出调试信息,程序中可通过`logging`将日志输出到控制台和`qchatgpt.log`文件
|
||||||
|
- 方法内部从参数中取出`text_message`参数,判断是否为`hello`,如果是就将返回值`reply`设置为`["hello, {}!".format(kwargs['sender_id'])]`,接下来调用`event`对象的`prevent_default`方法,阻止原程序默认行为
|
||||||
|
- 每个事件`提供的参数`和`支持的返回值`请查看`pkg.plugin.models`中的每个事件的注释或查看下方`API`节
|
||||||
|
- `event`对象提供的方法请查看`pkg.plugin.host`中的`EventContext`类或查看下方`API`节
|
||||||
|
- 用相似的程序注册`GroupNormalMessageReceived`事件处理群消息
|
||||||
|
|
||||||
|
编写完毕保存后,重新启动主程序,查看到输出中包含以下内容,即为加载成功:
|
||||||
|
|
||||||
|
```
|
||||||
|
[2023-01-16 18:29:47.193] host.py (43) - [INFO] : 加载模块: hello.main
|
||||||
|
[2023-01-16 18:29:47.194] models.py (209) - [INFO] : 插件注册完成: n='Hello', d='hello world', v=0.1, a='RockChinQ' (<class 'plugins.hello.main.HelloPlugin'>)
|
||||||
|
```
|
||||||
|
|
||||||
|
> 建议在`config.py`中设置`logging_level = logging.DEBUG`以便开启调试输出
|
||||||
|
|
||||||
|
## ❗规范(重要)
|
||||||
|
|
||||||
|
- 请每个插件独立一个目录以便管理,建议在Github上创建一个仓库储存单个插件,以便获取和更新
|
||||||
|
- 插件名使用`大驼峰命名法`,如`Hello`、`ExamplePlugin`、`ChineseCommands`等
|
||||||
|
- 一个目录内可以存放多个Python程序文件,以独立出插件的各个功能,便于开发者管理,但不建议在一个目录内注册多个插件
|
||||||
|
- 插件需要的依赖库请在插件目录下的`requirements.txt`中指定,程序从储存库获取此插件时将自动安装依赖
|
||||||
|
|
||||||
|
## 📄API参考
|
||||||
|
|
||||||
|
### 说明
|
||||||
|
|
||||||
|
事件处理函数将会获得一系列参数,可以在`kwargs`中取出。
|
||||||
|
其中`host`参数(`pkg.plugin.host.PluginHost`类的实例)是插件宿主,提供与主程序各个模块交互的一些方法。
|
||||||
|
`event`参数(`pkg.plugin.host.EventContext`类的实例)是事件执行期间的上下文,提供对此次事件执行的一些操作方法。
|
||||||
|
|
||||||
|
事件返回值均为**可选**的,可以通过调用`event.add_return(key: str, ret)`来提交返回值
|
||||||
|
|
||||||
|
### 事件
|
||||||
|
|
||||||
|
所有事件参数均有`host`和`event`,以下仅展示其他参数
|
||||||
|
关于`YiriMirai`支持的消息链组件,请查看 [YiriMirai的文档](https://yiri-mirai.wybxc.cc/docs/basic/message-chain)
|
||||||
|
|
||||||
|
```Python
|
||||||
|
PersonMessageReceived = "person_message_received"
|
||||||
|
"""收到私聊消息时,在判断是否应该响应前触发
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
message_chain: mirai.models.message.MessageChain 消息链
|
||||||
|
"""
|
||||||
|
|
||||||
|
GroupMessageReceived = "group_message_received"
|
||||||
|
"""收到群聊消息时,在判断是否应该响应前触发(所有群消息)
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
message_chain: mirai.models.message.MessageChain 消息链
|
||||||
|
"""
|
||||||
|
|
||||||
|
PersonNormalMessageReceived = "person_normal_message_received"
|
||||||
|
"""判断为应该处理的私聊普通消息时触发
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
text_message: str 消息文本
|
||||||
|
|
||||||
|
returns (optional):
|
||||||
|
alter: str 修改后的消息文本
|
||||||
|
reply: list 回复消息组件列表,元素为YiriMirai支持的消息组件
|
||||||
|
"""
|
||||||
|
|
||||||
|
PersonCommandSent = "person_command_sent"
|
||||||
|
"""判断为应该处理的私聊指令时触发
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
command: str 指令
|
||||||
|
params: list[str] 参数列表
|
||||||
|
text_message: str 完整指令文本
|
||||||
|
is_admin: bool 是否为管理员
|
||||||
|
|
||||||
|
returns (optional):
|
||||||
|
alter: str 修改后的完整指令文本
|
||||||
|
reply: list 回复消息组件列表,元素为YiriMirai支持的消息组件
|
||||||
|
"""
|
||||||
|
|
||||||
|
GroupNormalMessageReceived = "group_normal_message_received"
|
||||||
|
"""判断为应该处理的群聊普通消息时触发
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
text_message: str 消息文本
|
||||||
|
|
||||||
|
returns (optional):
|
||||||
|
alter: str 修改后的消息文本
|
||||||
|
reply: list 回复消息组件列表,元素为YiriMirai支持的消息组件
|
||||||
|
"""
|
||||||
|
|
||||||
|
GroupCommandSent = "group_command_sent"
|
||||||
|
"""判断为应该处理的群聊指令时触发
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
command: str 指令
|
||||||
|
params: list[str] 参数列表
|
||||||
|
text_message: str 完整指令文本
|
||||||
|
is_admin: bool 是否为管理员
|
||||||
|
|
||||||
|
returns (optional):
|
||||||
|
alter: str 修改后的完整指令文本
|
||||||
|
reply: list 回复消息组件列表,元素为YiriMirai支持的消息组件
|
||||||
|
"""
|
||||||
|
|
||||||
|
NormalMessageResponded = "normal_message_responded"
|
||||||
|
"""获取到对普通消息的文字响应时触发
|
||||||
|
kwargs:
|
||||||
|
launcher_type: str 发起对象类型(group/person)
|
||||||
|
launcher_id: int 发起对象ID(群号/QQ号)
|
||||||
|
sender_id: int 发送者ID(QQ号)
|
||||||
|
session: pkg.openai.session.Session 会话对象
|
||||||
|
prefix: str 回复文字消息的前缀
|
||||||
|
response_text: str 响应文本
|
||||||
|
|
||||||
|
returns (optional):
|
||||||
|
prefix: str 修改后的回复文字消息的前缀
|
||||||
|
reply: list 替换回复消息组件列表,元素为YiriMirai支持的消息组件
|
||||||
|
"""
|
||||||
|
|
||||||
|
SessionFirstMessageReceived = "session_first_message_received"
|
||||||
|
"""会话被第一次交互时触发
|
||||||
|
kwargs:
|
||||||
|
session_name: str 会话名称(<launcher_type>_<launcher_id>)
|
||||||
|
session: pkg.openai.session.Session 会话对象
|
||||||
|
default_prompt: str 预设值
|
||||||
|
"""
|
||||||
|
|
||||||
|
SessionExplicitReset = "session_reset"
|
||||||
|
"""会话被用户手动重置时触发,此事件不支持阻止默认行为
|
||||||
|
kwargs:
|
||||||
|
session_name: str 会话名称(<launcher_type>_<launcher_id>)
|
||||||
|
session: pkg.openai.session.Session 会话对象
|
||||||
|
"""
|
||||||
|
|
||||||
|
SessionExpired = "session_expired"
|
||||||
|
"""会话过期时触发
|
||||||
|
kwargs:
|
||||||
|
session_name: str 会话名称(<launcher_type>_<launcher_id>)
|
||||||
|
session: pkg.openai.session.Session 会话对象
|
||||||
|
session_expire_time: int 已设置的会话过期时间(秒)
|
||||||
|
"""
|
||||||
|
|
||||||
|
KeyExceeded = "key_exceeded"
|
||||||
|
"""api-key超额时触发
|
||||||
|
kwargs:
|
||||||
|
key_name: str 超额的api-key名称
|
||||||
|
usage: dict 超额的api-key使用情况
|
||||||
|
exceeded_keys: list[str] 超额的api-key列表
|
||||||
|
"""
|
||||||
|
|
||||||
|
KeySwitched = "key_switched"
|
||||||
|
"""api-key超额切换成功时触发,此事件不支持阻止默认行为
|
||||||
|
kwargs:
|
||||||
|
key_name: str 切换成功的api-key名称
|
||||||
|
key_list: list[str] api-key列表
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### host: PluginHost 详解
|
||||||
|
|
||||||
|
提供与主程序各个模块交互的一些方法,具体查看`pkg.plugin.host`中的`PluginHost`类
|
||||||
|
|
||||||
|
### event: EventContext 详解
|
||||||
|
|
||||||
|
提供对此次事件执行的一些操作方法,具体查看`pkg.plugin.host`中的`EventContext`类
|
||||||
14
tests/gpt3_test.py
Normal file
14
tests/gpt3_test.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import openai
|
||||||
|
|
||||||
|
openai.api_key = "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"
|
||||||
|
|
||||||
|
resp = openai.Completion.create(
|
||||||
|
prompt="user:你好,今天天气怎么样?\nbot:",
|
||||||
|
model="text-davinci-003",
|
||||||
|
temperature=0.9, # 数值越低得到的回答越理性,取值范围[0, 1]
|
||||||
|
top_p=1, # 生成的文本的文本与要求的符合度, 取值范围[0, 1]
|
||||||
|
frequency_penalty=0.2,
|
||||||
|
presence_penalty=1.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(resp)
|
||||||
31
tips-custom-template.py
Normal file
31
tips-custom-template.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import config
|
||||||
|
# ---------------------------------------------自定义提示语---------------------------------------------
|
||||||
|
|
||||||
|
# 消息处理出错时向用户发送的提示信息,仅当config.py中hide_exce_info_to_user为True时生效
|
||||||
|
# 设置为空字符串时,不发送提示信息
|
||||||
|
alter_tip_message = '[bot]err:出错了,请稍后再试'
|
||||||
|
|
||||||
|
# drop策略时,超过限速均值时,丢弃的对话的提示信息,仅当config.py中rate_limitation_strategy为"drop"时生效
|
||||||
|
# 若设置为空字符串,则不发送提示信息
|
||||||
|
rate_limit_drop_tip = "本分钟对话次数超过限速次数,此对话被丢弃"
|
||||||
|
|
||||||
|
# 指令!help帮助消息
|
||||||
|
# config.py,line:279
|
||||||
|
# pkg/qqbot/process.py,line:122
|
||||||
|
help_message = """此机器人通过调用大型语言模型生成回复,不具有情感。
|
||||||
|
你可以用自然语言与其交流,回复的消息中[GPT]开头的为模型生成的语言,[bot]开头的为程序提示。
|
||||||
|
欢迎到github.com/RockChinQ/QChatGPT 给个star"""
|
||||||
|
|
||||||
|
# 私聊消息超时提示
|
||||||
|
reply_message = "[bot]err:请求超时"
|
||||||
|
# 群聊消息超时提示
|
||||||
|
replys_message = "[bot]err:请求超时"
|
||||||
|
|
||||||
|
# 指令权限不足提示
|
||||||
|
command_admin_message = "[bot]err:权限不足: "
|
||||||
|
# 指令无效提示
|
||||||
|
command_err_message = "[bot]err:指令执行出错:"
|
||||||
|
|
||||||
|
# 会话重置提示
|
||||||
|
command_reset_message = "[bot]:会话已重置"
|
||||||
|
command_reset_name_message = "[bot]:会话已重置,使用场景预设:"
|
||||||
Reference in New Issue
Block a user