mirror of
https://github.com/RockChinQ/QChatGPT.git
synced 2024-11-15 19:22:24 +08:00
Merge pull request #644 from RockChinQ/feat/online-data-analysis
Feat: v2 数据统计接口
This commit is contained in:
commit
256bc4dc1e
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -30,4 +30,5 @@ qcapi
|
|||
claude.json
|
||||
bard.json
|
||||
/*yaml
|
||||
!/docker-compose.yaml
|
||||
!/docker-compose.yaml
|
||||
res/instance_id.json
|
|
@ -29,7 +29,6 @@
|
|||
<img alt="Static Badge" src="https://img.shields.io/badge/Linux%E9%83%A8%E7%BD%B2%E8%A7%86%E9%A2%91-208647">
|
||||
</a>
|
||||
|
||||
|
||||
<a href="https://qchatgpt.rockchin.top">项目主页</a> |
|
||||
<a href="https://qchatgpt.rockchin.top/posts/feature.html">功能介绍</a> |
|
||||
<a href="https://qchatgpt.rockchin.top/posts/deploy/">部署文档</a> |
|
||||
|
@ -38,6 +37,4 @@
|
|||
<a href="https://github.com/RockChinQ/QChatGPT/issues/new?assignees=&labels=%E7%8B%AC%E7%AB%8B%E6%8F%92%E4%BB%B6&projects=&template=submit-plugin.yml&title=%5BPlugin%5D%3A+%E8%AF%B7%E6%B1%82%E7%99%BB%E8%AE%B0%E6%96%B0%E6%8F%92%E4%BB%B6">提交插件</a>
|
||||
|
||||
<img alt="回复效果(带有联网插件)" src="https://qchatgpt.rockchin.top/assets/image/QChatGPT-1211.png" width="500px"/>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
31
main.py
31
main.py
|
@ -122,7 +122,7 @@ def complete_tips():
|
|||
non_exist_keys = []
|
||||
|
||||
is_integrity = True
|
||||
logging.info("检查tips模块完整性.")
|
||||
logging.debug("检查tips模块完整性.")
|
||||
tips_template = importlib.import_module('tips-custom-template')
|
||||
tips = importlib.import_module('tips')
|
||||
for key in dir(tips_template):
|
||||
|
@ -145,6 +145,10 @@ async def start_process(first_time_init=False):
|
|||
global known_exception_caught
|
||||
import pkg.utils.context
|
||||
|
||||
# 计算host和instance标识符
|
||||
import pkg.audit.identifier
|
||||
pkg.audit.identifier.init()
|
||||
|
||||
# 加载配置
|
||||
cfg_inst: pymodule_cfg.PythonModuleConfigFile = pymodule_cfg.PythonModuleConfigFile(
|
||||
'config.py',
|
||||
|
@ -158,6 +162,7 @@ async def start_process(first_time_init=False):
|
|||
complete_tips()
|
||||
|
||||
cfg = pkg.utils.context.get_config_manager().data
|
||||
|
||||
# 更新openai库到最新版本
|
||||
if 'upgrade_dependencies' not in cfg or cfg['upgrade_dependencies']:
|
||||
print("正在更新依赖库,请等待...")
|
||||
|
@ -204,6 +209,24 @@ async def start_process(first_time_init=False):
|
|||
break
|
||||
except ValueError:
|
||||
print("请输入数字")
|
||||
|
||||
# 初始化中央服务器 API 交互实例
|
||||
from pkg.utils.center import apigroup
|
||||
from pkg.utils.center import v2 as center_v2
|
||||
|
||||
center_v2_api = center_v2.V2CenterAPI(
|
||||
basic_info={
|
||||
"host_id": pkg.audit.identifier.identifier['host_id'],
|
||||
"instance_id": pkg.audit.identifier.identifier['instance_id'],
|
||||
"semantic_version": pkg.utils.updater.get_current_tag(),
|
||||
"platform": sys.platform,
|
||||
},
|
||||
runtime_info={
|
||||
"admin_id": "{}".format(cfg['admin_qq']),
|
||||
"msg_source": cfg['msg_source_adapter'],
|
||||
}
|
||||
)
|
||||
pkg.utils.context.set_center_v2_api(center_v2_api)
|
||||
|
||||
import pkg.openai.manager
|
||||
import pkg.database.manager
|
||||
|
@ -375,6 +398,12 @@ async def start_process(first_time_init=False):
|
|||
if len(new_announcement) > 0:
|
||||
for announcement in new_announcement:
|
||||
logging.critical("[公告]<{}> {}".format(announcement['time'], announcement['content']))
|
||||
|
||||
# 发送统计数据
|
||||
pkg.utils.context.get_center_v2_api().main.post_announcement_showed(
|
||||
[announcement['id'] for announcement in new_announcement]
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logging.warning("获取公告失败:{}".format(e))
|
||||
|
||||
|
|
83
pkg/audit/identifier.py
Normal file
83
pkg/audit/identifier.py
Normal file
|
@ -0,0 +1,83 @@
|
|||
import os
|
||||
import uuid
|
||||
import json
|
||||
import time
|
||||
|
||||
|
||||
identifier = {
|
||||
'host_id': '',
|
||||
'instance_id': '',
|
||||
'host_create_ts': 0,
|
||||
'instance_create_ts': 0,
|
||||
}
|
||||
|
||||
HOST_ID_FILE = os.path.expanduser('~/.qchatgpt/host_id.json')
|
||||
INSTANCE_ID_FILE = 'res/instance_id.json'
|
||||
|
||||
def init():
|
||||
global identifier
|
||||
|
||||
if not os.path.exists(os.path.expanduser('~/.qchatgpt')):
|
||||
os.mkdir(os.path.expanduser('~/.qchatgpt'))
|
||||
|
||||
if not os.path.exists(HOST_ID_FILE):
|
||||
new_host_id = 'host_'+str(uuid.uuid4())
|
||||
new_host_create_ts = int(time.time())
|
||||
|
||||
with open(HOST_ID_FILE, 'w') as f:
|
||||
json.dump({
|
||||
'host_id': new_host_id,
|
||||
'host_create_ts': new_host_create_ts
|
||||
}, f)
|
||||
|
||||
identifier['host_id'] = new_host_id
|
||||
identifier['host_create_ts'] = new_host_create_ts
|
||||
else:
|
||||
loaded_host_id = ''
|
||||
loaded_host_create_ts = 0
|
||||
|
||||
with open(HOST_ID_FILE, 'r') as f:
|
||||
file_content = json.load(f)
|
||||
loaded_host_id = file_content['host_id']
|
||||
loaded_host_create_ts = file_content['host_create_ts']
|
||||
|
||||
identifier['host_id'] = loaded_host_id
|
||||
identifier['host_create_ts'] = loaded_host_create_ts
|
||||
|
||||
# 检查实例 id
|
||||
if os.path.exists(INSTANCE_ID_FILE):
|
||||
instance_id = {}
|
||||
with open(INSTANCE_ID_FILE, 'r') as f:
|
||||
instance_id = json.load(f)
|
||||
|
||||
if instance_id['host_id'] != identifier['host_id']: # 如果实例 id 不是当前主机的,删除
|
||||
os.remove(INSTANCE_ID_FILE)
|
||||
|
||||
if not os.path.exists(INSTANCE_ID_FILE):
|
||||
new_instance_id = 'instance_'+str(uuid.uuid4())
|
||||
new_instance_create_ts = int(time.time())
|
||||
|
||||
with open(INSTANCE_ID_FILE, 'w') as f:
|
||||
json.dump({
|
||||
'host_id': identifier['host_id'],
|
||||
'instance_id': new_instance_id,
|
||||
'instance_create_ts': new_instance_create_ts
|
||||
}, f)
|
||||
|
||||
identifier['instance_id'] = new_instance_id
|
||||
identifier['instance_create_ts'] = new_instance_create_ts
|
||||
else:
|
||||
loaded_instance_id = ''
|
||||
loaded_instance_create_ts = 0
|
||||
|
||||
with open(INSTANCE_ID_FILE, 'r') as f:
|
||||
file_content = json.load(f)
|
||||
loaded_instance_id = file_content['instance_id']
|
||||
loaded_instance_create_ts = file_content['instance_create_ts']
|
||||
|
||||
identifier['instance_id'] = loaded_instance_id
|
||||
identifier['instance_create_ts'] = loaded_instance_create_ts
|
||||
|
||||
def print_out():
|
||||
global identifier
|
||||
print(identifier)
|
|
@ -91,7 +91,7 @@ class DatabaseManager:
|
|||
`json` text not null
|
||||
)
|
||||
""")
|
||||
print('Database initialized.')
|
||||
# print('Database initialized.')
|
||||
|
||||
# session持久化
|
||||
def persistence_session(self, subject_type: str, subject_number: int, create_timestamp: int,
|
||||
|
|
|
@ -6,6 +6,8 @@ from openai.types.chat import chat_completion_message
|
|||
|
||||
from .model import RequestBase
|
||||
from .. import funcmgr
|
||||
from ...plugin import host
|
||||
from ...utils import context
|
||||
|
||||
|
||||
class ChatCompletionRequest(RequestBase):
|
||||
|
@ -189,6 +191,16 @@ class ChatCompletionRequest(RequestBase):
|
|||
ret = "error: execute function failed: {}".format(str(e))
|
||||
logging.error("函数执行失败: {}".format(str(e)))
|
||||
|
||||
# 上报数据
|
||||
plugin_info = host.get_plugin_info_for_audit(func_name.split('-')[0])
|
||||
audit_func_name = func_name.split('-')[1]
|
||||
audit_func_desc = funcmgr.get_func_schema(func_name)['description']
|
||||
context.get_center_v2_api().usage.post_function_record(
|
||||
plugin=plugin_info,
|
||||
function_name=audit_func_name,
|
||||
function_description=audit_func_desc,
|
||||
)
|
||||
|
||||
self.append_message(
|
||||
role="function",
|
||||
content=json.dumps(ret, ensure_ascii=False),
|
||||
|
|
|
@ -75,7 +75,7 @@ class KeysManager:
|
|||
if self.api_key[key_name] not in self.exceeded:
|
||||
self.using_key = self.api_key[key_name]
|
||||
|
||||
logging.info("使用api-key:" + key_name)
|
||||
logging.debug("使用api-key:" + key_name)
|
||||
|
||||
# 触发插件事件
|
||||
args = {
|
||||
|
|
|
@ -261,6 +261,8 @@ class Session:
|
|||
|
||||
pending_res_text = ""
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# TODO 对不起,我知道这样非常非常屎山,但我之后会重构的
|
||||
for resp in context.get_openai_manager().request_completion(prompts):
|
||||
|
||||
|
@ -349,6 +351,26 @@ class Session:
|
|||
self.just_switched_to_exist_session = False
|
||||
self.set_ongoing()
|
||||
|
||||
# 上报使用量数据
|
||||
session_type = session_name_spt[0]
|
||||
session_id = session_name_spt[1]
|
||||
|
||||
ability_provider = "QChatGPT.Text"
|
||||
usage = total_tokens
|
||||
model_name = context.get_config_manager().data['completion_api_params']['model']
|
||||
response_seconds = int(time.time() - start_time)
|
||||
retry_times = -1 # 暂不记录
|
||||
|
||||
context.get_center_v2_api().usage.post_query_record(
|
||||
session_type=session_type,
|
||||
session_id=session_id,
|
||||
query_ability_provider=ability_provider,
|
||||
usage=usage,
|
||||
model_name=model_name,
|
||||
response_seconds=response_seconds,
|
||||
retry_times=retry_times
|
||||
)
|
||||
|
||||
return res_ans if res_ans[0] != '\n' else res_ans[1:], finish_reason, funcs
|
||||
|
||||
# 删除上一回合并返回上一回合的问题
|
||||
|
|
|
@ -84,23 +84,34 @@ def iter_plugins_name():
|
|||
__current_module_path__ = ""
|
||||
|
||||
|
||||
def walk_plugin_path(module, prefix='', path_prefix=''):
|
||||
def walk_plugin_path(module, prefix="", path_prefix=""):
|
||||
global __current_module_path__
|
||||
"""遍历插件路径"""
|
||||
for item in pkgutil.iter_modules(module.__path__):
|
||||
if item.ispkg:
|
||||
logging.debug("扫描插件包: plugins/{}".format(path_prefix + item.name))
|
||||
walk_plugin_path(__import__(module.__name__ + '.' + item.name, fromlist=['']),
|
||||
prefix + item.name + '.', path_prefix + item.name + '/')
|
||||
walk_plugin_path(
|
||||
__import__(module.__name__ + "." + item.name, fromlist=[""]),
|
||||
prefix + item.name + ".",
|
||||
path_prefix + item.name + "/",
|
||||
)
|
||||
else:
|
||||
try:
|
||||
logging.debug("扫描插件模块: plugins/{}".format(path_prefix + item.name + '.py'))
|
||||
__current_module_path__ = "plugins/"+path_prefix + item.name + '.py'
|
||||
logging.debug(
|
||||
"扫描插件模块: plugins/{}".format(path_prefix + item.name + ".py")
|
||||
)
|
||||
__current_module_path__ = "plugins/" + path_prefix + item.name + ".py"
|
||||
|
||||
importlib.import_module(module.__name__ + '.' + item.name)
|
||||
logging.debug('加载模块: plugins/{} 成功'.format(path_prefix + item.name + '.py'))
|
||||
importlib.import_module(module.__name__ + "." + item.name)
|
||||
logging.debug(
|
||||
"加载模块: plugins/{} 成功".format(path_prefix + item.name + ".py")
|
||||
)
|
||||
except:
|
||||
logging.error('加载模块: plugins/{} 失败: {}'.format(path_prefix + item.name + '.py', sys.exc_info()))
|
||||
logging.error(
|
||||
"加载模块: plugins/{} 失败: {}".format(
|
||||
path_prefix + item.name + ".py", sys.exc_info()
|
||||
)
|
||||
)
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
|
@ -108,7 +119,7 @@ def load_plugins():
|
|||
"""加载插件"""
|
||||
logging.debug("加载插件")
|
||||
PluginHost()
|
||||
walk_plugin_path(__import__('plugins'))
|
||||
walk_plugin_path(__import__("plugins"))
|
||||
|
||||
logging.debug(__plugins__)
|
||||
|
||||
|
@ -132,7 +143,7 @@ def load_plugins():
|
|||
|
||||
def initialize_plugins():
|
||||
"""初始化插件"""
|
||||
logging.info("初始化插件")
|
||||
logging.debug("初始化插件")
|
||||
import pkg.plugin.models as models
|
||||
|
||||
successfully_initialized_plugins = []
|
||||
|
@ -141,14 +152,14 @@ def initialize_plugins():
|
|||
# if not plugin['enabled']:
|
||||
# continue
|
||||
try:
|
||||
models.__current_registering_plugin__ = plugin['name']
|
||||
plugin['instance'] = plugin["class"](plugin_host=context.get_plugin_host())
|
||||
models.__current_registering_plugin__ = plugin["name"]
|
||||
plugin["instance"] = plugin["class"](plugin_host=context.get_plugin_host())
|
||||
# logging.info("插件 {} 已初始化".format(plugin['name']))
|
||||
successfully_initialized_plugins.append(plugin['name'])
|
||||
successfully_initialized_plugins.append(plugin["name"])
|
||||
except:
|
||||
logging.error("插件{}初始化时发生错误: {}".format(plugin['name'], sys.exc_info()))
|
||||
logging.error("插件{}初始化时发生错误: {}".format(plugin["name"], sys.exc_info()))
|
||||
logging.debug(traceback.format_exc())
|
||||
|
||||
|
||||
logging.info("以下插件已初始化: {}".format(", ".join(successfully_initialized_plugins)))
|
||||
|
||||
|
||||
|
@ -172,9 +183,12 @@ def get_github_plugin_repo_label(repo_url: str) -> list[str]:
|
|||
"""获取username, repo"""
|
||||
|
||||
# 提取 username/repo , 正则表达式
|
||||
repo = re.findall(r'(?:https?://github\.com/|git@github\.com:)([^/]+/[^/]+?)(?:\.git|/|$)', repo_url)
|
||||
repo = re.findall(
|
||||
r"(?:https?://github\.com/|git@github\.com:)([^/]+/[^/]+?)(?:\.git|/|$)",
|
||||
repo_url,
|
||||
)
|
||||
|
||||
if len(repo) > 0: # github
|
||||
if len(repo) > 0: # github
|
||||
return repo[0].split("/")
|
||||
else:
|
||||
return None
|
||||
|
@ -183,53 +197,52 @@ def get_github_plugin_repo_label(repo_url: str) -> list[str]:
|
|||
def download_plugin_source_code(repo_url: str, target_path: str) -> str:
|
||||
"""下载插件源码"""
|
||||
# 检查源类型
|
||||
|
||||
|
||||
# 提取 username/repo , 正则表达式
|
||||
repo = get_github_plugin_repo_label(repo_url)
|
||||
repo = get_github_plugin_repo_label(repo_url)
|
||||
|
||||
target_path += repo[1]
|
||||
|
||||
if repo is not None: # github
|
||||
if repo is not None: # github
|
||||
logging.info("从 GitHub 下载插件源码...")
|
||||
|
||||
zipball_url = f"https://api.github.com/repos/{'/'.join(repo)}/zipball/HEAD"
|
||||
|
||||
zip_resp = requests.get(
|
||||
url=zipball_url,
|
||||
proxies=network.wrapper_proxies(),
|
||||
stream=True
|
||||
url=zipball_url, proxies=network.wrapper_proxies(), stream=True
|
||||
)
|
||||
|
||||
if zip_resp.status_code != 200:
|
||||
raise Exception("下载源码失败: {}".format(zip_resp.text))
|
||||
|
||||
if os.path.exists("temp/"+target_path):
|
||||
shutil.rmtree("temp/"+target_path)
|
||||
|
||||
if os.path.exists("temp/" + target_path):
|
||||
shutil.rmtree("temp/" + target_path)
|
||||
|
||||
if os.path.exists(target_path):
|
||||
shutil.rmtree(target_path)
|
||||
|
||||
os.makedirs("temp/"+target_path)
|
||||
os.makedirs("temp/" + target_path)
|
||||
|
||||
with open("temp/"+target_path+"/source.zip", "wb") as f:
|
||||
with open("temp/" + target_path + "/source.zip", "wb") as f:
|
||||
for chunk in zip_resp.iter_content(chunk_size=1024):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
logging.info("下载完成, 解压...")
|
||||
import zipfile
|
||||
with zipfile.ZipFile("temp/"+target_path+"/source.zip", 'r') as zip_ref:
|
||||
zip_ref.extractall("temp/"+target_path)
|
||||
os.remove("temp/"+target_path+"/source.zip")
|
||||
|
||||
with zipfile.ZipFile("temp/" + target_path + "/source.zip", "r") as zip_ref:
|
||||
zip_ref.extractall("temp/" + target_path)
|
||||
os.remove("temp/" + target_path + "/source.zip")
|
||||
|
||||
# 目标是 username-repo-hash , 用正则表达式提取完整的文件夹名,复制到 plugins/repo
|
||||
import glob
|
||||
|
||||
# 获取解压后的文件夹名
|
||||
unzip_dir = glob.glob("temp/"+target_path+"/*")[0]
|
||||
unzip_dir = glob.glob("temp/" + target_path + "/*")[0]
|
||||
|
||||
# 复制到 plugins/repo
|
||||
shutil.copytree(unzip_dir, target_path+"/")
|
||||
shutil.copytree(unzip_dir, target_path + "/")
|
||||
|
||||
# 删除解压后的文件夹
|
||||
shutil.rmtree(unzip_dir)
|
||||
|
@ -237,18 +250,20 @@ def download_plugin_source_code(repo_url: str, target_path: str) -> str:
|
|||
logging.info("解压完成")
|
||||
else:
|
||||
raise Exception("暂不支持的源类型,请使用 GitHub 仓库发行插件。")
|
||||
|
||||
|
||||
return repo[1]
|
||||
|
||||
|
||||
def check_requirements(path: str):
|
||||
# 检查此目录是否包含requirements.txt
|
||||
if os.path.exists(path+"/requirements.txt"):
|
||||
if os.path.exists(path + "/requirements.txt"):
|
||||
logging.info("检测到requirements.txt,正在安装依赖")
|
||||
import pkg.utils.pkgmgr
|
||||
pkg.utils.pkgmgr.install_requirements(path+"/requirements.txt")
|
||||
|
||||
pkg.utils.pkgmgr.install_requirements(path + "/requirements.txt")
|
||||
|
||||
import pkg.utils.log as log
|
||||
|
||||
log.reset_logging()
|
||||
|
||||
|
||||
|
@ -257,25 +272,43 @@ def install_plugin(repo_url: str):
|
|||
|
||||
repo_label = download_plugin_source_code(repo_url, "plugins/")
|
||||
|
||||
check_requirements("plugins/"+repo_label)
|
||||
check_requirements("plugins/" + repo_label)
|
||||
|
||||
metadata.set_plugin_metadata(repo_label, repo_url, int(time.time()), "HEAD")
|
||||
|
||||
# 上报安装记录
|
||||
context.get_center_v2_api().plugin.post_install_record(
|
||||
plugin={
|
||||
"name": "unknown",
|
||||
"remote": repo_url,
|
||||
"author": "unknown",
|
||||
"version": "HEAD",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def uninstall_plugin(plugin_name: str) -> str:
|
||||
"""卸载插件"""
|
||||
if plugin_name not in __plugins__:
|
||||
raise Exception("插件不存在")
|
||||
|
||||
plugin_info = get_plugin_info_for_audit(plugin_name)
|
||||
|
||||
# 获取文件夹路径
|
||||
plugin_path = __plugins__[plugin_name]['path'].replace("\\", "/")
|
||||
plugin_path = __plugins__[plugin_name]["path"].replace("\\", "/")
|
||||
|
||||
# 剪切路径为plugins/插件名
|
||||
plugin_path = plugin_path.split("plugins/")[1].split("/")[0]
|
||||
|
||||
# 删除文件夹
|
||||
shutil.rmtree("plugins/"+plugin_path)
|
||||
return "plugins/"+plugin_path
|
||||
shutil.rmtree("plugins/" + plugin_path)
|
||||
|
||||
# 上报卸载记录
|
||||
context.get_center_v2_api().plugin.post_remove_record(
|
||||
plugin=plugin_info
|
||||
)
|
||||
|
||||
return "plugins/" + plugin_path
|
||||
|
||||
|
||||
def update_plugin(plugin_name: str):
|
||||
|
@ -287,12 +320,26 @@ def update_plugin(plugin_name: str):
|
|||
|
||||
if meta == {}:
|
||||
raise Exception("没有此插件元数据信息,无法更新")
|
||||
|
||||
remote_url = meta['source']
|
||||
if remote_url == "https://github.com/RockChinQ/QChatGPT" or remote_url == "https://gitee.com/RockChin/QChatGPT" \
|
||||
or remote_url == "" or remote_url is None or remote_url == "http://github.com/RockChinQ/QChatGPT" or remote_url == "http://gitee.com/RockChin/QChatGPT":
|
||||
raise Exception("插件没有远程地址记录,无法更新")
|
||||
|
||||
old_plugin_info = get_plugin_info_for_audit(plugin_name)
|
||||
|
||||
context.get_center_v2_api().plugin.post_update_record(
|
||||
plugin=old_plugin_info,
|
||||
old_version=old_plugin_info['version'],
|
||||
new_version='HEAD',
|
||||
)
|
||||
|
||||
remote_url = meta["source"]
|
||||
if (
|
||||
remote_url == "https://github.com/RockChinQ/QChatGPT"
|
||||
or remote_url == "https://gitee.com/RockChin/QChatGPT"
|
||||
or remote_url == ""
|
||||
or remote_url is None
|
||||
or remote_url == "http://github.com/RockChinQ/QChatGPT"
|
||||
or remote_url == "http://gitee.com/RockChin/QChatGPT"
|
||||
):
|
||||
raise Exception("插件没有远程地址记录,无法更新")
|
||||
|
||||
# 重新安装插件
|
||||
logging.info("正在重新安装插件以进行更新...")
|
||||
|
||||
|
@ -301,7 +348,7 @@ def update_plugin(plugin_name: str):
|
|||
|
||||
def get_plugin_name_by_path_name(plugin_path_name: str) -> str:
|
||||
for k, v in __plugins__.items():
|
||||
if v['path'] == "plugins/"+plugin_path_name+"/main.py":
|
||||
if v["path"] == "plugins/" + plugin_path_name + "/main.py":
|
||||
return k
|
||||
return None
|
||||
|
||||
|
@ -309,8 +356,8 @@ def get_plugin_name_by_path_name(plugin_path_name: str) -> str:
|
|||
def get_plugin_path_name_by_plugin_name(plugin_name: str) -> str:
|
||||
if plugin_name not in __plugins__:
|
||||
return None
|
||||
|
||||
plugin_main_module_path = __plugins__[plugin_name]['path']
|
||||
|
||||
plugin_main_module_path = __plugins__[plugin_name]["path"]
|
||||
|
||||
plugin_main_module_path = plugin_main_module_path.replace("\\", "/")
|
||||
|
||||
|
@ -319,8 +366,29 @@ def get_plugin_path_name_by_plugin_name(plugin_name: str) -> str:
|
|||
return spt[1]
|
||||
|
||||
|
||||
def get_plugin_info_for_audit(plugin_name: str) -> dict:
|
||||
"""获取插件信息"""
|
||||
if plugin_name not in __plugins__:
|
||||
return {}
|
||||
plugin = __plugins__[plugin_name]
|
||||
|
||||
name = plugin["name"]
|
||||
meta = metadata.get_plugin_metadata(get_plugin_path_name_by_plugin_name(name))
|
||||
remote = meta["source"] if meta != {} else ""
|
||||
author = plugin["author"]
|
||||
version = plugin["version"]
|
||||
|
||||
return {
|
||||
"name": name,
|
||||
"remote": remote,
|
||||
"author": author,
|
||||
"version": version,
|
||||
}
|
||||
|
||||
|
||||
class EventContext:
|
||||
"""事件上下文"""
|
||||
|
||||
eid = 0
|
||||
"""事件编号"""
|
||||
|
||||
|
@ -395,6 +463,7 @@ class EventContext:
|
|||
def emit(event_name: str, **kwargs) -> EventContext:
|
||||
"""触发事件"""
|
||||
import pkg.utils.context as context
|
||||
|
||||
if context.get_plugin_host() is None:
|
||||
return None
|
||||
return context.get_plugin_host().emit(event_name, **kwargs)
|
||||
|
@ -443,9 +512,10 @@ class PluginHost:
|
|||
|
||||
event_context = EventContext(event_name)
|
||||
logging.debug("触发事件: {} ({})".format(event_name, event_context.eid))
|
||||
|
||||
emitted_plugins = []
|
||||
for plugin in iter_plugins():
|
||||
|
||||
if not plugin['enabled']:
|
||||
if not plugin["enabled"]:
|
||||
continue
|
||||
|
||||
# if plugin['instance'] is None:
|
||||
|
@ -457,9 +527,11 @@ class PluginHost:
|
|||
# logging.error("插件 {} 初始化时发生错误: {}".format(plugin['name'], sys.exc_info()))
|
||||
# continue
|
||||
|
||||
if 'hooks' not in plugin or event_name not in plugin['hooks']:
|
||||
if "hooks" not in plugin or event_name not in plugin["hooks"]:
|
||||
continue
|
||||
|
||||
emitted_plugins.append(plugin['name'])
|
||||
|
||||
hooks = []
|
||||
if event_name in plugin["hooks"]:
|
||||
hooks = plugin["hooks"][event_name]
|
||||
|
@ -467,27 +539,44 @@ class PluginHost:
|
|||
try:
|
||||
already_prevented_default = event_context.is_prevented_default()
|
||||
|
||||
kwargs['host'] = context.get_plugin_host()
|
||||
kwargs['event'] = event_context
|
||||
kwargs["host"] = context.get_plugin_host()
|
||||
kwargs["event"] = event_context
|
||||
|
||||
hook(plugin['instance'], **kwargs)
|
||||
hook(plugin["instance"], **kwargs)
|
||||
|
||||
if event_context.is_prevented_default() and not already_prevented_default:
|
||||
logging.debug("插件 {} 已要求阻止事件 {} 的默认行为".format(plugin['name'], event_name))
|
||||
if (
|
||||
event_context.is_prevented_default()
|
||||
and not already_prevented_default
|
||||
):
|
||||
logging.debug(
|
||||
"插件 {} 已要求阻止事件 {} 的默认行为".format(plugin["name"], event_name)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logging.error("插件{}响应事件{}时发生错误".format(plugin['name'], event_name))
|
||||
logging.error("插件{}响应事件{}时发生错误".format(plugin["name"], event_name))
|
||||
logging.error(traceback.format_exc())
|
||||
|
||||
# print("done:{}".format(plugin['name']))
|
||||
if event_context.is_prevented_postorder():
|
||||
logging.debug("插件 {} 阻止了后序插件的执行".format(plugin['name']))
|
||||
logging.debug("插件 {} 阻止了后序插件的执行".format(plugin["name"]))
|
||||
break
|
||||
|
||||
logging.debug("事件 {} ({}) 处理完毕,返回值: {}".format(event_name, event_context.eid,
|
||||
event_context.__return_value__))
|
||||
logging.debug(
|
||||
"事件 {} ({}) 处理完毕,返回值: {}".format(
|
||||
event_name, event_context.eid, event_context.__return_value__
|
||||
)
|
||||
)
|
||||
|
||||
if len(emitted_plugins) > 0:
|
||||
plugins_info = [get_plugin_info_for_audit(p) for p in emitted_plugins]
|
||||
|
||||
context.get_center_v2_api().usage.post_event_record(
|
||||
plugins=plugins_info,
|
||||
event_name=event_name,
|
||||
)
|
||||
|
||||
return event_context
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
|
|
@ -4,11 +4,10 @@ import pkgutil
|
|||
import traceback
|
||||
import json
|
||||
|
||||
|
||||
__command_list__ = {}
|
||||
|
||||
import tips as tips_custom
|
||||
|
||||
|
||||
__command_list__ = {}
|
||||
"""命令树
|
||||
|
||||
结构:
|
||||
|
|
|
@ -125,6 +125,10 @@ class QQBotManager:
|
|||
else:
|
||||
self.adapter = context.get_qqbot_manager().adapter
|
||||
self.bot_account_id = context.get_qqbot_manager().bot_account_id
|
||||
|
||||
# 保存 account_id 到审计模块
|
||||
from ..utils.center import apigroup
|
||||
apigroup.APIGroup._runtime_info['account_id'] = "{}".format(self.bot_account_id)
|
||||
|
||||
context.set_qqbot_manager(self)
|
||||
|
||||
|
|
0
pkg/utils/center/__init__.py
Normal file
0
pkg/utils/center/__init__.py
Normal file
88
pkg/utils/center/apigroup.py
Normal file
88
pkg/utils/center/apigroup.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
import abc
|
||||
import uuid
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class APIGroup(metaclass=abc.ABCMeta):
|
||||
"""API 组抽象类"""
|
||||
_basic_info: dict = None
|
||||
_runtime_info: dict = None
|
||||
|
||||
prefix = None
|
||||
|
||||
def __init__(self, prefix: str):
|
||||
self.prefix = prefix
|
||||
|
||||
def do(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
data: dict = None,
|
||||
params: dict = None,
|
||||
headers: dict = {},
|
||||
**kwargs
|
||||
):
|
||||
"""执行一个请求"""
|
||||
def thr_wrapper(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
data: dict = None,
|
||||
params: dict = None,
|
||||
headers: dict = {},
|
||||
**kwargs
|
||||
):
|
||||
try:
|
||||
url = self.prefix + path
|
||||
data = json.dumps(data)
|
||||
headers['Content-Type'] = 'application/json'
|
||||
|
||||
ret = requests.request(
|
||||
method,
|
||||
url,
|
||||
data=data,
|
||||
params=params,
|
||||
headers=headers,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
logging.debug("data: %s", data)
|
||||
|
||||
logging.debug("ret: %s", ret.json())
|
||||
except Exception as e:
|
||||
logging.debug("上报数据失败: %s", e)
|
||||
|
||||
thr = threading.Thread(target=thr_wrapper, args=(
|
||||
self,
|
||||
method,
|
||||
path,
|
||||
data,
|
||||
params,
|
||||
headers,
|
||||
), kwargs=kwargs)
|
||||
thr.start()
|
||||
|
||||
|
||||
def gen_rid(
|
||||
self
|
||||
):
|
||||
"""生成一个请求 ID"""
|
||||
return str(uuid.uuid4())
|
||||
|
||||
def basic_info(
|
||||
self
|
||||
):
|
||||
"""获取基本信息"""
|
||||
basic_info = APIGroup._basic_info.copy()
|
||||
basic_info['rid'] = self.gen_rid()
|
||||
return basic_info
|
||||
|
||||
def runtime_info(
|
||||
self
|
||||
):
|
||||
"""获取运行时信息"""
|
||||
return APIGroup._runtime_info
|
0
pkg/utils/center/groups/__init__.py
Normal file
0
pkg/utils/center/groups/__init__.py
Normal file
48
pkg/utils/center/groups/main.py
Normal file
48
pkg/utils/center/groups/main.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from .. import apigroup
|
||||
|
||||
|
||||
class V2MainDataAPI(apigroup.APIGroup):
|
||||
"""主程序相关 数据API"""
|
||||
|
||||
def __init__(self, prefix: str):
|
||||
super().__init__(prefix+"/main")
|
||||
|
||||
def post_update_record(
|
||||
self,
|
||||
spent_seconds: int,
|
||||
infer_reason: str,
|
||||
old_version: str,
|
||||
new_version: str,
|
||||
):
|
||||
"""提交更新记录"""
|
||||
return self.do(
|
||||
"POST",
|
||||
"/update",
|
||||
data={
|
||||
"basic": self.basic_info(),
|
||||
"update_info": {
|
||||
"spent_seconds": spent_seconds,
|
||||
"infer_reason": infer_reason,
|
||||
"old_version": old_version,
|
||||
"new_version": new_version,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def post_announcement_showed(
|
||||
self,
|
||||
ids: list[int],
|
||||
):
|
||||
"""提交公告已阅"""
|
||||
return self.do(
|
||||
"POST",
|
||||
"/announcement",
|
||||
data={
|
||||
"basic": self.basic_info(),
|
||||
"announcement_info": {
|
||||
"ids": ids,
|
||||
}
|
||||
}
|
||||
)
|
58
pkg/utils/center/groups/plugin.py
Normal file
58
pkg/utils/center/groups/plugin.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from .. import apigroup
|
||||
|
||||
|
||||
class V2PluginDataAPI(apigroup.APIGroup):
|
||||
"""插件数据相关 API"""
|
||||
|
||||
def __init__(self, prefix: str):
|
||||
super().__init__(prefix+"/plugin")
|
||||
|
||||
def post_install_record(
|
||||
self,
|
||||
plugin: dict
|
||||
):
|
||||
"""提交插件安装记录"""
|
||||
return self.do(
|
||||
"POST",
|
||||
"/install",
|
||||
data={
|
||||
"basic": self.basic_info(),
|
||||
"plugin": plugin,
|
||||
}
|
||||
)
|
||||
|
||||
def post_remove_record(
|
||||
self,
|
||||
plugin: dict
|
||||
):
|
||||
"""提交插件卸载记录"""
|
||||
return self.do(
|
||||
"POST",
|
||||
"/remove",
|
||||
data={
|
||||
"basic": self.basic_info(),
|
||||
"plugin": plugin,
|
||||
}
|
||||
)
|
||||
|
||||
def post_update_record(
|
||||
self,
|
||||
plugin: dict,
|
||||
old_version: str,
|
||||
new_version: str,
|
||||
):
|
||||
"""提交插件更新记录"""
|
||||
return self.do(
|
||||
"POST",
|
||||
"/update",
|
||||
data={
|
||||
"basic": self.basic_info(),
|
||||
"plugin": plugin,
|
||||
"update_info": {
|
||||
"old_version": old_version,
|
||||
"new_version": new_version,
|
||||
}
|
||||
}
|
||||
)
|
81
pkg/utils/center/groups/usage.py
Normal file
81
pkg/utils/center/groups/usage.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from .. import apigroup
|
||||
|
||||
|
||||
class V2UsageDataAPI(apigroup.APIGroup):
|
||||
"""使用量数据相关 API"""
|
||||
|
||||
def __init__(self, prefix: str):
|
||||
super().__init__(prefix+"/usage")
|
||||
|
||||
def post_query_record(
|
||||
self,
|
||||
session_type: str,
|
||||
session_id: str,
|
||||
query_ability_provider: str,
|
||||
usage: int,
|
||||
model_name: str,
|
||||
response_seconds: int,
|
||||
retry_times: int,
|
||||
):
|
||||
"""提交请求记录"""
|
||||
return self.do(
|
||||
"POST",
|
||||
"/query",
|
||||
data={
|
||||
"basic": self.basic_info(),
|
||||
"runtime": self.runtime_info(),
|
||||
"session_info": {
|
||||
"type": session_type,
|
||||
"id": session_id,
|
||||
},
|
||||
"query_info": {
|
||||
"ability_provider": query_ability_provider,
|
||||
"usage": usage,
|
||||
"model_name": model_name,
|
||||
"response_seconds": response_seconds,
|
||||
"retry_times": retry_times,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def post_event_record(
|
||||
self,
|
||||
plugins: list[dict],
|
||||
event_name: str,
|
||||
):
|
||||
"""提交事件触发记录"""
|
||||
return self.do(
|
||||
"POST",
|
||||
"/event",
|
||||
data={
|
||||
"basic": self.basic_info(),
|
||||
"runtime": self.runtime_info(),
|
||||
"plugins": plugins,
|
||||
"event_info": {
|
||||
"name": event_name,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
def post_function_record(
|
||||
self,
|
||||
plugin: dict,
|
||||
function_name: str,
|
||||
function_description: str,
|
||||
):
|
||||
"""提交内容函数使用记录"""
|
||||
return self.do(
|
||||
"POST",
|
||||
"/function",
|
||||
data={
|
||||
"basic": self.basic_info(),
|
||||
"plugin": plugin,
|
||||
"function_info": {
|
||||
"name": function_name,
|
||||
"description": function_description,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
35
pkg/utils/center/v2.py
Normal file
35
pkg/utils/center/v2.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from . import apigroup
|
||||
from .groups import main
|
||||
from .groups import usage
|
||||
from .groups import plugin
|
||||
|
||||
|
||||
BACKEND_URL = "https://api.qchatgpt.rockchin.top/api/v2"
|
||||
|
||||
class V2CenterAPI:
|
||||
"""中央服务器 v2 API 交互类"""
|
||||
|
||||
main: main.V2MainDataAPI = None
|
||||
"""主 API 组"""
|
||||
|
||||
usage: usage.V2UsageDataAPI = None
|
||||
"""使用量 API 组"""
|
||||
|
||||
plugin: plugin.V2PluginDataAPI = None
|
||||
"""插件 API 组"""
|
||||
|
||||
def __init__(self, basic_info: dict = None, runtime_info: dict = None):
|
||||
"""初始化"""
|
||||
|
||||
logging.debug("basic_info: %s, runtime_info: %s", basic_info, runtime_info)
|
||||
|
||||
apigroup.APIGroup._basic_info = basic_info
|
||||
apigroup.APIGroup._runtime_info = runtime_info
|
||||
|
||||
self.main = main.V2MainDataAPI(BACKEND_URL)
|
||||
self.usage = usage.V2UsageDataAPI(BACKEND_URL)
|
||||
self.plugin = plugin.V2PluginDataAPI(BACKEND_URL)
|
|
@ -8,6 +8,7 @@ from ..openai import manager as openai_mgr
|
|||
from ..qqbot import manager as qqbot_mgr
|
||||
from ..config import manager as config_mgr
|
||||
from ..plugin import host as plugin_host
|
||||
from .center import v2 as center_v2
|
||||
|
||||
|
||||
context = {
|
||||
|
@ -114,3 +115,16 @@ def get_thread_ctl() -> threadctl.ThreadCtl:
|
|||
t: threadctl.ThreadCtl = context['pool_ctl']
|
||||
context_lock.release()
|
||||
return t
|
||||
|
||||
|
||||
def set_center_v2_api(inst: center_v2.V2CenterAPI):
|
||||
context_lock.acquire()
|
||||
context['center_v2_api'] = inst
|
||||
context_lock.release()
|
||||
|
||||
|
||||
def get_center_v2_api() -> center_v2.V2CenterAPI:
|
||||
context_lock.acquire()
|
||||
t: center_v2.V2CenterAPI = context['center_v2_api']
|
||||
context_lock.release()
|
||||
return t
|
7
pkg/utils/platform.py
Normal file
7
pkg/utils/platform.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def get_platform() -> str:
|
||||
"""获取当前平台"""
|
||||
return sys.platform
|
|
@ -1,11 +1,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import os.path
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
from . import constants
|
||||
from . import network
|
||||
from . import context
|
||||
|
||||
|
||||
def check_dulwich_closure():
|
||||
|
@ -107,7 +111,10 @@ def compare_version_str(v0: str, v1: str) -> int:
|
|||
|
||||
def update_all(cli: bool = False) -> bool:
|
||||
"""检查更新并下载源码"""
|
||||
start_time = time.time()
|
||||
|
||||
current_tag = get_current_tag()
|
||||
old_tag = current_tag
|
||||
|
||||
rls_list = get_release_list()
|
||||
|
||||
|
@ -200,6 +207,13 @@ def update_all(cli: bool = False) -> bool:
|
|||
with open("current_tag", "w") as f:
|
||||
f.write(current_tag)
|
||||
|
||||
context.get_center_v2_api().main.post_update_record(
|
||||
spent_seconds=int(time.time()-start_time),
|
||||
infer_reason="update",
|
||||
old_version=old_tag,
|
||||
new_version=current_tag,
|
||||
)
|
||||
|
||||
# 通知管理员
|
||||
if not cli:
|
||||
import pkg.utils.context
|
||||
|
|
|
@ -11,3 +11,4 @@ nakuru-project-idk
|
|||
CallingGPT
|
||||
tiktoken
|
||||
PyYaml
|
||||
aiohttp
|
43
tests/identifier_test/host_identifier.py
Normal file
43
tests/identifier_test/host_identifier.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
import os
|
||||
import uuid
|
||||
import json
|
||||
|
||||
# 向 ~/.qchatgpt 写入一个 标识符
|
||||
|
||||
if not os.path.exists(os.path.expanduser('~/.qchatgpt')):
|
||||
os.mkdir(os.path.expanduser('~/.qchatgpt'))
|
||||
|
||||
identifier = {
|
||||
"host_id": "host_"+str(uuid.uuid4()),
|
||||
}
|
||||
|
||||
if not os.path.exists(os.path.expanduser('~/.qchatgpt/host.json')):
|
||||
print('create ~/.qchatgpt/host.json')
|
||||
with open(os.path.expanduser('~/.qchatgpt/host.json'), 'w') as f:
|
||||
json.dump(identifier, f)
|
||||
else:
|
||||
print('load ~/.qchatgpt/host.json')
|
||||
with open(os.path.expanduser('~/.qchatgpt/host.json'), 'r') as f:
|
||||
identifier = json.load(f)
|
||||
|
||||
print(identifier)
|
||||
|
||||
instance_id = {
|
||||
"host_id": identifier['host_id'],
|
||||
"instance_id": "instance_"+str(uuid.uuid4()),
|
||||
}
|
||||
|
||||
# 实例 id
|
||||
if os.path.exists("res/instance_id.json"):
|
||||
with open("res/instance_id.json", 'r') as f:
|
||||
instance_id = json.load(f)
|
||||
|
||||
if instance_id['host_id'] != identifier['host_id']:
|
||||
os.remove("res/instance_id.json")
|
||||
|
||||
if not os.path.exists("res/instance_id.json"):
|
||||
print('create res/instance_id.json')
|
||||
with open("res/instance_id.json", 'w') as f:
|
||||
json.dump(instance_id, f)
|
||||
|
||||
print(instance_id)
|
Loading…
Reference in New Issue
Block a user