Merge pull request #677 from RockChinQ/refactor/asyncio/config

Refactor: 配置文件重构
This commit is contained in:
Junyan Qin
2024-02-07 00:09:23 +08:00
committed by GitHub
59 changed files with 288 additions and 926 deletions

View File

@@ -1,52 +0,0 @@
name: Check and Update override_all
on:
push:
paths:
- 'config-template.py'
pull_request:
types:
- closed
branches:
- master
paths:
- 'config-template.py'
jobs:
update-override-all:
name: check and update
if: github.event.pull_request.merged == true || github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.x
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- name: Copy Scripts
run: |
cp res/scripts/generate_override_all.py .
- name: Run generate_override_all.py
run: python3 generate_override_all.py
- name: Check for changes in override-all.json
id: check_changes
run: |
git diff --exit-code override-all.json || echo "::set-output name=changes_detected::true"
- name: Commit and push changes
if: steps.check_changes.outputs.changes_detected == 'true'
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "GitHub Actions"
git add override-all.json
git commit -m "Update override-all.json"
git push

1
.gitignore vendored
View File

@@ -33,3 +33,4 @@ bard.json
!/docker-compose.yaml
res/instance_id.json
.DS_Store
/data

View File

@@ -1,15 +1,8 @@
FROM python:3.10.13-bullseye
WORKDIR /QChatGPT
WORKDIR /app
COPY . /QChatGPT/
COPY . .
RUN ls
RUN python -m pip install -r requirements.txt
RUN python -m pip install -r requirements.txt && \
python -m pip install -U websockets==10.0 && \
python -m pip install -U httpcore httpx openai
# 生成配置文件
RUN python main.py
CMD [ "python", "main.py" ]
CMD [ "python", "start.py" ]

Binary file not shown.

View File

@@ -1,30 +0,0 @@
# 是否处理群聊消息
# 为False时忽略所有群聊消息
# 优先级高于下方禁用列表
enable_group = True
# 是否处理私聊消息
# 为False时忽略所有私聊消息
# 优先级高于下方禁用列表
enable_private = True
# 是否启用禁用列表
enable = True
# 禁用规则(黑名单)
# person为个人其中的QQ号会被禁止与机器人进行私聊或群聊交互
# 示例: person = [2854196310, 1234567890, 9876543210]
# group为群组其中的群号会被禁止与机器人进行交互
# 示例: group = [123456789, 987654321, 1234567890]
#
# 支持正则表达式,字符串都将被识别为正则表达式,例如:
# person = [12345678, 87654321, "2854.*"]
# group = [123456789, 987654321, "1234.*"]
# 若要排除某个QQ号或群号即允许使用可以在前面加上"!",例如:
# person = ["!1234567890"]
# group = ["!987654321"]
# 排除规则优先级高于包含规则,即如果同时存在包含规则和排除规则,排除规则将生效,例如:
# person = ["1234.*", "!1234567890"]
# 那么1234567890将不会被禁用而其他以1234开头的QQ号都会被禁用
person = [2854196310] # 2854196310是Q群管家机器人的QQ号默认屏蔽以免出现循环
group = [204785790, 691226829] # 本项目交流群的群号,默认屏蔽,避免在交流群测试机器人

View File

@@ -1,372 +0,0 @@
# 配置文件: 注释里标[必需]的参数必须修改, 其他参数根据需要修改, 但请勿删除
import logging
# 消息处理协议适配器
# 目前支持以下适配器:
# - "yirimirai": mirai的通信框架YiriMirai框架适配器, 请同时填写下方mirai_http_api_config
# - "nakuru": go-cqhttp通信框架请同时填写下方nakuru_config
msg_source_adapter = "yirimirai"
# [必需(与nakuru二选一取决于msg_source_adapter)] Mirai的配置
# 请到配置mirai的步骤中的教程查看每个字段的信息
# adapter: 选择适配器目前支持HTTPAdapter和WebSocketAdapter
# host: 运行mirai的主机地址
# port: 运行mirai的主机端口
# verifyKey: mirai-api-http的verifyKey
# qq: 机器人的QQ号
#
# 注意: QQ机器人配置不支持热重载及热更新
mirai_http_api_config = {
"adapter": "WebSocketAdapter",
"host": "localhost",
"port": 8080,
"verifyKey": "yirimirai",
"qq": 1234567890
}
# [必需(与mirai二选一取决于msg_source_adapter)]
# 使用nakuru-project框架连接go-cqhttp的配置
nakuru_config = {
"host": "localhost", # go-cqhttp的地址
"port": 6700, # go-cqhttp的正向websocket端口
"http_port": 5700, # go-cqhttp的正向http端口
"token": "" # 若在go-cqhttp的config.yml设置了access_token, 则填写此处
}
# [必需] OpenAI的配置
# api_key: OpenAI的API Key
# http_proxy: 请求OpenAI时使用的代理None为不使用https和socks5暂不能使用
# 若只有一个api-key请直接修改以下内容中的"openai_api_key"为你的api-key
#
# 如准备了多个api-key可以以字典的形式填写程序会自动选择可用的api-key
# 例如
# openai_config = {
# "api_key": {
# "default": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
# "key1": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
# "key2": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
# },
# "http_proxy": "http://127.0.0.1:12345"
# }
#
# 现已支持反向代理可以添加reverse_proxy字段以使用反向代理
# 使用反向代理可以在国内使用OpenAI的API反向代理的配置请参考
# https://github.com/Ice-Hazymoon/openai-scf-proxy
#
# 反向代理填写示例:
# openai_config = {
# "api_key": {
# "default": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
# "key1": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
# "key2": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
# },
# "reverse_proxy": "http://example.com:12345/v1"
# }
#
# 作者开设公用反向代理地址: https://api.openai.rockchin.top/v1
# 随时可能关闭,仅供测试使用,有条件建议使用正向代理或者自建反向代理
openai_config = {
"api_key": {
"default": "openai_api_key"
},
"http_proxy": None,
"reverse_proxy": None
}
# api-key切换策略
# active每次请求时都会切换api-key
# passive仅当api-key超额时才会切换api-key
switch_strategy = "active"
# [必需] 管理员QQ号用于接收报错等通知及执行管理员级别命令
# 支持多个管理员可以使用list形式设置例如
# admin_qq = [12345678, 87654321]
admin_qq = 0
# 情景预设(机器人人格)
# 每个会话的预设信息,影响所有会话,无视命令重置
# 可以通过这个字段指定某些情况的回复,可直接用自然语言描述指令
# 例如:
# default_prompt = "如果我之后想获取帮助,请你说“输入!help获取帮助”"
# 这样用户在不知所措的时候机器人就会提示其输入!help获取帮助
# 可参考 https://github.com/PlexPt/awesome-chatgpt-prompts-zh
#
# 如果需要多个情景预设,并在运行期间方便切换,请使用字典的形式填写,例如
# default_prompt = {
# "default": "如果我之后想获取帮助,请你说“输入!help获取帮助”",
# "linux-terminal": "我想让你充当 Linux 终端。我将输入命令,您将回复终端应显示的内容。",
# "en-dict": "我想让你充当英英词典,对于给出的英文单词,你要给出其中文意思以及英文解释,并且给出一个例句,此外不要有其他反馈。",
# }
#
# 在使用期间即可通过命令:
# !reset [名称]
# 来使用指定的情景预设重置会话
# 例如:
# !reset linux-terminal
# 若不指定名称,则使用默认情景预设
#
# 也可以使用命令:
# !default <名称>
# 将指定的情景预设设置为默认情景预设
# 例如:
# !default 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
default_prompt = {
"default": "如果用户之后想获取帮助,请你说“输入!help获取帮助”。",
}
# 情景预设格式
# 参考值默认方式normal | 完整情景full_scenario
# 默认方式 的格式为上述default_prompt中的内容或prompts目录下的文件名
# 完整情景方式 的格式为JSON在scenario目录下的JSON文件中列出对话的每个回合编写方法见scenario/default-template.json
# 编写方法请查看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%97full_scenario%E6%A8%A1%E5%BC%8F
preset_mode = "normal"
# 群内响应规则
# 符合此消息的群内消息即使不包含at机器人也会响应
# 支持消息前缀匹配及正则表达式匹配
# 支持设置是否响应at消息、随机响应概率
# 注意:由消息前缀(prefix)匹配的消息中将会删除此前缀,正则表达式(regexp)匹配的消息不会删除匹配的部分
# 前缀匹配优先级高于正则表达式匹配
# 正则表达式简明教程https://www.runoob.com/regexp/regexp-tutorial.html
#
# 支持针对不同群设置不同的响应规则,例如:
# response_rules = {
# "default": {
# "at": True,
# "prefix": ["/ai", "!ai", "ai", "ai"],
# "regexp": [],
# "random_rate": 0.0,
# },
# "12345678": {
# "at": False,
# "prefix": ["/ai", "!ai", "ai", "ai"],
# "regexp": [],
# "random_rate": 0.0,
# },
# }
#
# 以上设置将会在群号为12345678的群中关闭at响应
# 未单独设置的群将使用default规则
response_rules = {
"default": {
"at": True, # 是否响应at机器人的消息
"prefix": ["/ai", "!ai", "ai", "ai"],
"regexp": [], # "为什么.*", "怎么?样.*", "怎么.*", "如何.*", "[Hh]ow to.*", "[Ww]hy not.*", "[Ww]hat is.*", ".*怎么办", ".*咋办"
"random_rate": 0.0, # 随机响应概率0.0-1.00.0为不随机响应1.0为响应所有消息, 仅在前几项判断不通过时生效
},
}
# 消息忽略规则
# 适用于私聊及群聊
# 符合此规则的消息将不会被响应
# 支持消息前缀匹配及正则表达式匹配
# 此设置优先级高于response_rules
# 用以过滤mirai等其他层级的命令
# @see https://github.com/RockChinQ/QChatGPT/issues/165
#
# *需要同时开启下方 income_msg_check 才会生效
ignore_rules = {
"prefix": ["/"],
"regexp": []
}
# 是否检查收到的消息中是否包含敏感词
# 若收到的消息无法通过下方指定的敏感词检查策略,则发送提示信息
income_msg_check = False
# 敏感词过滤开关,以同样数量的*代替敏感词回复
# 请在sensitive.json中添加敏感词
sensitive_word_filter = True
# 是否启用百度云内容安全审核
# 注册方式查看 https://cloud.baidu.com/doc/ANTIPORN/s/Wkhu9d5iy
baidu_check = False
# 百度云API_KEY 24位英文数字字符串
baidu_api_key = ""
# 百度云SECRET_KEY 32位的英文数字字符串
baidu_secret_key = ""
# 不合规消息自定义返回
inappropriate_message_tips = "[百度云]请珍惜机器人,当前返回内容不合规"
# 启动时是否发送赞赏码
# 仅当使用量已经超过2048字时发送
encourage_sponsor_at_start = True
# 每次向OpenAI接口发送对话记录上下文的字符数
# 最大不超过(4096 - max_tokens)个字符max_tokens为下方completion_api_params中的max_tokens
# 注意较大的prompt_submit_length会导致OpenAI账户额度消耗更快
prompt_submit_length = 3072
# 是否在token超限报错时自动重置会话
# 可在tips.py中编辑提示语
auto_reset = True
# OpenAI补全API的参数
# 请在下方填写模型,程序自动选择接口
# 模型文档https://platform.openai.com/docs/models
# 现已支持的模型有:
#
# ChatCompletions 接口:
# # GPT 4 系列
# "gpt-4-1106-preview",
# "gpt-4-vision-preview",
# "gpt-4",
# "gpt-4-32k",
# "gpt-4-0613",
# "gpt-4-32k-0613",
# "gpt-4-0314", # legacy
# "gpt-4-32k-0314", # legacy
# # GPT 3.5 系列
# "gpt-3.5-turbo-1106",
# "gpt-3.5-turbo",
# "gpt-3.5-turbo-16k",
# "gpt-3.5-turbo-0613", # legacy
# "gpt-3.5-turbo-16k-0613", # legacy
# "gpt-3.5-turbo-0301", # legacy
#
# Completions接口
# "gpt-3.5-turbo-instruct",
#
# 具体请查看OpenAI的文档: https://beta.openai.com/docs/api-reference/completions/create
# 请将内容修改到config.py中请勿修改config-template.py
#
# 支持通过 One API 接入多种模型请在上方的openai_config中设置One API的代理地址
# 并在此填写您要使用的模型名称详细请参考https://github.com/songquanpeng/one-api
#
# 支持的 One API 模型:
# "SparkDesk",
# "chatglm_pro",
# "chatglm_std",
# "chatglm_lite",
# "qwen-v1",
# "qwen-plus-v1",
# "ERNIE-Bot",
# "ERNIE-Bot-turbo",
# "gemini-pro",
completion_api_params = {
"model": "gpt-3.5-turbo",
"temperature": 0.9, # 数值越低得到的回答越理性,取值范围[0, 1]
}
# OpenAI的Image API的参数
# 具体请查看OpenAI的文档: https://platform.openai.com/docs/api-reference/images/create
image_api_params = {
"model": "dall-e-2", # 默认使用 dall-e-2 模型,也可以改为 dall-e-3
# 图片尺寸
# dall-e-2 模型支持 256x256, 512x512, 1024x1024
# dall-e-3 模型支持 1024x1024, 1792x1024, 1024x1792
"size": "256x256",
}
# 跟踪函数调用
# 为True时在每次GPT进行Function Calling时都会输出发送一条回复给用户
# 同时一次提问内所有的Function Calling和普通回复消息都会单独发送给用户
trace_function_calls = True
# 群内回复消息时是否引用原消息
quote_origin = False
# 群内回复消息时是否at发送者
at_sender = False
# 回复绘图时是否包含图片描述
include_image_description = True
# 消息处理的超时时间,单位为秒
process_message_timeout = 120
# 回复消息时是否显示[GPT]前缀
show_prefix = False
# 回复前的强制延迟时间,降低机器人被腾讯风控概率
# *此机制对命令和消息、私聊及群聊均生效
# 每次处理时从以下的范围取一个随机秒数,
# 当此次消息处理时间低于此秒数时,将会强制延迟至此秒数
# 例如:[1.5, 3]则每次处理时会随机取一个1.5-3秒的随机数若处理时间低于此随机数则强制延迟至此随机秒数
# 若您不需要此功能请将force_delay_range设置为[0, 0]
force_delay_range = [0, 0]
# 应用长消息处理策略的阈值
# 当回复消息长度超过此值时,将使用长消息处理策略
blob_message_threshold = 256
# 长消息处理策略
# - "image": 将长消息转换为图片发送
# - "forward": 将长消息转换为转发消息组件发送
blob_message_strategy = "forward"
# 允许等待
# 同一会话内,是否等待上一条消息处理完成后再处理下一条消息
# 若设置为False若上一条未处理完时收到了新消息将会丢弃新消息
# 丢弃消息时的提示信息可以在tips.py中修改
wait_last_done = True
# 文字转图片时使用的字体文件路径
# 当策略为"image"时生效
# 若在Windows系统下程序会自动使用Windows自带的微软雅黑字体
# 若未填写或不存在且不是Windows将禁用文字转图片功能改为使用转发消息组件
font_path = ""
# 消息处理超时重试次数
retry_times = 3
# 消息处理出错时是否向用户隐藏错误详细信息
# 设置为True时仅向管理员发送错误详细信息
# 设置为False时向用户及管理员发送错误详细信息
hide_exce_info_to_user = False
# 每个会话的过期时间,单位为秒
# 默认值20分钟
session_expire_time = 1200
# 会话限速
# 单会话内每分钟可进行的对话次数
# 若不需要限速,可以设置为一个很大的值
# 默认值60次基本上不会触发限速
#
# 若要设置针对某特定群的限速,请使用如下格式:
# {
# "group_<群号>": 60,
# "default": 60,
# }
# 若要设置针对某特定用户私聊的限速,请使用如下格式:
# {
# "person_<用户QQ>": 60,
# "default": 60,
# }
# 同时设置多个群和私聊的限速,示例:
# {
# "group_12345678": 60,
# "group_87654321": 60,
# "person_234567890": 60,
# "person_345678901": 60,
# "default": 60,
# }
#
# 注意: 未指定的都使用default的限速值default不可删除
rate_limitation = {
"default": 60,
}
# 会话限速策略
# - "wait": 每次对话获取到回复时,等待一定时间再发送回复,保证其不会超过限速均值
# - "drop": 此分钟内,若对话次数超过限速次数,则丢弃之后的对话,每自然分钟重置
rate_limit_strategy = "drop"
# 是否在启动时进行依赖库更新
upgrade_dependencies = False
# 是否上报统计信息
# 用于统计机器人的使用情况,数据不公开,不会收集任何敏感信息。
# 仅实例识别UUID、上报时间、字数使用量、绘图使用量、插件使用情况、用户信息其他信息不会上报
report_usage = True
# 日志级别
logging_level = logging.INFO

View File

@@ -1,90 +0,0 @@
{
"comment": "这是override.json支持的字段全集, 关于override.json机制, 请查看https://github.com/RockChinQ/QChatGPT/pull/271",
"msg_source_adapter": "yirimirai",
"mirai_http_api_config": {
"adapter": "WebSocketAdapter",
"host": "localhost",
"port": 8080,
"verifyKey": "yirimirai",
"qq": 1234567890
},
"nakuru_config": {
"host": "localhost",
"port": 6700,
"http_port": 5700,
"token": ""
},
"openai_config": {
"api_key": {
"default": "openai_api_key"
},
"http_proxy": null,
"reverse_proxy": null
},
"switch_strategy": "active",
"admin_qq": 0,
"default_prompt": {
"default": "如果用户之后想获取帮助,请你说“输入!help获取帮助”。"
},
"preset_mode": "normal",
"response_rules": {
"default": {
"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": 3072,
"auto_reset": true,
"completion_api_params": {
"model": "gpt-3.5-turbo",
"temperature": 0.9
},
"image_api_params": {
"model": "dall-e-2",
"size": "256x256"
},
"trace_function_calls": true,
"quote_origin": false,
"at_sender": false,
"include_image_description": true,
"process_message_timeout": 120,
"show_prefix": false,
"force_delay_range": [
0,
0
],
"blob_message_threshold": 256,
"blob_message_strategy": "forward",
"wait_last_done": true,
"font_path": "",
"retry_times": 3,
"hide_exce_info_to_user": false,
"session_expire_time": 1200,
"rate_limitation": {
"default": 60
},
"rate_limit_strategy": "drop",
"upgrade_dependencies": false,
"report_usage": true,
"logging_level": 20
}

View File

@@ -12,8 +12,7 @@ class V2MainDataAPI(apigroup.APIGroup):
super().__init__(prefix+"/main", ap)
async def do(self, *args, **kwargs):
config = self.ap.cfg_mgr.data
if not config['report_usage']:
if not self.ap.system_cfg.data['report-usage']:
return None
return await super().do(*args, **kwargs)

View File

@@ -12,8 +12,7 @@ class V2PluginDataAPI(apigroup.APIGroup):
super().__init__(prefix+"/plugin", ap)
async def do(self, *args, **kwargs):
config = self.ap.cfg_mgr.data
if not config['report_usage']:
if not self.ap.system_cfg.data['report-usage']:
return None
return await super().do(*args, **kwargs)

View File

@@ -12,8 +12,7 @@ class V2UsageDataAPI(apigroup.APIGroup):
super().__init__(prefix+"/usage", ap)
async def do(self, *args, **kwargs):
config = self.ap.cfg_mgr.data
if not config['report_usage']:
if not self.ap.system_cfg.data['report-usage']:
return None
return await super().do(*args, **kwargs)

View File

@@ -5,8 +5,9 @@ import typing
from ..core import app, entities as core_entities
from ..provider import entities as llm_entities
from . import entities, operator, errors
from ..config import manager as cfg_mgr
from .operators import func, plugin, default, reset, list as list_cmd, last, next, delc, resend, prompt, cfg, cmd, help, version, update
from .operators import func, plugin, default, reset, list as list_cmd, last, next, delc, resend, prompt, cmd, help, version, update
class CommandManager:
@@ -21,6 +22,23 @@ class CommandManager:
self.ap = ap
async def initialize(self):
# 设置各个类的路径
def set_path(cls: operator.CommandOperator, ancestors: list[str]):
cls.path = '.'.join(ancestors + [cls.name])
for op in operator.preregistered_operators:
if op.parent_class == cls:
set_path(op, ancestors + [cls.name])
for cls in operator.preregistered_operators:
if cls.parent_class is None:
set_path(cls, [])
# 应用命令权限配置
for cls in operator.preregistered_operators:
if cls.path in self.ap.command_cfg.data['privilege']:
cls.lowest_privilege = self.ap.command_cfg.data['privilege'][cls.path]
# 实例化所有类
self.cmd_list = [cls(self.ap) for cls in operator.preregistered_operators]
@@ -85,9 +103,11 @@ class CommandManager:
"""
privilege = 1
if query.sender_id == self.ap.cfg_mgr.data['admin_qq'] \
or query.sender_id in self.ap.cfg_mgr['admin_qq']:
if f'{query.launcher_type.value}_{query.launcher_id}' in self.ap.system_cfg.data['admin-sessions']:
privilege = 2
print(f'privilege: {privilege}')
ctx = entities.ExecuteContext(
query=query,

View File

@@ -24,6 +24,7 @@ def operator_class(
cls.help = help
cls.usage = usage
cls.parent_class = parent_class
cls.lowest_privilege = privilege
preregistered_operators.append(cls)
@@ -41,6 +42,9 @@ class CommandOperator(metaclass=abc.ABCMeta):
name: str
"""名称,搜索到时若符合则使用"""
path: str
"""路径所有父节点的name的连接用于定义命令权限"""
alias: list[str]
"""同name"""

View File

@@ -1,98 +0,0 @@
from __future__ import annotations
import typing
import json
from .. import operator, entities, cmdmgr, errors
@operator.operator_class(
name="cfg",
help="配置项管理",
usage='!cfg <配置项> [配置值]\n!cfg all',
privilege=2
)
class CfgOperator(operator.CommandOperator):
async def execute(
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
"""执行
"""
reply = ''
params = context.crt_params
cfg_mgr = self.ap.cfg_mgr
false = False
true = True
reply_str = ""
if len(params) == 0:
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供配置项名称'))
else:
cfg_name = params[0]
if cfg_name == 'all':
reply_str = "[bot]所有配置项:\n\n"
for cfg in cfg_mgr.data.keys():
if not cfg.startswith('__') and not cfg == 'logging':
# 根据配置项类型进行格式化如果是字典则转换为json并格式化
if isinstance(cfg_mgr.data[cfg], str):
reply_str += "{}: \"{}\"\n".format(cfg, cfg_mgr.data[cfg])
elif isinstance(cfg_mgr.data[cfg], dict):
# 不进行unicode转义并格式化
reply_str += "{}: {}\n".format(cfg,
json.dumps(cfg_mgr.data[cfg],
ensure_ascii=False, indent=4))
else:
reply_str += "{}: {}\n".format(cfg, cfg_mgr.data[cfg])
yield entities.CommandReturn(text=reply_str)
else:
cfg_entry_path = cfg_name.split('.')
try:
if len(params) == 1: # 未指定配置值,返回配置项值
cfg_entry = cfg_mgr.data[cfg_entry_path[0]]
if len(cfg_entry_path) > 1:
for i in range(1, len(cfg_entry_path)):
cfg_entry = cfg_entry[cfg_entry_path[i]]
if isinstance(cfg_entry, str):
reply_str = "[bot]配置项{}: \"{}\"\n".format(cfg_name, cfg_entry)
elif isinstance(cfg_entry, dict):
reply_str = "[bot]配置项{}: {}\n".format(cfg_name,
json.dumps(cfg_entry,
ensure_ascii=False, indent=4))
else:
reply_str = "[bot]配置项{}: {}\n".format(cfg_name, cfg_entry)
yield entities.CommandReturn(text=reply_str)
else:
cfg_value = " ".join(params[1:])
cfg_value = eval(cfg_value)
cfg_entry = cfg_mgr.data[cfg_entry_path[0]]
if len(cfg_entry_path) > 1:
for i in range(1, len(cfg_entry_path) - 1):
cfg_entry = cfg_entry[cfg_entry_path[i]]
if isinstance(cfg_entry[cfg_entry_path[-1]], type(cfg_value)):
cfg_entry[cfg_entry_path[-1]] = cfg_value
yield entities.CommandReturn(text="配置项{}修改成功".format(cfg_name))
else:
# reply = ["[bot]err:配置项{}类型不匹配".format(cfg_name)]
yield entities.CommandReturn(error=errors.CommandOperationError("配置项{}类型不匹配".format(cfg_name)))
else:
cfg_mgr.data[cfg_entry_path[0]] = cfg_value
# reply = ["[bot]配置项{}修改成功".format(cfg_name)]
yield entities.CommandReturn(text="配置项{}修改成功".format(cfg_name))
except KeyError:
# reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
yield entities.CommandReturn(error=errors.CommandOperationError("未找到配置项 {}".format(cfg_name)))
except NameError:
# reply = ["[bot]err:值{}不合法(字符串需要使用双引号包裹)".format(cfg_value)]
yield entities.CommandReturn(error=errors.CommandOperationError("{}不合法(字符串需要使用双引号包裹)".format(cfg_value)))
except ValueError:
# reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
yield entities.CommandReturn(error=errors.CommandOperationError("未找到配置项 {}".format(cfg_name)))

View File

@@ -16,7 +16,7 @@ class HelpOperator(operator.CommandOperator):
self,
context: entities.ExecuteContext
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
help = self.ap.tips_mgr.data['help_message']
help = self.ap.system_cfg.data['help-message']
help += '\n发送命令 !cmd 可查看命令列表'

View File

@@ -4,6 +4,9 @@ from . import model as file_model
from .impls import pymodule, json as json_file
managers: ConfigManager = []
class ConfigManager:
"""配置文件管理器"""

View File

@@ -31,9 +31,15 @@ class Application:
tool_mgr: llm_tool_mgr.ToolManager = None
cfg_mgr: config_mgr.ConfigManager = None
command_cfg: config_mgr.ConfigManager = None
tips_mgr: config_mgr.ConfigManager = None
pipeline_cfg: config_mgr.ConfigManager = None
platform_cfg: config_mgr.ConfigManager = None
provider_cfg: config_mgr.ConfigManager = None
system_cfg: config_mgr.ConfigManager = None
ctr_mgr: center_mgr.V2CenterAPI = None

View File

@@ -32,7 +32,7 @@ async def make_app() -> app.Application:
generated_files = await files.generate_files()
if generated_files:
print("以下文件不存在,已自动生成,请修改配置文件后重启:")
print("以下文件不存在,已自动生成,请按需修改配置文件后重启:")
for file in generated_files:
print("-", file)
@@ -52,31 +52,23 @@ async def make_app() -> app.Application:
# 生成标识符
identifier.init()
cfg_mgr = await config.load_python_module_config("config.py", "config-template.py")
cfg = cfg_mgr.data
# ========== 加载配置文件 ==========
# 检查是否携带了 --override 或 -r 参数
if "--override" in sys.argv or "-r" in sys.argv:
use_override = True
command_cfg = await config.load_json_config("data/config/command.json", "templates/command.json")
pipeline_cfg = await config.load_json_config("data/config/pipeline.json", "templates/pipeline.json")
platform_cfg = await config.load_json_config("data/config/platform.json", "templates/platform.json")
provider_cfg = await config.load_json_config("data/config/provider.json", "templates/provider.json")
system_cfg = await config.load_json_config("data/config/system.json", "templates/system.json")
if use_override:
overrided = await config.override_config_manager(cfg_mgr)
if overrided:
qcg_logger.info("以下配置项已使用 override.json 覆盖:" + ",".join(overrided))
tips_mgr = await config.load_python_module_config(
"tips.py", "tips-custom-template.py"
)
# 检查管理员QQ号
if cfg_mgr.data["admin_qq"] == 0:
qcg_logger.warning("未设置管理员QQ号将无法使用管理员命令请在 config.py 中修改 admin_qq")
# 构建组建实例
# ========== 构建应用实例 ==========
ap = app.Application()
ap.logger = qcg_logger
ap.cfg_mgr = cfg_mgr
ap.tips_mgr = tips_mgr
ap.command_cfg = command_cfg
ap.pipeline_cfg = pipeline_cfg
ap.platform_cfg = platform_cfg
ap.provider_cfg = provider_cfg
ap.system_cfg = system_cfg
proxy_mgr = proxy.ProxyManager(ap)
await proxy_mgr.initialize()
@@ -95,8 +87,8 @@ async def make_app() -> app.Application:
"platform": sys.platform,
},
runtime_info={
"admin_id": "{}".format(cfg["admin_qq"]),
"msg_source": cfg["msg_source_adapter"],
"admin_id": "{}".format(system_cfg.data["admin-sessions"]),
"msg_source": platform_cfg.data["platform-adapter"],
},
)
ap.ctr_mgr = center_v2_api

View File

@@ -6,19 +6,25 @@ import sys
required_files = {
"config.py": "config-template.py",
"banlist.py": "banlist-template.py",
"tips.py": "tips-custom-template.py",
"sensitive.json": "res/templates/sensitive-template.json",
"scenario/default.json": "scenario/default-template.json",
"cmdpriv.json": "res/templates/cmdpriv-template.json",
"plugins/__init__.py": "templates/__init__.py",
"plugins/plugins.json": "templates/plugin-settings.json",
"data/config/command.json": "templates/command.json",
"data/config/pipeline.json": "templates/pipeline.json",
"data/config/platform.json": "templates/platform.json",
"data/config/provider.json": "templates/provider.json",
"data/config/system.json": "templates/system.json",
"data/config/sensitive-words.json": "templates/sensitive-words.json",
"data/scenario/default.json": "templates/scenario-template.json",
}
required_paths = [
"plugins",
"prompts",
"temp",
"logs"
"data",
"data/prompts",
"data/scenario",
"data/logs",
"data/config",
"plugins"
]
async def generate_files() -> list[str]:

View File

@@ -21,7 +21,7 @@ async def init_logging() -> logging.Logger:
if "DEBUG" in os.environ and os.environ["DEBUG"] in ["true", "1"]:
level = logging.DEBUG
log_file_name = "logs/qcg-%s.log" % time.strftime(
log_file_name = "data/logs/qcg-%s.log" % time.strftime(
"%Y-%m-%d-%H-%M-%S", time.localtime()
)

View File

@@ -8,8 +8,6 @@ from . import app, entities
from ..pipeline import entities as pipeline_entities
from ..plugin import events
DEFAULT_QUERY_CONCURRENCY = 10
class Controller:
"""总控制器
@@ -21,7 +19,7 @@ class Controller:
def __init__(self, ap: app.Application):
self.ap = ap
self.semaphore = asyncio.Semaphore(DEFAULT_QUERY_CONCURRENCY)
self.semaphore = asyncio.Semaphore(self.ap.system_cfg.data['pipeline-concurrency'])
async def consumer(self):
"""事件处理循环
@@ -150,9 +148,9 @@ class Controller:
try:
await self._execute_from_stage(0, query)
except Exception as e:
self.ap.logger.error(f"处理请求时出错 {query}: {e}")
# self.ap.logger.debug(f"处理请求时出错 {query}: {e}", exc_info=True)
traceback.print_exc()
self.ap.logger.error(f"处理请求时出错 query_id={query.query_id}: {e}")
self.ap.logger.debug(f"Traceback: {traceback.format_exc()}")
# traceback.print_exc()
finally:
self.ap.logger.debug(f"Query {query} processed")

View File

@@ -9,13 +9,8 @@ from ...config import manager as cfg_mgr
@stage.stage_class('BanSessionCheckStage')
class BanSessionCheckStage(stage.PipelineStage):
banlist_mgr: cfg_mgr.ConfigManager
async def initialize(self):
self.banlist_mgr = await cfg_mgr.load_python_module_config(
"banlist.py",
"res/templates/banlist-template.py"
)
pass
async def process(
self,
@@ -23,54 +18,28 @@ class BanSessionCheckStage(stage.PipelineStage):
stage_inst_name: str
) -> entities.StageProcessResult:
if not self.banlist_mgr.data['enable']:
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
found = False
mode = self.ap.pipeline_cfg.data['access-control']['mode']
sess_list = self.ap.pipeline_cfg.data['access-control'][mode]
if (query.launcher_type == 'group' and 'group_*' in sess_list) \
or (query.launcher_type == 'person' and 'person_*' in sess_list):
found = True
else:
for sess in sess_list:
if sess == f"{query.launcher_type}_{query.launcher_id}":
found = True
break
result = False
if query.launcher_type == 'group':
if not self.banlist_mgr.data['enable_group']: # 未启用群聊响应
result = True
# 检查是否显式声明发起人QQ要被person忽略
elif query.sender_id in self.banlist_mgr.data['person']:
result = True
else:
for group_rule in self.banlist_mgr.data['group']:
if type(group_rule) == int:
if group_rule == query.launcher_id:
result = True
elif type(group_rule) == str:
if group_rule.startswith('!'):
reg_str = group_rule[1:]
if re.match(reg_str, str(query.launcher_id)):
result = False
break
else:
if re.match(group_rule, str(query.launcher_id)):
result = True
elif query.launcher_type == 'person':
if not self.banlist_mgr.data['enable_private']:
result = True
else:
for person_rule in self.banlist_mgr.data['person']:
if type(person_rule) == int:
if person_rule == query.launcher_id:
result = True
elif type(person_rule) == str:
if person_rule.startswith('!'):
reg_str = person_rule[1:]
if re.match(reg_str, str(query.launcher_id)):
result = False
break
else:
if re.match(person_rule, str(query.launcher_id)):
result = True
if mode == 'blacklist':
result = found
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE if not result else entities.ResultType.INTERRUPT,
new_query=query,
debug_notice=f'根据禁用列表忽略消息: {query.launcher_type}_{query.launcher_id}' if result else ''
debug_notice=f'根据访问控制忽略消息: {query.launcher_type}_{query.launcher_id}' if result else ''
)

View File

@@ -24,10 +24,10 @@ class ContentFilterStage(stage.PipelineStage):
async def initialize(self):
self.filter_chain.append(cntignore.ContentIgnore(self.ap))
if self.ap.cfg_mgr.data['sensitive_word_filter']:
if self.ap.pipeline_cfg.data['check-sensitive-words']:
self.filter_chain.append(banwords.BanWordFilter(self.ap))
if self.ap.cfg_mgr.data['baidu_check']:
if self.ap.pipeline_cfg.data['baidu-cloud-examine']['enable']:
self.filter_chain.append(baiduexamine.BaiduCloudExamine(self.ap))
for filter in self.filter_chain:
@@ -41,7 +41,7 @@ class ContentFilterStage(stage.PipelineStage):
"""请求llm前处理消息
只要有一个不通过就不放行,只放行 PASS 的消息
"""
if not self.ap.cfg_mgr.data['income_msg_check']:
if not self.ap.pipeline_cfg.data['income-msg-check']:
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query

View File

@@ -19,8 +19,8 @@ class BaiduCloudExamine(filter_model.ContentFilter):
BAIDU_EXAMINE_TOKEN_URL,
params={
"grant_type": "client_credentials",
"client_id": self.ap.cfg_mgr.data['baidu_api_key'],
"client_secret": self.ap.cfg_mgr.data['baidu_secret_key']
"client_id": self.ap.pipeline_cfg.data['baidu-cloud-examine']['api-key'],
"client_secret": self.ap.pipeline_cfg.data['baidu-cloud-examine']['api-secret']
}
) as resp:
return (await resp.json())['access_token']
@@ -56,6 +56,6 @@ class BaiduCloudExamine(filter_model.ContentFilter):
return entities.FilterResult(
level=entities.ResultLevel.BLOCK,
replacement=message,
user_notice=self.ap.cfg_mgr.data['inappropriate_message_tips'],
user_notice="消息中存在不合适的内容, 请修改",
console_notice=f"百度云判定结果:{conclusion}"
)
)

View File

@@ -13,8 +13,8 @@ class BanWordFilter(filter_model.ContentFilter):
async def initialize(self):
self.sensitive = await cfg_mgr.load_json_config(
"sensitive.json",
"res/templates/sensitive-template.json"
"data/config/sensitive-words.json",
"templates/sensitive-words.json"
)
async def process(self, message: str) -> entities.FilterResult:
@@ -39,6 +39,6 @@ class BanWordFilter(filter_model.ContentFilter):
return entities.FilterResult(
level=entities.ResultLevel.MASKED if found else entities.ResultLevel.PASS,
replacement=message,
user_notice='[bot] 消息中存在不合适的内容, 请更换措辞' if found else '',
user_notice='消息中存在不合适的内容, 请修改' if found else '',
console_notice=''
)

View File

@@ -15,8 +15,8 @@ class ContentIgnore(filter_model.ContentFilter):
]
async def process(self, message: str) -> entities.FilterResult:
if 'prefix' in self.ap.cfg_mgr.data['ignore_rules']:
for rule in self.ap.cfg_mgr.data['ignore_rules']['prefix']:
if 'prefix' in self.ap.pipeline_cfg.data['ignore-rules']:
for rule in self.ap.pipeline_cfg.data['ignore-rules']['prefix']:
if message.startswith(rule):
return entities.FilterResult(
level=entities.ResultLevel.BLOCK,
@@ -25,8 +25,8 @@ class ContentIgnore(filter_model.ContentFilter):
console_notice='根据 ignore_rules 中的 prefix 规则,忽略消息'
)
if 'regexp' in self.ap.cfg_mgr.data['ignore_rules']:
for rule in self.ap.cfg_mgr.data['ignore_rules']['regexp']:
if 'regexp' in self.ap.pipeline_cfg.data['ignore-rules']:
for rule in self.ap.pipeline_cfg.data['ignore-rules']['regexp']:
if re.search(rule, message):
return entities.FilterResult(
level=entities.ResultLevel.BLOCK,

View File

@@ -19,9 +19,9 @@ class LongTextProcessStage(stage.PipelineStage):
strategy_impl: strategy.LongTextStrategy
async def initialize(self):
config = self.ap.cfg_mgr.data
if self.ap.cfg_mgr.data['blob_message_strategy'] == 'image':
use_font = config['font_path']
config = self.ap.platform_cfg.data['long-text-process']
if config['strategy'] == 'image':
use_font = config['font-path']
try:
# 检查是否存在
if not os.path.exists(use_font):
@@ -33,23 +33,25 @@ class LongTextProcessStage(stage.PipelineStage):
config['blob_message_strategy'] = "forward"
else:
self.ap.logger.info("使用Windows自带字体" + use_font)
self.ap.cfg_mgr.data['font_path'] = use_font
config['font-path'] = use_font
else:
self.ap.logger.warn("未找到字体文件且无法使用系统自带字体更换为转发消息组件以发送长消息您可以在config.py中调整相关设置。")
self.ap.cfg_mgr.data['blob_message_strategy'] = "forward"
self.ap.platform_cfg.data['long-text-process']['strategy'] = "forward"
except:
traceback.print_exc()
self.ap.logger.error("加载字体文件失败({})更换为转发消息组件以发送长消息您可以在config.py中调整相关设置。".format(use_font))
self.ap.cfg_mgr.data['blob_message_strategy'] = "forward"
self.ap.platform_cfg.data['long-text-process']['strategy'] = "forward"
if self.ap.cfg_mgr.data['blob_message_strategy'] == 'image':
if config['strategy'] == 'image':
self.strategy_impl = image.Text2ImageStrategy(self.ap)
elif self.ap.cfg_mgr.data['blob_message_strategy'] == 'forward':
elif config['strategy'] == 'forward':
self.strategy_impl = forward.ForwardComponentStrategy(self.ap)
await self.strategy_impl.initialize()
async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult:
if len(str(query.resp_message_chain)) > self.ap.cfg_mgr.data['blob_message_threshold']:
if len(str(query.resp_message_chain)) > self.ap.platform_cfg.data['long-text-process']['threshold']:
query.resp_message_chain = MessageChain(await self.strategy_impl.process(str(query.resp_message_chain)))
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,

View File

@@ -19,7 +19,7 @@ class Text2ImageStrategy(strategy_model.LongTextStrategy):
text_render_font: ImageFont.FreeTypeFont
async def initialize(self):
self.text_render_font = ImageFont.truetype(self.ap.cfg_mgr.data['font_path'], 32, encoding="utf-8")
self.text_render_font = ImageFont.truetype(self.ap.platform_cfg.data['long-text-process']['font-path'], 32, encoding="utf-8")
async def process(self, message: str) -> list[MessageComponent]:
img_path = self.text_to_image(

View File

@@ -52,7 +52,7 @@ class PreProcessor(stage.PipelineStage):
query.messages = event_ctx.event.prompt
# 根据模型max_tokens剪裁
max_tokens = min(query.use_model.max_tokens, self.ap.cfg_mgr.data['prompt_submit_length'])
max_tokens = min(query.use_model.max_tokens, self.ap.pipeline_cfg.data['submit-messages-tokens'])
test_messages = query.prompt.messages + query.messages + [query.user_message]
@@ -63,7 +63,7 @@ class PreProcessor(stage.PipelineStage):
result_type=entities.ResultType.INTERRUPT,
new_query=query,
user_notice='输入内容过长,请减少情景预设或者输入内容长度',
console_notice='输入内容过长,请减少情景预设或者输入内容长度,或者增大配置文件中的 prompt_submit_length但不能超过所用模型最大tokens数'
console_notice='输入内容过长,请减少情景预设或者输入内容长度,或者增大配置文件中的 submit-messages-tokens但不能超过所用模型最大tokens数'
)
query.messages.pop(0) # pop第一个肯定是role=user的

View File

@@ -54,6 +54,12 @@ class ChatMessageHandler(handler.MessageHandler):
)
else:
if not self.ap.provider_cfg.data['enable-chat']:
yield entities.StageProcessResult(
result_type=entities.ResultType.INTERRUPT,
new_query=query,
)
if event_ctx.event.alter is not None:
query.message_chain = mirai.MessageChain([
mirai.Plain(event_ctx.event.alter)
@@ -83,7 +89,7 @@ class ChatMessageHandler(handler.MessageHandler):
yield entities.StageProcessResult(
result_type=entities.ResultType.INTERRUPT,
new_query=query,
user_notice=self.ap.tips_mgr.data['alter_tip_message'] if self.ap.cfg_mgr.data['hide_exce_info_to_user'] else f'{e}',
user_notice='请求失败' if self.ap.platform_cfg.data['hide-exception-info'] else f'{e}',
error_notice=f'{e}',
debug_notice=traceback.format_exc()
)

View File

@@ -23,8 +23,8 @@ class CommandHandler(handler.MessageHandler):
privilege = 1
if query.sender_id == self.ap.cfg_mgr.data['admin_qq'] \
or query.sender_id in self.ap.cfg_mgr['admin_qq']:
if f'{query.launcher_type.value}_{query.launcher_id}' in self.ap.system_cfg.data['admin-sessions']:
privilege = 2
spt = str(query.message_chain).strip().split(' ')

View File

@@ -55,16 +55,16 @@ class FixedWindowAlgo(algo.ReteLimitAlgo):
# 获取当前分钟的访问次数
count = container.records.get(now, 0)
limitation = self.ap.cfg_mgr.data['rate_limitation']['default']
limitation = self.ap.pipeline_cfg.data['rate-limit']['fixwin']['default']
if session_name in self.ap.cfg_mgr.data['rate_limitation']:
limitation = self.ap.cfg_mgr.data['rate_limitation'][session_name]
if session_name in self.ap.pipeline_cfg.data['rate-limit']['fixwin']:
limitation = self.ap.pipeline_cfg.data['rate-limit']['fixwin'][session_name]
# 如果访问次数超过了限制
if count >= limitation:
if self.ap.cfg_mgr.data['rate_limit_strategy'] == 'drop':
if self.ap.pipeline_cfg.data['rate-limit']['strategy'] == 'drop':
return False
elif self.ap.cfg_mgr.data['rate_limit_strategy'] == 'wait':
elif self.ap.pipeline_cfg.data['rate-limit']['strategy'] == 'wait':
# 等待下一分钟
await asyncio.sleep(60 - time.time() % 60)

View File

@@ -42,7 +42,7 @@ class RateLimit(stage.PipelineStage):
result_type=entities.ResultType.INTERRUPT,
new_query=query,
console_notice=f"根据限速规则忽略 {query.launcher_type.value}:{query.launcher_id} 消息",
user_notice=self.ap.tips_mgr.data['rate_limit_drop_tip']
user_notice=f"请求数超过限速器设定值,已丢弃本消息。"
)
elif stage_inst_name == "ReleaseRateLimitOccupancy":
await self.algo.release_access(

View File

@@ -20,7 +20,7 @@ class SendResponseBackStage(stage.PipelineStage):
async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult:
"""处理
"""
random_delay = random.uniform(*self.ap.cfg_mgr.data['force_delay_range'])
random_delay = random.uniform(*self.ap.platform_cfg.data['force-delay'])
self.ap.logger.debug(
"根据规则强制延迟回复: %s s",

View File

@@ -33,13 +33,13 @@ class GroupRespondRuleCheckStage(stage.PipelineStage):
async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult:
if query.launcher_type != 'group':
if query.launcher_type.value != 'group':
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
rules = self.ap.cfg_mgr.data['response_rules']
rules = self.ap.pipeline_cfg.data['respond-rules']
use_rule = rules['default']

View File

@@ -16,6 +16,7 @@ class PrefixRule(rule_model.GroupRespondRule):
for prefix in prefixes:
if message_text.startswith(prefix):
return entities.RuleJudgeResult(
matching=True,
replacement=mirai.MessageChain([

View File

@@ -14,7 +14,7 @@ class RandomRespRule(rule_model.GroupRespondRule):
message_chain: mirai.MessageChain,
rule_dict: dict
) -> entities.RuleJudgeResult:
random_rate = rule_dict['random_rate']
random_rate = rule_dict['random']
return entities.RuleJudgeResult(
matching=random.random() < random_rate,

View File

@@ -85,7 +85,7 @@ class ResponseWrapper(stage.PipelineStage):
query.resp_message_chain = mirai.MessageChain([mirai.Plain(reply_text)])
if self.ap.cfg_mgr.data['trace_function_calls']:
if self.ap.platform_cfg.data['track-function-calls']:
event_ctx = await self.ap.plugin_mgr.emit_event(
event=events.NormalMessageResponded(

View File

@@ -31,19 +31,16 @@ class PlatformManager:
async def initialize(self):
config = self.ap.cfg_mgr.data
logging.debug("Use adapter:" + config['msg_source_adapter'])
if config['msg_source_adapter'] == 'yirimirai':
if self.ap.platform_cfg.data['platform-adapter'] == 'yiri-mirai':
from pkg.platform.sources.yirimirai import YiriMiraiAdapter
mirai_http_api_config = config['mirai_http_api_config']
self.bot_account_id = config['mirai_http_api_config']['qq']
mirai_http_api_config = self.ap.platform_cfg.data['yiri-mirai-config']
self.bot_account_id = mirai_http_api_config['qq']
self.adapter = YiriMiraiAdapter(mirai_http_api_config)
elif config['msg_source_adapter'] == 'nakuru':
from pkg.platform.sources.nakuru import NakuruProjectAdapter
self.adapter = NakuruProjectAdapter(config['nakuru_config'])
self.bot_account_id = self.adapter.bot_account_id
# elif config['msg_source_adapter'] == 'nakuru':
# from pkg.platform.sources.nakuru import NakuruProjectAdapter
# self.adapter = NakuruProjectAdapter(config['nakuru_config'])
# self.bot_account_id = self.adapter.bot_account_id
# 保存 account_id 到审计模块
from ..audit.center import apigroup
@@ -99,7 +96,7 @@ class PlatformManager:
)
# nakuru不区分好友和陌生人故仅为yirimirai注册陌生人事件
if config['msg_source_adapter'] == 'yirimirai':
if self.ap.platform_cfg.data['platform-adapter'] == 'yiri-mirai':
self.adapter.register_listener(
StrangerMessage,
on_stranger_message
@@ -133,27 +130,23 @@ class PlatformManager:
)
async def send(self, event, msg, check_quote=True, check_at_sender=True):
config = self.ap.cfg_mgr.data
if check_at_sender and config['at_sender']:
if check_at_sender and self.ap.platform_cfg.data['at-sender'] and isinstance(event, GroupMessage):
msg.insert(
0,
Plain(" \n")
)
# 当回复的正文中包含换行时quote可能会自带at此时就不再单独添加at只添加换行
if "\n" not in str(msg[1]) or config['msg_source_adapter'] == 'nakuru':
msg.insert(
0,
At(
event.sender.id
)
msg.insert(
0,
At(
event.sender.id
)
)
await self.adapter.reply_message(
event,
msg,
quote_origin=True if config['quote_origin'] and check_quote else False
quote_origin=True if self.ap.platform_cfg.data['quote-origin'] and check_quote else False
)
# 通知系统管理员
@@ -161,19 +154,16 @@ class PlatformManager:
await self.notify_admin_message_chain(MessageChain([Plain("[bot]{}".format(message))]))
async def notify_admin_message_chain(self, message: mirai.MessageChain):
config = self.ap.cfg_mgr.data
if config['admin_qq'] != 0 and config['admin_qq'] != []:
logging.info("通知管理员:{}".format(message))
if self.ap.system_cfg.data['admin-sessions'] != []:
admin_list = []
if type(config['admin_qq']) == int:
admin_list.append(config['admin_qq'])
for admin in self.ap.system_cfg.data['admin-sessions']:
admin_list.append(admin)
for adm in admin_list:
self.adapter.send_message(
"person",
adm,
adm.split("_")[0],
adm.split("_")[1],
message
)

View File

@@ -17,7 +17,7 @@ class SettingManager:
async def initialize(self):
self.settings = await cfg_mgr.load_json_config(
'plugins/plugins.json',
'res/templates/plugin-setting-template.json'
'templates/plugin-settings.json'
)
async def sync_setting(

View File

@@ -22,8 +22,8 @@ class OpenAIChatCompletion(api.LLMAPIRequester):
async def initialize(self):
self.client = openai.AsyncClient(
api_key="",
base_url=self.ap.cfg_mgr.data["openai_config"]["reverse_proxy"],
timeout=self.ap.cfg_mgr.data["process_message_timeout"],
base_url=self.ap.provider_cfg.data['openai-config']['base_url'],
timeout=self.ap.provider_cfg.data['openai-config']['request-timeout'],
)
async def _req(
@@ -51,7 +51,7 @@ class OpenAIChatCompletion(api.LLMAPIRequester):
) -> llm_entities.Message:
self.client.api_key = use_model.token_mgr.get_token()
args = self.ap.cfg_mgr.data["completion_api_params"].copy()
args = self.ap.provider_cfg.data['openai-config']['chat-completions-params'].copy()
args["model"] = use_model.name if use_model.model_name is None else use_model.model_name
if use_model.tool_call_supported:

View File

@@ -29,7 +29,7 @@ class ModelManager:
async def initialize(self):
openai_chat_completion = chatcmpl.OpenAIChatCompletion(self.ap)
await openai_chat_completion.initialize()
openai_token_mgr = token.TokenManager(self.ap, list(self.ap.cfg_mgr.data['openai_config']['api_key'].values()))
openai_token_mgr = token.TokenManager(self.ap, list(self.ap.provider_cfg.data['openai-config']['api-keys']))
tiktoken_tokenizer = tiktoken.Tiktoken(self.ap)

View File

@@ -25,10 +25,15 @@ class SessionManager:
if query.launcher_type == session.launcher_type and query.launcher_id == session.launcher_id:
return session
session_concurrency = self.ap.system_cfg.data['session-concurrency']['default']
if f'{query.launcher_type.value}_{query.launcher_id}' in self.ap.system_cfg.data['session-concurrency']:
session_concurrency = self.ap.system_cfg.data['session-concurrency'][f'{query.launcher_type.value}_{query.launcher_id}']
session = core_entities.Session(
launcher_type=query.launcher_type,
launcher_id=query.launcher_id,
semaphore=asyncio.Semaphore(1) if self.ap.cfg_mgr.data['wait_last_done'] else asyncio.Semaphore(10000),
semaphore=asyncio.Semaphore(session_concurrency),
)
self.session_list.append(session)
return session
@@ -41,7 +46,7 @@ class SessionManager:
conversation = core_entities.Conversation(
prompt=await self.ap.prompt_mgr.get_prompt(session.use_prompt_name),
messages=[],
use_model=await self.ap.model_mgr.get_model_by_name(self.ap.cfg_mgr.data['completion_api_params']['model']),
use_model=await self.ap.model_mgr.get_model_by_name(self.ap.provider_cfg.data['openai-config']['chat-completions-params']['model']),
use_funcs=await self.ap.tool_mgr.get_all_functions(),
)
session.conversations.append(conversation)

View File

@@ -14,8 +14,8 @@ class ScenarioPromptLoader(loader.PromptLoader):
async def load(self):
"""加载Prompt
"""
for file in os.listdir("scenarios"):
with open("scenarios/{}".format(file), "r", encoding="utf-8") as f:
for file in os.listdir("data/scenarios"):
with open("data/scenarios/{}".format(file), "r", encoding="utf-8") as f:
file_str = f.read()
file_name = file.split(".")[0]
file_json = json.loads(file_str)

View File

@@ -14,7 +14,7 @@ class SingleSystemPromptLoader(loader.PromptLoader):
"""加载Prompt
"""
for name, cnt in self.ap.cfg_mgr.data['default_prompt'].items():
for name, cnt in self.ap.provider_cfg.data['prompt'].items():
prompt = entities.Prompt(
name=name,
messages=[
@@ -26,8 +26,8 @@ class SingleSystemPromptLoader(loader.PromptLoader):
)
self.prompts.append(prompt)
for file in os.listdir("prompts"):
with open("prompts/{}".format(file), "r", encoding="utf-8") as f:
for file in os.listdir("data/prompts"):
with open("data/prompts/{}".format(file), "r", encoding="utf-8") as f:
file_str = f.read()
file_name = file.split(".")[0]
prompt = entities.Prompt(

View File

@@ -23,7 +23,7 @@ class PromptManager:
"full_scenario": scenario.ScenarioPromptLoader
}
loader_cls = loader_map[self.ap.cfg_mgr.data['preset_mode']]
loader_cls = loader_map[self.ap.provider_cfg.data['prompt-mode']]
self.loader_inst: loader.PromptLoader = loader_cls(self.ap)

View File

@@ -1,5 +1,8 @@
from __future__ import annotations
import os
import sys
from ..core import app
@@ -14,17 +17,15 @@ class ProxyManager:
self.forward_proxies = {}
async def initialize(self):
config = self.ap.cfg_mgr.data
self.forward_proxies = {
"http": os.getenv("HTTP_PROXY") or os.getenv("http_proxy"),
"https": os.getenv("HTTPS_PROXY") or os.getenv("https_proxy"),
}
return (
{
"http": config["openai_config"]["proxy"],
"https": config["openai_config"]["proxy"],
}
if "proxy" in config["openai_config"]
and (config["openai_config"]["proxy"] is not None)
else None
)
if 'http' in self.ap.system_cfg.data['network-proxies']:
self.forward_proxies['http'] = self.ap.system_cfg.data['network-proxies']['http']
if 'https' in self.ap.system_cfg.data['network-proxies']:
self.forward_proxies['https'] = self.ap.system_cfg.data['network-proxies']['https']
def get_forward_proxies(self) -> str:
def get_forward_proxies(self) -> dict:
return self.forward_proxies

View File

@@ -1,30 +0,0 @@
{
"comment": "以下为命令权限请设置到cmdpriv.json中。关于此功能的说明请查看https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E5%91%BD%E4%BB%A4%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6",
"draw": 1,
"func": 1,
"plugin": 1,
"plugin.get": 2,
"plugin.update": 2,
"plugin.del": 2,
"plugin.off": 2,
"plugin.on": 2,
"default": 1,
"default.set": 2,
"del": 1,
"del.all": 1,
"delhst": 2,
"delhst.all": 2,
"last": 1,
"list": 1,
"next": 1,
"prompt": 1,
"resend": 1,
"reset": 1,
"cfg": 2,
"cmd": 1,
"help": 1,
"reload": 2,
"update": 2,
"usage": 1,
"version": 1
}

3
templates/command.json Normal file
View File

@@ -0,0 +1,3 @@
{
"privilege": {}
}

36
templates/pipeline.json Normal file
View File

@@ -0,0 +1,36 @@
{
"access-control":{
"mode": "blacklist",
"blacklist": [],
"whitelist": []
},
"respond-rules": {
"default": {
"at": true,
"prefix": [
"/ai", "!ai", "ai", "ai"
],
"regexp": [],
"random": 0.0
}
},
"income-msg-check": true,
"ignore-rules": {
"prefix": ["/"],
"regexp": []
},
"check-sensitive-words": true,
"baidu-cloud-examine": {
"enable": false,
"api-key": "",
"api-secret": ""
},
"submit-messages-tokens": 3072,
"rate-limit": {
"strategy": "drop",
"algo": "fixwin",
"fixwin": {
"default": 60
}
}
}

20
templates/platform.json Normal file
View File

@@ -0,0 +1,20 @@
{
"platform-adapter": "yiri-mirai",
"yiri-mirai-config": {
"adapter": "WebSocketAdapter",
"host": "localhost",
"port": 8080,
"verifyKey": "yirimirai",
"qq": 123456789
},
"track-function-calls": true,
"quote-origin": false,
"at-sender": false,
"force-delay": [0, 0],
"long-text-process": {
"threshold": 256,
"strategy": "forward",
"font-path": ""
},
"hide-exception-info": true
}

17
templates/provider.json Normal file
View File

@@ -0,0 +1,17 @@
{
"enable-chat": true,
"openai-config": {
"api-keys": [
"sk-1234567890"
],
"base_url": "https://api.openai.com/v1",
"chat-completions-params": {
"model": "gpt-3.5-turbo"
},
"request-timeout": 120
},
"prompt-mode": "normal",
"prompt": {
"default": "如果用户之后想获取帮助,请你说”输入!help获取帮助“。"
}
}

11
templates/system.json Normal file
View File

@@ -0,0 +1,11 @@
{
"admin-sessions": [],
"network-proxies": {},
"report-usage": true,
"logging-level": "info",
"session-concurrency": {
"default": 1
},
"pipeline-concurrency": 20,
"help-message": "QChatGPT - 😎高稳定性、🧩支持插件、🌏实时联网的 ChatGPT QQ 机器人🤖\n链接https://q.rkcn.top"
}

View File

@@ -1,10 +0,0 @@
<html>
<head>
<title>Test</title>
</head>
<body>
<h1>Test</h1>
<p>Test</p>
<p>This is a test for QChatGPT.</p>
</body>
</html>

View File

@@ -1,37 +0,0 @@
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 = "本分钟对话次数超过限速次数,此对话被丢弃"
# 只允许同时处理一条消息时,新消息被丢弃时的提示信息
# 当config.py中的wait_last_done为False时生效
# 若设置为空字符串,则不发送提示信息
message_drop_tip = "[bot]当前有一条消息正在处理,请等待处理完成"
# 命令 !help帮助消息
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]会话已重置,使用场景预设:"
# 会话自动重置时的提示
session_auto_reset_message = "[bot]会话token超限已自动重置请重新发送消息"

Binary file not shown.