From a313ae5f97c142cb71e805be5cb2e35a003a8699 Mon Sep 17 00:00:00 2001 From: Junyan Qin <1010553892@qq.com> Date: Wed, 16 Oct 2024 15:34:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E5=8F=AF=E8=A7=86=E5=8C=96=E7=BC=96=E8=BE=91schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/api/http/controller/main.py | 3 +- pkg/audit/center/apigroup.py | 6 +- pkg/core/app.py | 5 +- .../migrations/m014_force_delay_config.py | 22 ++ pkg/core/stages/load_config.py | 9 +- pkg/core/stages/migrate.py | 2 +- pkg/pipeline/controller.py | 4 +- pkg/pipeline/respback/respback.py | 5 +- pkg/platform/manager.py | 3 +- pkg/utils/schema.py | 5 +- templates/platform.json | 5 +- templates/schema/command.json | 39 +++ templates/schema/platform.json | 256 ++++++++++++++++++ templates/schema/provider.json | 196 ++++++++++++++ 14 files changed, 547 insertions(+), 13 deletions(-) create mode 100644 pkg/core/migrations/m014_force_delay_config.py create mode 100644 templates/schema/command.json create mode 100644 templates/schema/platform.json create mode 100644 templates/schema/provider.json diff --git a/pkg/api/http/controller/main.py b/pkg/api/http/controller/main.py index 17b15192..13b1afcf 100644 --- a/pkg/api/http/controller/main.py +++ b/pkg/api/http/controller/main.py @@ -30,11 +30,12 @@ class HTTPController: while True: await asyncio.sleep(1) - asyncio.create_task(self.quart_app.run_task( + task = asyncio.create_task(self.quart_app.run_task( host=self.ap.system_cfg.data['http-api']['host'], port=self.ap.system_cfg.data['http-api']['port'], shutdown_trigger=shutdown_trigger_placeholder )) + self.ap.asyncio_tasks.append(task) async def register_routes(self) -> None: diff --git a/pkg/audit/center/apigroup.py b/pkg/audit/center/apigroup.py index 26eac7ae..6ae8be5d 100644 --- a/pkg/audit/center/apigroup.py +++ b/pkg/audit/center/apigroup.py @@ -69,7 +69,11 @@ class APIGroup(metaclass=abc.ABCMeta): **kwargs ) -> asyncio.Task: """执行请求""" - asyncio.create_task(self._do(method, path, data, params, headers, **kwargs)) + task = asyncio.create_task(self._do(method, path, data, params, headers, **kwargs)) + + self.ap.asyncio_tasks.append(task) + + return task def gen_rid( self diff --git a/pkg/core/app.py b/pkg/core/app.py index 11a1eed3..c67cb082 100644 --- a/pkg/core/app.py +++ b/pkg/core/app.py @@ -125,9 +125,11 @@ class Application: import signal def signal_handler(sig, frame): - for task in tasks: + for task in self.asyncio_tasks: task.cancel() self.logger.info("程序退出.") + # 结束当前事件循环 + self.event_loop.stop() exit(0) signal.signal(signal.SIGINT, signal_handler) @@ -138,4 +140,3 @@ class Application: except Exception as e: self.logger.error(f"应用运行致命异常: {e}") self.logger.debug(f"Traceback: {traceback.format_exc()}") - diff --git a/pkg/core/migrations/m014_force_delay_config.py b/pkg/core/migrations/m014_force_delay_config.py new file mode 100644 index 00000000..55521c9c --- /dev/null +++ b/pkg/core/migrations/m014_force_delay_config.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from .. import migration + + +@migration.migration_class("force-delay-config", 14) +class ForceDelayConfigMigration(migration.Migration): + """迁移""" + + async def need_migrate(self) -> bool: + """判断当前环境是否需要运行此迁移""" + return type(self.ap.platform_cfg.data['force-delay']) == list + + async def run(self): + """执行迁移""" + + self.ap.platform_cfg.data['force-delay'] = { + "min": self.ap.platform_cfg.data['force-delay'][0], + "max": self.ap.platform_cfg.data['force-delay'][1] + } + + await self.ap.platform_cfg.dump_config() diff --git a/pkg/core/stages/load_config.py b/pkg/core/stages/load_config.py index f25bc4a5..9ae4c20c 100644 --- a/pkg/core/stages/load_config.py +++ b/pkg/core/stages/load_config.py @@ -27,7 +27,8 @@ class LoadConfigStage(stage.BootingStage): ap.settings_mgr.register_manager( name="command.json", description="命令配置", - manager=ap.command_cfg + manager=ap.command_cfg, + schema=schema.CONFIG_COMMAND_SCHEMA ) ap.settings_mgr.register_manager( @@ -40,13 +41,15 @@ class LoadConfigStage(stage.BootingStage): ap.settings_mgr.register_manager( name="platform.json", description="消息平台配置", - manager=ap.platform_cfg + manager=ap.platform_cfg, + schema=schema.CONFIG_PLATFORM_SCHEMA ) ap.settings_mgr.register_manager( name="provider.json", description="大模型能力配置", - manager=ap.provider_cfg + manager=ap.provider_cfg, + schema=schema.CONFIG_PROVIDER_SCHEMA ) ap.settings_mgr.register_manager( diff --git a/pkg/core/stages/migrate.py b/pkg/core/stages/migrate.py index a4e57e78..dd9023ed 100644 --- a/pkg/core/stages/migrate.py +++ b/pkg/core/stages/migrate.py @@ -6,7 +6,7 @@ from .. import stage, app from .. import migration from ..migrations import m001_sensitive_word_migration, m002_openai_config_migration, m003_anthropic_requester_cfg_completion, m004_moonshot_cfg_completion from ..migrations import m005_deepseek_cfg_completion, m006_vision_config, m007_qcg_center_url, m008_ad_fixwin_config_migrate, m009_msg_truncator_cfg -from ..migrations import m010_ollama_requester_config, m011_command_prefix_config, m012_runner_config, m013_http_api_config +from ..migrations import m010_ollama_requester_config, m011_command_prefix_config, m012_runner_config, m013_http_api_config, m014_force_delay_config @stage.stage_class("MigrationStage") diff --git a/pkg/pipeline/controller.py b/pkg/pipeline/controller.py index 0f07e068..3b4e2c64 100644 --- a/pkg/pipeline/controller.py +++ b/pkg/pipeline/controller.py @@ -60,7 +60,9 @@ class Controller: # 通知其他协程,有新的请求可以处理了 self.ap.query_pool.condition.notify_all() - asyncio.create_task(_process_query(selected_query)) + task = asyncio.create_task(_process_query(selected_query)) + self.ap.asyncio_tasks.append(task) + except Exception as e: # traceback.print_exc() self.ap.logger.error(f"控制器循环出错: {e}") diff --git a/pkg/pipeline/respback/respback.py b/pkg/pipeline/respback/respback.py index d3dd83fe..08b335d5 100644 --- a/pkg/pipeline/respback/respback.py +++ b/pkg/pipeline/respback/respback.py @@ -19,7 +19,10 @@ class SendResponseBackStage(stage.PipelineStage): async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult: """处理 """ - random_delay = random.uniform(*self.ap.platform_cfg.data['force-delay']) + + random_range = (self.ap.platform_cfg.data['force-delay']['min'], self.ap.platform_cfg.data['force-delay']['max']) + + random_delay = random.uniform(*random_range) self.ap.logger.debug( "根据规则强制延迟回复: %s s", diff --git a/pkg/platform/manager.py b/pkg/platform/manager.py index aed8deff..4b22d5c9 100644 --- a/pkg/platform/manager.py +++ b/pkg/platform/manager.py @@ -184,7 +184,8 @@ class PlatformManager: tasks.append(exception_wrapper(adapter)) for task in tasks: - asyncio.create_task(task) + async_task = asyncio.create_task(task) + self.ap.asyncio_tasks.append(async_task) except Exception as e: self.ap.logger.error('平台适配器运行出错: ' + str(e)) diff --git a/pkg/utils/schema.py b/pkg/utils/schema.py index 3ca51045..378cdf5b 100644 --- a/pkg/utils/schema.py +++ b/pkg/utils/schema.py @@ -8,4 +8,7 @@ def load_schema(schema_path: str) -> dict: CONFIG_SYSTEM_SCHEMA = load_schema("templates/schema/system.json") -CONFIG_PIPELINE_SCHEMA = load_schema("templates/schema/pipeline.json") \ No newline at end of file +CONFIG_PIPELINE_SCHEMA = load_schema("templates/schema/pipeline.json") +CONFIG_COMMAND_SCHEMA = load_schema("templates/schema/command.json") +CONFIG_PLATFORM_SCHEMA = load_schema("templates/schema/platform.json") +CONFIG_PROVIDER_SCHEMA = load_schema("templates/schema/provider.json") diff --git a/templates/platform.json b/templates/platform.json index b048f2f8..f0a13fd6 100644 --- a/templates/platform.json +++ b/templates/platform.json @@ -37,7 +37,10 @@ "track-function-calls": true, "quote-origin": false, "at-sender": false, - "force-delay": [0, 0], + "force-delay": { + "min": 0, + "max": 0 + }, "long-text-process": { "threshold": 256, "strategy": "forward", diff --git a/templates/schema/command.json b/templates/schema/command.json new file mode 100644 index 00000000..70bfeda7 --- /dev/null +++ b/templates/schema/command.json @@ -0,0 +1,39 @@ +{ + "type": "object", + "layout": "expansion-panels", + "properties": { + "command-prefix": { + "type": "array", + "title": "命令前缀", + "description": "以数组形式设置,程序将前缀符合设置的消息视为命令(群内需要符合群响应规则)", + "items": { + "type": "string" + } + }, + "privilege": { + "type": "object", + "title": "权限管理", + "layout": { + "props": { + "title": "权限管理" + } + }, + "description": "设置每个命令的权限配置。普通用户权限级别为 1,管理员(system.json中设置的)权限级别为 2;在这里设置每个命令的最低权限级别,若设置为1,则用户和管理员均可用,若为2,则仅管理员可用;设置子命令时,以点号间隔,如\"plugin.on\"", + "properties": { + "placeholder": { + "type": "integer", + "minimum": 1, + "maximum": 2, + "const": 1 + } + }, + "patternProperties": { + "^[a-zA-Z0-9_.]+$": { + "type": "integer", + "minimum": 1, + "maximum": 2 + } + } + } + } +} \ No newline at end of file diff --git a/templates/schema/platform.json b/templates/schema/platform.json new file mode 100644 index 00000000..064e966b --- /dev/null +++ b/templates/schema/platform.json @@ -0,0 +1,256 @@ +{ + "type": "object", + "layout": "expansion-panels", + "properties": { + "platform-adapters": { + "type": "array", + "title": "消息平台适配器", + "items": { + "type": "object", + "oneOf": [ + { + "title": "YiriMirai 适配器", + "description": "用于接入 Mirai", + "properties": { + "adapter": { + "type": "string", + "const": "yiri-mirai" + }, + "enable": { + "type": "boolean", + "default": false, + "description": "是否启用此适配器", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "host": { + "type": "string", + "default": "127.0.0.1" + }, + "port": { + "type": "integer", + "default": 8080 + }, + "verifyKey": { + "type": "string", + "default": "yirimirai" + }, + "qq": { + "type": "integer", + "default": 123456789 + } + } + }, + { + "title": "Nakuru 适配器", + "description": "用于接入 go-cqhttp", + "properties": { + "adapter": { + "type": "string", + "const": "nakuru" + }, + "enable": { + "type": "boolean", + "default": false, + "description": "是否启用此适配器", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "host": { + "type": "string", + "default": "127.0.0.1" + }, + "ws_port": { + "type": "integer", + "default": 8080 + }, + "http_port": { + "type": "integer", + "default": 5700 + }, + "token": { + "type": "string", + "default": "" + } + } + }, + { + "title": "aiocqhttp 适配器", + "description": "用于接入 Lagrange 等兼容 OneBot v11 协议的机器人框架(仅支持反向ws)", + "properties": { + "adapter": { + "type": "string", + "const": "aiocqhttp" + }, + "enable": { + "type": "boolean", + "default": false, + "description": "是否启用此适配器", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "host": { + "type": "string", + "default": "0.0.0.0", + "description": "监听的 IP 地址,一般就保持 0.0.0.0 就可以了。使用 aiocqhttp 时,QChatGPT 作为服务端被动等待框架连接,请在 Lagrange 等框架中设置被动 ws 地址或者反向 ws 地址(具体视框架而定)为 QChatGPT 监听的地址,且路径为/ws,例如:ws://127.0.0.1:8080/ws" + }, + "port": { + "type": "integer", + "default": 8080, + "description": "设置监听的端口,默认8080,需在 Lagrange 等框架中设置为与此处一致的端口" + }, + "access-token": { + "type": "string", + "default": "", + "description": "设置访问密钥,与 Lagrange 等框架中设置的保持一致" + } + } + }, + { + "title": "qq-botpy 适配器", + "description": "用于接入 QQ 官方机器人 API", + "properties": { + "adapter": { + "type": "string", + "const": "qq-botpy" + }, + "enable": { + "type": "boolean", + "default": false, + "description": "是否启用此适配器", + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + } + }, + "appid": { + "type": "string", + "default": "", + "description": "申请到的QQ官方机器人的appid" + }, + "secret": { + "type": "string", + "default": "", + "description": "申请到的QQ官方机器人的secret" + }, + "intents": { + "type": "array", + "description": "控制监听的事件类型,需要填写才能接收到对应消息,目前支持的事件类型有:public_guild_messages(QQ 频道消息)、direct_message(QQ 频道私聊消息)、public_messages(QQ 群 和 列表私聊消息)", + "default": [ + "public_guild_messages", + "direct_message", + "public_messages" + ] + } + } + } + ] + } + }, + "track-function-calls": { + "type": "boolean", + "default": true, + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + }, + "title": "跟踪内容函数调用", + "description": "开启之后,在对话中调用的内容函数记录也会发给用户,关闭后(false)仅会发给用户最终结果" + }, + "quote-origin": { + "type": "boolean", + "default": false, + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + }, + "title": "引用原消息", + "description": "在群内回复时是否引用原消息" + }, + "at-sender": { + "type": "boolean", + "default": false, + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + }, + "title": "是否 at 原用户", + "description": "在群内回复时是否@发送者" + }, + "force-delay": { + "type": "object", + "default": { + "min": 0, + "max": 0 + }, + "title": "强制消息延迟范围", + "description": "在将响应内容发回给用户前的强制消息随机延迟时间范围,以防风控,单位是秒", + "properties": { + "min": { + "type": "integer", + "default": 0, + "description": "最小值,单位是秒" + }, + "max": { + "type": "integer", + "default": 0, + "description": "最大值,单位是秒" + } + } + }, + "long-text-process": { + "type": "object", + "title": "长消息处理策略", + "properties": { + "threshold": { + "type": "integer", + "default": 256, + "title": "长消息处理阈值", + "description": "当消息长度超过此阈值时,将启用长消息处理策略" + }, + "strategy": { + "type": "string", + "default": "forward", + "title": "长消息处理策略", + "description": "长消息处理策略,目前支持forward(转发消息组件)和image(文字转图片)。aiocqhttp 和 qq-botpy 不支持 forward 策略" + }, + "font-path": { + "type": "string", + "description": "image的渲染字体。未设置时,如果在windows下,会尝试寻找系统的微软雅黑字体,若找不到,则转为forward策略。未设置时,若不是windows系统,则直接转为forward策略" + } + } + }, + "hide-exception-info": { + "type": "boolean", + "default": true, + "layout": { + "comp": "switch", + "props": { + "color": "primary" + } + }, + "title": "向用户隐藏AI接口的异常信息", + "description": "是否向用户隐藏AI的异常信息,如果为true,当请求AI接口出现异常时,会返回一个错误的提示给用户。而把报错详情输出在控制台。" + } + } +} \ No newline at end of file diff --git a/templates/schema/provider.json b/templates/schema/provider.json new file mode 100644 index 00000000..833b73bb --- /dev/null +++ b/templates/schema/provider.json @@ -0,0 +1,196 @@ +{ + "type": "object", + "layout": "expansion-panels", + "properties": { + "enable-chat": { + "type": "boolean", + "default": true, + "title": "启用聊天功能", + "description": "是否启用 AI 聊天功能" + }, + "enable-vision": { + "type": "boolean", + "default": true, + "title": "启用视觉功能", + "description": "是否开启AI视觉功能。需要使用的模型同时支持视觉功能,详情见元数据板块" + }, + "keys": { + "type": "object", + "title": "模型接口密钥", + "description": "以字典的形式设置若干个密钥组,每个密钥组的键为密钥组名称,值为密钥列表。模型与密钥组的对应关系,请查看元数据板块", + "properties": { + "openai": { + "type": "array", + "title": "OpenAI API 密钥", + "description": "OpenAI API 密钥", + "items": { + "type": "string" + } + }, + "anthropic": { + "type": "array", + "title": "Anthropic API 密钥", + "description": "Anthropic API 密钥", + "items": { + "type": "string" + } + }, + "moonshot": { + "type": "array", + "title": "Moonshot API 密钥", + "description": "Moonshot API 密钥", + "items": { + "type": "string" + } + }, + "deepseek": { + "type": "array", + "title": "DeepSeek API 密钥", + "description": "DeepSeek API 密钥", + "items": { + "type": "string" + } + } + } + }, + "requester": { + "type": "object", + "title": "大模型请求器", + "description": "以字典的形式设置若干个请求器,每个请求器的键为请求器名称,值为请求器配置。模型与请求器的对应关系,请查看元数据板块。实现请求器的方式,请查看插件编写教程", + "properties": { + "openai-chat-completions": { + "type": "object", + "title": "OpenAI API 请求配置", + "description": "仅可编辑 URL 和 超时时间,额外请求参数不支持可视化编辑,请到编辑器编辑", + "properties": { + "base-url": { + "type": "string", + "title": "API URL" + }, + "args": { + "type": "object" + }, + "timeout": { + "type": "number", + "title": "API 请求超时时间", + "default": 120 + } + } + }, + "anthropic-messages": { + "type": "object", + "title": "Anthropic API 请求配置", + "description": "仅可编辑 URL 和 超时时间,额外请求参数不支持可视化编辑,请到编辑器编辑", + "properties": { + "base-url": { + "type": "string", + "title": "API URL" + }, + "args": { + "type": "object" + }, + "timeout": { + "type": "number", + "title": "API 请求超时时间", + "default": 120 + } + } + }, + "moonshot-chat-completions": { + "type": "object", + "title": "Moonshot API 请求配置", + "description": "仅可编辑 URL 和 超时时间,额外请求参数不支持可视化编辑,请到编辑器编辑", + "properties": { + "base-url": { + "type": "string", + "title": "API URL" + }, + "args": { + "type": "object" + }, + "timeout": { + "type": "number", + "title": "API 请求超时时间", + "default": 120 + } + } + }, + "deepseek-chat-completions": { + "type": "object", + "title": "DeepSeek API 请求配置", + "description": "仅可编辑 URL 和 超时时间,额外请求参数不支持可视化编辑,请到编辑器编辑", + "properties": { + "base-url": { + "type": "string", + "title": "API URL" + }, + "args": { + "type": "object" + }, + "timeout": { + "type": "number", + "title": "API 请求超时时间", + "default": 120 + } + } + }, + "ollama-chat": { + "type": "object", + "title": "Ollama API 请求配置", + "description": "仅可编辑 URL 和 超时时间,额外请求参数不支持可视化编辑,请到编辑器编辑", + "properties": { + "base-url": { + "type": "string", + "title": "API URL" + }, + "args": { + "type": "object" + }, + "timeout": { + "type": "number", + "title": "API 请求超时时间", + "default": 600 + } + } + } + } + }, + "model": { + "type": "string", + "title": "所使用的模型名称", + "description": "设置要使用的模型名称。通常来说直接填写模型名称即可,但如果要使用原生接口不是 ChatCompletion 但以 ChatCompletion 接口格式接入的模型,请在模型名称前方加一个 OneAPI/ 前缀以进行区分。 简单来说可以认为是:现阶段非 OpenAI 的模型接入都需要在模型名称前方加一个 OneAPI/ 前缀。\n\n例如:\n\n1. 通过 OneAPI 等中转服务接入了 OpenAI 的 gpt-4 模型,由于 gpt-4 也是使用 ChatCompletion 接口格式进行请求,则可以直接填入 gpt-4;\n2. 通过 OneAPI 等中转服务接入了 Google 的 gemini-pro 模型,由于 gemini-pro 原生接口格式并非 ChatCompletion,因此需要填入 OneAPI/gemini-pro。\n具体支持的模型列表和各个模型对应的请求器和密钥组,请查看元数据板块 llm-models.json " + }, + "prompt-mode": { + "type": "string", + "title": "情景预设(人格)模式", + "description": "值为normal(单预设模式)和full-scenario(完整历史对话模式);normal模式时,使用下方设置的情景预设,也支持读取data/prompts目录下的文件内容作为单个 System Prompt,文件名即为prompt的名称;full-scenario模式时,读取 data/scenario/ 下的完整历史对话作为情景预设", + "enum": ["normal", "full-scenario"], + "default": "normal" + }, + "prompt": { + "type": "object", + "title": "情景预设(人格)", + "description": "设置情景预设(人格)。值为空字符串时,将不使用情景预设(人格)。normal模式时,使用下方设置的情景预设,也支持读取data/prompts目录下的文件内容作为单个 System Prompt,文件名即为prompt的名称;full-scenario模式时,读取 data/scenario/ 下的完整历史对话作为情景预设", + "properties": { + "default": { + "type": "string", + "title": "默认情景预设", + "description": "设置默认情景预设。值为空字符串时,将不使用情景预设(人格)" + } + }, + "patternProperties": { + "^.*$": { + "type": "string", + "title": "情景预设", + "description": "设置情景预设。值为空字符串时,将不使用情景预设(人格)" + } + }, + "required": ["default"] + }, + "runner": { + "type": "string", + "title": "请求运行器", + "description": "设置请求运行器。值为local-agent时,使用内置默认运行器;支持插件扩展" + } + } +} \ No newline at end of file