Browse Source

Merge pull request #1328 from zhayujie/feat-1.3.3

feat: support global plugin config for docker env
master
zhayujie GitHub 1 year ago
parent
commit
e0d2e34980
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 120 additions and 41 deletions
  1. +26
    -2
      config.py
  2. +10
    -9
      plugins/banwords/banwords.py
  3. +2
    -7
      plugins/bdunit/bdunit.py
  4. +24
    -0
      plugins/config.json.template
  5. +7
    -10
      plugins/godcmd/godcmd.py
  6. +22
    -0
      plugins/plugin.py
  7. +27
    -3
      plugins/plugin_manager.py
  8. +2
    -10
      plugins/tool/tool.py

+ 26
- 2
config.py View File

@@ -25,7 +25,7 @@ available_setting = {
"single_chat_reply_suffix": "", # 私聊时自动回复的后缀,\n 可以换行 "single_chat_reply_suffix": "", # 私聊时自动回复的后缀,\n 可以换行
"group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复 "group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复
"group_chat_reply_prefix": "", # 群聊时自动回复的前缀 "group_chat_reply_prefix": "", # 群聊时自动回复的前缀
"group_chat_reply_suffix": "", # 群聊时自动回复的后缀,\n 可以换行
"group_chat_reply_suffix": "", # 群聊时自动回复的后缀,\n 可以换行
"group_chat_keyword": [], # 群聊时包含该关键词则会触发机器人回复 "group_chat_keyword": [], # 群聊时包含该关键词则会触发机器人回复
"group_at_off": False, # 是否关闭群聊时@bot的触发 "group_at_off": False, # 是否关闭群聊时@bot的触发
"group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], # 开启自动回复的群名称列表 "group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], # 开启自动回复的群名称列表
@@ -37,7 +37,8 @@ available_setting = {
"image_create_size": "256x256", # 图片大小,可选有 256x256, 512x512, 1024x1024 "image_create_size": "256x256", # 图片大小,可选有 256x256, 512x512, 1024x1024
# chatgpt会话参数 # chatgpt会话参数
"expires_in_seconds": 3600, # 无操作会话的过期时间 "expires_in_seconds": 3600, # 无操作会话的过期时间
"character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", # 人格描述
# 人格描述
"character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。",
"conversation_max_tokens": 1000, # 支持上下文记忆的最多字符数 "conversation_max_tokens": 1000, # 支持上下文记忆的最多字符数
# chatgpt限流配置 # chatgpt限流配置
"rate_limit_chatgpt": 20, # chatgpt的调用频率限制 "rate_limit_chatgpt": 20, # chatgpt的调用频率限制
@@ -228,3 +229,26 @@ def subscribe_msg():
trigger_prefix = conf().get("single_chat_prefix", [""])[0] trigger_prefix = conf().get("single_chat_prefix", [""])[0]
msg = conf().get("subscribe_msg", "") msg = conf().get("subscribe_msg", "")
return msg.format(trigger_prefix=trigger_prefix) return msg.format(trigger_prefix=trigger_prefix)


# global plugin config
plugin_config = {}


def write_plugin_config(pconf: dict):
"""
写入插件全局配置
:param pconf: 全量插件配置
"""
global plugin_config
for k in pconf:
plugin_config[k.lower()] = pconf[k]


def pconf(plugin_name: str) -> dict:
"""
根据插件名称获取配置
:param plugin_name: 插件名称
:return: 该插件的配置项
"""
return plugin_config.get(plugin_name.lower())

+ 10
- 9
plugins/banwords/banwords.py View File

@@ -24,16 +24,17 @@ class Banwords(Plugin):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
try: try:
# load config
conf = super().load_config()
curdir = os.path.dirname(__file__) curdir = os.path.dirname(__file__)
config_path = os.path.join(curdir, "config.json")
conf = None
if not os.path.exists(config_path):
conf = {"action": "ignore"}
with open(config_path, "w") as f:
json.dump(conf, f, indent=4)
else:
with open(config_path, "r") as f:
conf = json.load(f)
if not conf:
# 配置不存在则写入默认配置
config_path = os.path.join(curdir, "config.json")
if not os.path.exists(config_path):
conf = {"action": "ignore"}
with open(config_path, "w") as f:
json.dump(conf, f, indent=4)

self.searchr = WordsSearch() self.searchr = WordsSearch()
self.action = conf["action"] self.action = conf["action"]
banwords_path = os.path.join(curdir, "banwords.txt") banwords_path = os.path.join(curdir, "banwords.txt")


+ 2
- 7
plugins/bdunit/bdunit.py View File

@@ -29,14 +29,9 @@ class BDunit(Plugin):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
try: try:
curdir = os.path.dirname(__file__)
config_path = os.path.join(curdir, "config.json")
conf = None
if not os.path.exists(config_path):
conf = super().load_config()
if not conf:
raise Exception("config.json not found") raise Exception("config.json not found")
else:
with open(config_path, "r") as f:
conf = json.load(f)
self.service_id = conf["service_id"] self.service_id = conf["service_id"]
self.api_key = conf["api_key"] self.api_key = conf["api_key"]
self.secret_key = conf["secret_key"] self.secret_key = conf["secret_key"]


+ 24
- 0
plugins/config.json.template View File

@@ -0,0 +1,24 @@
{
"godcmd": {
"password": "",
"admin_users": []
},
"banwords": {
"action": "replace",
"reply_filter": true,
"reply_action": "ignore"
},
"tool": {
"tools": [
"python",
"url-get",
"terminal",
"meteo-weather"
],
"kwargs": {
"top_k_results": 2,
"no_default": false,
"model_name": "gpt-3.5-turbo"
}
}
}

+ 7
- 10
plugins/godcmd/godcmd.py View File

@@ -178,16 +178,13 @@ class Godcmd(Plugin):
def __init__(self): def __init__(self):
super().__init__() super().__init__()


curdir = os.path.dirname(__file__)
config_path = os.path.join(curdir, "config.json")
gconf = None
if not os.path.exists(config_path):
gconf = {"password": "", "admin_users": []}
with open(config_path, "w") as f:
json.dump(gconf, f, indent=4)
else:
with open(config_path, "r") as f:
gconf = json.load(f)
config_path = os.path.join(os.path.dirname(__file__), "config.json")
gconf = super().load_config()
if not gconf:
if not os.path.exists(config_path):
gconf = {"password": "", "admin_users": []}
with open(config_path, "w") as f:
json.dump(gconf, f, indent=4)
if gconf["password"] == "": if gconf["password"] == "":
self.temp_password = "".join(random.sample(string.digits, 4)) self.temp_password = "".join(random.sample(string.digits, 4))
logger.info("[Godcmd] 因未设置口令,本次的临时口令为%s。" % self.temp_password) logger.info("[Godcmd] 因未设置口令,本次的临时口令为%s。" % self.temp_password)


+ 22
- 0
plugins/plugin.py View File

@@ -1,6 +1,28 @@
import os
import json
from config import pconf
from common.log import logger


class Plugin: class Plugin:
def __init__(self): def __init__(self):
self.handlers = {} self.handlers = {}


def load_config(self) -> dict:
"""
加载当前插件配置
:return: 插件配置字典
"""
# 优先获取 plugins/config.json 中的全局配置
plugin_conf = pconf(self.name)
if not plugin_conf:
# 全局配置不存在,则获取插件目录下的配置
plugin_config_path = os.path.join(self.path, "config.json")
if os.path.exists(plugin_config_path):
with open(plugin_config_path, "r") as f:
plugin_conf = json.load(f)
logger.debug(f"loading plugin config, plugin_name={self.name}, conf={plugin_conf}")
return plugin_conf

def get_help_text(self, **kwargs): def get_help_text(self, **kwargs):
return "暂无帮助信息" return "暂无帮助信息"

+ 27
- 3
plugins/plugin_manager.py View File

@@ -9,7 +9,7 @@ import sys
from common.log import logger from common.log import logger
from common.singleton import singleton from common.singleton import singleton
from common.sorted_dict import SortedDict from common.sorted_dict import SortedDict
from config import conf
from config import conf, write_plugin_config


from .event import * from .event import *


@@ -62,6 +62,28 @@ class PluginManager:
self.save_config() self.save_config()
return pconf return pconf


@staticmethod
def _load_all_config():
"""
背景: 目前插件配置存放于每个插件目录的config.json下,docker运行时不方便进行映射,故增加统一管理的入口,优先
加载 plugins/config.json,原插件目录下的config.json 不受影响

从 plugins/config.json 中加载所有插件的配置并写入 config.py 的全局配置中,供插件中使用
插件实例中通过 config.pconf(plugin_name) 即可获取该插件的配置
"""
all_config_path = "./plugins/config.json"
try:
if os.path.exists(all_config_path):
# read from all plugins config
with open(all_config_path, "r", encoding="utf-8") as f:
all_conf = json.load(f)
logger.info(f"load all config from plugins/config.json: {all_conf}")

# write to global config
write_plugin_config(all_conf)
except Exception as e:
logger.error(e)

def scan_plugins(self): def scan_plugins(self):
logger.info("Scaning plugins ...") logger.info("Scaning plugins ...")
plugins_dir = "./plugins" plugins_dir = "./plugins"
@@ -88,7 +110,7 @@ class PluginManager:
self.loaded[plugin_path] = importlib.import_module(import_path) self.loaded[plugin_path] = importlib.import_module(import_path)
self.current_plugin_path = None self.current_plugin_path = None
except Exception as e: except Exception as e:
logger.exception("Failed to import plugin %s: %s" % (plugin_name, e))
logger.warn("Failed to import plugin %s: %s" % (plugin_name, e))
continue continue
pconf = self.pconf pconf = self.pconf
news = [self.plugins[name] for name in self.plugins] news = [self.plugins[name] for name in self.plugins]
@@ -123,7 +145,7 @@ class PluginManager:
try: try:
instance = plugincls() instance = plugincls()
except Exception as e: except Exception as e:
logger.exception("Failed to init %s, diabled. %s" % (name, e))
logger.warn("Failed to init %s, diabled. %s" % (name, e))
self.disable_plugin(name) self.disable_plugin(name)
failed_plugins.append(name) failed_plugins.append(name)
continue continue
@@ -149,6 +171,8 @@ class PluginManager:
def load_plugins(self): def load_plugins(self):
self.load_config() self.load_config()
self.scan_plugins() self.scan_plugins()
# 加载全量插件配置
self._load_all_config()
pconf = self.pconf pconf = self.pconf
logger.debug("plugins.json config={}".format(pconf)) logger.debug("plugins.json config={}".format(pconf))
for name, plugin in pconf["plugins"].items(): for name, plugin in pconf["plugins"].items():


+ 2
- 10
plugins/tool/tool.py View File

@@ -10,7 +10,6 @@ from bridge.bridge import Bridge
from bridge.context import ContextType from bridge.context import ContextType
from bridge.reply import Reply, ReplyType from bridge.reply import Reply, ReplyType
from common import const from common import const
from common.log import logger
from config import conf from config import conf
from plugins import * from plugins import *


@@ -119,15 +118,8 @@ class Tool(Plugin):
return return


def _read_json(self) -> dict: def _read_json(self) -> dict:
curdir = os.path.dirname(__file__)
config_path = os.path.join(curdir, "config.json")
tool_config = {"tools": [], "kwargs": {}}
if not os.path.exists(config_path):
return tool_config
else:
with open(config_path, "r") as f:
tool_config = json.load(f)
return tool_config
default_config = {"tools": [], "kwargs": {}}
return super().load_config() or default_config


def _build_tool_kwargs(self, kwargs: dict): def _build_tool_kwargs(self, kwargs: dict):
tool_model_name = kwargs.get("model_name") tool_model_name = kwargs.get("model_name")


Loading…
Cancel
Save