增加插件功能,新增模型选择插件和生成图片插件

This commit is contained in:
RegimenArsenic
2023-04-07 03:40:27 +08:00
parent 8ee6787af7
commit 58a6c0c741
14 changed files with 520 additions and 58 deletions

3
app.py
View File

@@ -6,6 +6,8 @@ from channel import channel_factory
from common import log, const
from multiprocessing import Pool
from plugins.plugin_manager import PluginManager
# 启动通道
def start_process(channel_type, config_path):
@@ -28,6 +30,7 @@ def main():
model_type = config.conf().get("model").get("type")
channel_type = config.conf().get("channel").get("type")
PluginManager()
# 1.单个字符串格式配置时,直接启动
if not isinstance(channel_type, list):
start_process(channel_type, args.config)

View File

@@ -1,14 +1,33 @@
from model import model_factory
import config
from plugins.event import Event, EventContext
from plugins.plugin_manager import PluginManager
class Bridge(object):
def __init__(self):
pass
def fetch_reply_content(self, query, context):
return model_factory.create_bot(config.conf().get("model").get("type")).reply(query, context)
econtext = PluginManager().emit_event(EventContext(
Event.ON_BRIDGE_HANDLE_CONTEXT, {'context': query, 'args': context}))
type = econtext['args'].get('model') or config.conf().get("model").get("type")
query = econtext.econtext.get("context", None)
reply = econtext.econtext.get("reply", "无回复")
if not econtext.is_pass() and query:
return model_factory.create_bot(type).reply(query, context)
else:
return reply
async def fetch_reply_stream(self, query, context):
bot=model_factory.create_bot(config.conf().get("model").get("type"))
async for final,response in bot.reply_text_stream(query, context):
yield final,response
econtext = PluginManager().emit_event(EventContext(
Event.ON_BRIDGE_HANDLE_CONTEXT, {'context': query, 'args': context}))
type = econtext['args'].get('model') or config.conf().get("model").get("type")
query = econtext.get("context", None)
reply = econtext.get("reply", "无回复")
bot = model_factory.create_bot(type)
if not econtext.is_pass() and query:
async for final, response in bot.reply_text_stream(query, context):
yield final, response
else:
yield True, reply

View File

@@ -12,6 +12,7 @@ from config import channel_conf_val
from channel.channel import Channel
from flask_socketio import SocketIO
from common import log
from plugins.plugin_manager import *
http_app = Flask(__name__,)
socketio = SocketIO(http_app, close_timeout=5)
@@ -26,13 +27,13 @@ http_app.config['SEND_FILE_MAX_AGE_DEFAULT'] = timedelta(seconds=1)
async def return_stream(data):
async for final, response in HttpChannel().handle_stream(data=data):
try:
if(final):
if (final):
socketio.server.emit(
'disconnect', {'result': response, 'final': final}, request.sid, namespace="/chat")
'disconnect', {'result': response, 'final': final}, request.sid, namespace="/chat")
disconnect()
else:
socketio.server.emit(
'message', {'result': response, 'final': final}, request.sid, namespace="/chat")
'message', {'result': response, 'final': final}, request.sid, namespace="/chat")
except Exception as e:
disconnect()
log.warn("[http]emit:{}", e)
@@ -51,7 +52,8 @@ def stream(data):
data["msg"], channel_conf_val(const.HTTP, 'image_create_prefix'))
if img_match_prefix:
reply_text = HttpChannel().handle(data=data)
socketio.emit('disconnect', {'result': reply_text}, namespace='/chat')
socketio.emit(
'disconnect', {'result': reply_text}, namespace='/chat')
disconnect()
return
asyncio.run(return_stream(data))
@@ -66,7 +68,7 @@ def connect():
@socketio.on('disconnect', namespace='/chat')
def disconnect():
log.info('disconnect')
socketio.server.disconnect(request.sid,namespace="/chat")
socketio.server.disconnect(request.sid, namespace="/chat")
@http_app.route("/chat", methods=['POST'])
@@ -114,26 +116,30 @@ class HttpChannel(Channel):
def handle(self, data):
context = dict()
img_match_prefix = functions.check_prefix(
data["msg"], channel_conf_val(const.HTTP, 'image_create_prefix'))
if img_match_prefix:
data["msg"] = data["msg"].split(img_match_prefix, 1)[1].strip()
context['type'] = 'IMAGE_CREATE'
query = data["msg"]
id = data["id"]
context['from_user_id'] = str(id)
reply = super().build_reply_content(data["msg"], context)
if img_match_prefix:
if not isinstance(reply, list):
return reply
images = ""
for url in reply:
images += f"[!['IMAGE_CREATE']({url})]({url})\n"
reply = images
e_context = PluginManager().emit_event(EventContext(Event.ON_HANDLE_CONTEXT, {
'channel': self, 'context': query, "args": context}))
reply = e_context['reply']
if not e_context.is_pass():
reply = super().build_reply_content(e_context["context"], e_context["args"])
e_context = PluginManager().emit_event(EventContext(Event.ON_DECORATE_REPLY, {
'channel': self, 'context': context, 'reply': reply, "args": context}))
reply = e_context['reply']
return reply
async def handle_stream(self, data):
context = dict()
id = data["id"]
context['from_user_id'] = str(id)
async for final, reply in super().build_reply_stream(data["msg"], context):
yield final, reply
context['stream'] = True
context['origin'] = data["msg"]
e_context = PluginManager().emit_event(EventContext(Event.ON_HANDLE_CONTEXT, {
'channel': self, 'context': data["msg"], 'reply': data["msg"], "args": context}))
reply = e_context['reply']
if not e_context.is_pass():
async for final, reply in super().build_reply_stream(data["msg"], context):
yield final, reply
else:
yield True, reply

View File

@@ -14,7 +14,7 @@ from common.log import logger
from common import const
from config import channel_conf_val
import requests
from plugins.plugin_manager import *
from common.sensitive_word import SensitiveWord
import io
@@ -81,25 +81,14 @@ class WechatChannel(Channel):
str_list = content.split(match_prefix, 1)
if len(str_list) == 2:
content = str_list[1].strip()
img_match_prefix = self.check_prefix(content, channel_conf_val(const.WECHAT, 'image_create_prefix'))
if img_match_prefix:
content = content.split(img_match_prefix, 1)[1].strip()
thread_pool.submit(self._do_send_img, content, from_user_id)
else:
thread_pool.submit(self._do_send, content, from_user_id)
thread_pool.submit(self._do_send, content, from_user_id)
elif to_user_id == other_user_id and match_prefix:
# 自己给好友发送消息
str_list = content.split(match_prefix, 1)
if len(str_list) == 2:
content = str_list[1].strip()
img_match_prefix = self.check_prefix(content, channel_conf_val(const.WECHAT, 'image_create_prefix'))
if img_match_prefix:
content = content.split(img_match_prefix, 1)[1].strip()
thread_pool.submit(self._do_send_img, content, to_user_id)
else:
thread_pool.submit(self._do_send, content, to_user_id)
thread_pool.submit(self._do_send, content, to_user_id)
def handle_group(self, msg):
@@ -137,13 +126,7 @@ class WechatChannel(Channel):
group_white_list = channel_conf_val(const.WECHAT, 'group_name_white_list')
if ('ALL_GROUP' in group_white_list or group_name in group_white_list or self.check_contain(group_name, channel_conf_val(const.WECHAT, 'group_name_keyword_white_list'))) and match_prefix:
img_match_prefix = self.check_prefix(content, channel_conf_val(const.WECHAT, 'image_create_prefix'))
if img_match_prefix:
content = content.split(img_match_prefix, 1)[1].strip()
thread_pool.submit(self._do_send_img, content, group_id)
else:
thread_pool.submit(self._do_send_group, content, msg)
thread_pool.submit(self._do_send_group, content, msg)
return None
def send(self, msg, receiver):
@@ -156,18 +139,25 @@ class WechatChannel(Channel):
return
context = dict()
context['from_user_id'] = reply_user_id
reply_text = super().build_reply_content(query, context)
if reply_text:
self.send(channel_conf_val(const.WECHAT, "single_chat_reply_prefix") + reply_text, reply_user_id)
e_context = PluginManager().emit_event(EventContext(Event.ON_HANDLE_CONTEXT, {
'channel': self, 'context': query, "args": context}))
reply = e_context['reply']
if not e_context.is_pass():
reply = super().build_reply_content(e_context["context"], e_context["args"])
e_context = PluginManager().emit_event(EventContext(Event.ON_DECORATE_REPLY, {
'channel': self, 'context': context, 'reply': reply, "args": e_context["args"]}))
reply = e_context['reply']
if reply:
self.send(channel_conf_val(const.WECHAT, "single_chat_reply_prefix") + reply, reply_user_id)
except Exception as e:
logger.exception(e)
def _do_send_img(self, query, reply_user_id):
def _do_send_img(self, query, context):
try:
if not query:
return
context = dict()
context['type'] = 'IMAGE_CREATE'
reply_user_id=context['from_user_id']
img_urls = super().build_reply_content(query, context)
if not img_urls:
return
@@ -192,12 +182,19 @@ class WechatChannel(Channel):
if not query:
return
context = dict()
context['from_user_id'] = msg['ActualUserName']
reply_text = super().build_reply_content(query, context)
if reply_text:
reply_text = '@' + msg['ActualNickName'] + ' ' + reply_text.strip()
self.send(channel_conf_val(const.WECHAT, "group_chat_reply_prefix", "") + reply_text, msg['User']['UserName'])
context['from_user_id'] = msg['User']['UserName']
e_context = PluginManager().emit_event(EventContext(Event.ON_HANDLE_CONTEXT, {
'channel': self, 'context': query, "args": context}))
reply = e_context['reply']
if not e_context.is_pass():
context['from_user_id'] = msg['ActualUserName']
reply = super().build_reply_content(e_context["context"], e_context["args"])
e_context = PluginManager().emit_event(EventContext(Event.ON_DECORATE_REPLY, {
'channel': self, 'context': context, 'reply': reply, "args": e_context["args"]}))
reply = e_context['reply']
if reply:
reply = '@' + msg['ActualNickName'] + ' ' + reply.strip()
self.send(channel_conf_val(const.WECHAT, "group_chat_reply_prefix", "") + reply, msg['User']['UserName'])
def check_prefix(self, content, prefix_list):
for prefix in prefix_list:

View File

@@ -1,4 +1,31 @@
import json
import os
import re
from common import log
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
def load_json_file(curdir: str, file: str = 'config.json'):
config_path = os.path.join(curdir, file)
try:
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
return config
except Exception as e:
if isinstance(e, FileNotFoundError):
log.warn(
f"[common]load json file failed, {config_path}\{file} not found")
else:
log.warn("[common]load json file failed")
raise e
def contain_chinese(str):
@@ -11,7 +38,9 @@ def contain_chinese(str):
def check_prefix(content, prefix_list):
if(len(prefix_list)==0):
return True
for prefix in prefix_list:
if content.startswith(prefix):
return prefix
return None
return False

122
plugins/README.md Normal file
View File

@@ -0,0 +1,122 @@
# 简介
**[chatgpt-on-wechat](https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins)** 插件的思路对 **bot-on-anything** 进行插件化,期望能实现插件的共享使用,但是由于两个项目的架构存在较大差异,只能尽最大可能兼容 **[chatgpt-on-wechat](https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins)** 的插件,部分功能还需进行适配。
## **插件监听的事件:**
事件顺序为 1、ON_HANDLE_CONTEXT --> 2、ON_BRIDGE_HANDLE_CONTEXT(ON_BRIDGE_HANDLE_STREAM_CONTEXT) --> 3、ON_DECORATE_REPLY
触发事件会产生事件的上下文EventContext它可能包含了以下信息:
EventContext(Event事件类型, {'channel' : 本次消息的context, 'context': 本次消息用户的提问, 'reply': 当前AI回复, "args": 其他上下文参数})
插件处理函数可通过修改EventContext中的context、reply、args或者调用channel中对应的方法来实现功能。
```
class Event(Enum):
ON_HANDLE_CONTEXT = 2 # 对应通道处理消息前
"""
e_context = { "channel": 消息channel, "context" : 本次消息的context, "reply" : 目前的回复,初始为空 , "args": 其他上下文参数 }
"""
ON_DECORATE_REPLY = 3 # 得到回复后准备装饰
"""
e_context = { "channel": 消息channel, "context" : 本次消息的context, "reply" : 目前的回复 , "args": 其他上下文参数 }
"""
ON_SEND_REPLY = 4 # 发送回复前
"""
bot-on-anything 不支持ON_SEND_REPLY事件,请使用ON_BRIDGE_HANDLE_CONTEXT或者ON_BRIDGE_HANDLE_STREAM_CONTEXT事件
"""
ON_BRIDGE_HANDLE_CONTEXT = 6 # 模型桥处理消息前
"""
e_context = { "context" : 本次消息的context, "reply" : 目前的回复,初始为空 , "args": 其他上下文参数 , 模型桥会调用args.model指定的AI模型来进行回复 }
"""
ON_BRIDGE_HANDLE_STREAM_CONTEXT = 7 # 模型桥处理流式消息前,流式对话的消息处理仅支持一次性返回,请直接返回结果
"""
e_context = { "context" : 本次消息的context, "reply" : 目前的回复,初始为空 , "args": 其他上下文参数 , 模型桥会调用args.model指定的AI模型来进行回复 }
"""
```
## 插件编写示例
`plugins/selector`为例,其中编写了一个通过判断前缀调用不同模型的`Selector`插件。
### 1. 创建插件
`plugins`目录下创建一个插件文件夹`selector`。然后,在该文件夹中创建同名``selector.py``文件。
```
plugins/
└── selector
└── selector.py
```
### 2. 编写功能
在`selector.py`文件中,创建插件类`Selector`,它继承自`Plugin`。
在类定义之前需要使用`@plugins.register`装饰器注册插件,并填写插件的相关信息,其中`desire_priority`表示插件默认的优先级,越大优先级越高。`Selector`插件加载时读取了同目录下的`selector.json`文件,从中取出对应的模型和触发前缀,`Selector`插件为事件`ON_HANDLE_CONTEXT`和`ON_BRIDGE_HANDLE_STREAM_CONTEXT`绑定了一个处理函数`select_model`,它表示在模型桥调用指定模型之前,都会先由`select_model`函数预处理。
```python
@plugins.register(name="Selector", desire_priority=99, hidden=True, desc="A model selector", version="0.1", author="RegimenArsenic")
class Selector(Plugin):
def __init__(self):
super().__init__()
curdir = os.path.dirname(__file__)
try:
self.config = functions.load_json_file(curdir, "selector.json")
except Exception as e:
log.warn("[Selector] init failed")
raise e
self.handlers[Event.ON_HANDLE_CONTEXT] = self.select_model
self.handlers[Event.ON_BRIDGE_HANDLE_STREAM_CONTEXT] = self.select_model
log.info("[Selector] inited")
```
### 3. 编写事件处理函数
#### 修改事件上下文
事件处理函数接收一个`EventContext`对象`e_context`作为参数。`e_context`包含了事件相关信息,利用`e_context['key']`来访问这些信息。
`EventContext(Event事件类型, {'channel' : 消息channel, 'context': Context, 'reply': Reply , "args": 其他上下文参数})`
处理函数中通过修改`e_context`对象中的事件相关信息来实现所需功能,比如更改`e_context['reply']`中的内容可以修改回复,更改`e_context['context']`中的内容可以修改用户提问。
#### 决定是否交付给下个插件或默认逻辑
在处理函数结束时,还需要设置`e_context`对象的`action`属性,它决定如何继续处理事件。目前有以下三种处理方式:
- `EventAction.CONTINUE`: 事件未结束,继续交给下个插件处理,如果没有下个插件,则交付给默认的事件处理逻辑。
- `EventAction.BREAK`: 事件结束,不再给下个插件处理,交付给默认的处理逻辑。
- `EventAction.BREAK_PASS`: 事件结束,不再给下个插件处理,跳过默认的处理逻辑。
#### 示例处理函数
`Selector`插件通过判断前缀,如有`@bing`前缀,则修改调用模型为bing模型若前缀为`@gpt`则修改调用模型为chatgpt否则就使用app里配置的原始模型插件同时删去提问的前缀`@bing`或者`@gpt`
```python
def select_model(self, e_context: EventContext):
model=e_context['args'].get('model')
for selector in self.config.get("selector", []):
prefix = selector.get('prefix', [])
check_prefix=functions.check_prefix(e_context["context"], prefix)
if (check_prefix):
model=selector.get('model')
if isinstance(check_prefix, str):
e_context["context"] = e_context["context"].split(check_prefix, 1)[1].strip()
break
log.debug(f"[Selector] select model {model}")
e_context.action = EventAction.CONTINUE # 事件继续,交付给下个插件或默认逻辑
e_context['args']['model']=model
return e_context
```

10
plugins/__init__.py Normal file
View File

@@ -0,0 +1,10 @@
# encoding:utf-8
from .event import *
from .plugin import *
from plugins.plugin_registry import PluginRegistry
instance = PluginRegistry()
register = instance.register
# load_plugins = instance.load_plugins
# emit_event = instance.emit_event

View File

@@ -0,0 +1,66 @@
# encoding:utf-8
from channel.http.http_channel import HttpChannel
from channel.wechat.wechat_channel import WechatChannel
import plugins
from plugins import *
from common import functions
from config import channel_conf
from config import channel_conf_val
from common import const
@plugins.register(name="CreateImg", desire_priority=90, hidden=True, desc="A simple plugin that create images from model", version="0.1", author="RegimenArseic")
class Createimg(Plugin):
def __init__(self):
super().__init__()
self.handles = {HttpChannel: self.handle_http}
self.channel_types = {HttpChannel: const.HTTP,
WechatChannel: const.WECHAT}
self.handlers[Event.ON_HANDLE_CONTEXT] = self.handle_query
self.handlers[Event.ON_DECORATE_REPLY] = self.send_images
def get_events(self):
return self.handlers
def handle_query(self, e_context: EventContext):
channel = e_context['channel']
channel_type = self.channel_types.get(type(channel), None)
if (channel_type):
query = e_context['context']
if (query):
img_match_prefix = functions.check_prefix(
query, channel_conf_val(channel_type, 'image_create_prefix'))
if img_match_prefix:
if (channel_type == const.HTTP) and e_context['args'].get('stream', False):
e_context['reply'] = channel.handle(
{'msg': e_context['args']['origin'], 'id': e_context['args']['from_user_id']})
e_context.action = EventAction.BREAK_PASS
else:
query = query.split(img_match_prefix, 1)[1].strip()
e_context['args']['type'] = 'IMAGE_CREATE'
if (channel_type == const.WECHAT):
channel._do_send_img(
query, e_context['args'])
e_context.action = EventAction.BREAK_PASS
else:
e_context.action = EventAction.CONTINUE
return e_context
def handle_http(self, e_context: EventContext):
reply = e_context["reply"]
if e_context['args'].get('type', '') == 'IMAGE_CREATE':
if isinstance(reply, list):
images = ""
for url in reply:
images += f"[!['IMAGE_CREATE']({url})]({url})\n\n"
e_context["reply"] = images
return e_context
def send_images(self, e_context: EventContext):
channel = e_context['channel']
method = self.handles.get(type(channel), None)
if (method):
e_context = method(e_context)
e_context.action = EventAction.BREAK_PASS # 事件结束,不再给下个插件处理,不交付给默认的事件处理逻辑
return e_context

59
plugins/event.py Normal file
View File

@@ -0,0 +1,59 @@
# encoding:utf-8
from enum import Enum
class Event(Enum):
# ON_RECEIVE_MESSAGE = 1 # 收到消息
ON_HANDLE_CONTEXT = 2 # 对应通道处理消息前
"""
e_context = { "channel": 消息channel, "context" : 本次消息的context, "reply" : 目前的回复,初始为空 , "args": 其他上下文参数 }
"""
ON_DECORATE_REPLY = 3 # 得到回复后准备装饰
"""
e_context = { "channel": 消息channel, "context" : 本次消息的context, "reply" : 目前的回复 , "args": 其他上下文参数 }
"""
ON_SEND_REPLY = 4 # 发送回复前
"""
bot-on-anything 不支持ON_SEND_REPLY事件,请使用ON_BRIDGE_HANDLE_CONTEXT或者ON_BRIDGE_HANDLE_STREAM_CONTEXT事件
"""
# AFTER_SEND_REPLY = 5 # 发送回复后
ON_BRIDGE_HANDLE_CONTEXT = 6 # 模型桥处理消息前
"""
e_context = { "context" : 本次消息的context, "reply" : 目前的回复,初始为空 , "args": 其他上下文参数 }
"""
ON_BRIDGE_HANDLE_STREAM_CONTEXT = 7 # 模型桥处理流式消息前,流式对话的消息处理仅支持一次性返回,请直接返回结果
"""
e_context = { "context" : 本次消息的context, "reply" : 目前的回复,初始为空 , "args": 其他上下文参数 }
"""
class EventAction(Enum):
CONTINUE = 1 # 事件未结束,继续交给下个插件处理,如果没有下个插件,则交付给默认的事件处理逻辑
BREAK = 2 # 事件结束,不再给下个插件处理,交付给默认的事件处理逻辑
BREAK_PASS = 3 # 事件结束,不再给下个插件处理,不交付给默认的事件处理逻辑
class EventContext:
def __init__(self, event, econtext=dict()):
self.event = event
self.econtext = econtext
self.action = EventAction.CONTINUE
def __getitem__(self, key):
return self.econtext.get(key,"")
def __setitem__(self, key, value):
self.econtext[key] = value
def __delitem__(self, key):
del self.econtext[key]
def is_pass(self):
return self.action == EventAction.BREAK_PASS

7
plugins/plugin.py Normal file
View File

@@ -0,0 +1,7 @@
# encoding:utf-8
class Plugin:
def __init__(self):
self.handlers = {}
def get_help_text(self, **kwargs):
return "暂无帮助信息"

41
plugins/plugin_manager.py Normal file
View File

@@ -0,0 +1,41 @@
# encoding:utf-8
import os
import importlib.util
from plugins.event import EventAction, EventContext,Event
from plugins.plugin_registry import PluginRegistry
from common import functions
@functions.singleton
class PluginManager:
def __init__(self, plugins_dir="./plugins/"):
self.plugins_dir = plugins_dir
self.plugin_registry = PluginRegistry()
self.load_plugins()
def load_plugins(self):
for plugin_name in self.find_plugin_names():
if os.path.exists(f"./plugins/{plugin_name}/{plugin_name}.py"):
plugin_module = self.load_plugin_module(plugin_name)
self.plugin_registry.register_from_module(plugin_module)
def find_plugin_names(self):
plugin_names = []
for entry in os.scandir(self.plugins_dir):
if entry.is_dir():
plugin_names.append(entry.name)
return plugin_names
def load_plugin_module(self, plugin_name):
spec = importlib.util.spec_from_file_location(
plugin_name, os.path.join(self.plugins_dir, plugin_name, f"{plugin_name}.py")
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def emit_event(self, e_context: EventContext, *args, **kwargs):
for plugin in self.plugin_registry.list_plugins():
if plugin.enabled and e_context.action == EventAction.CONTINUE:
if(e_context.event in plugin.handlers):
plugin.handlers[e_context.event](e_context, *args, **kwargs)
return e_context

View File

@@ -0,0 +1,51 @@
# encoding:utf-8
import inspect
from plugins.plugin import Plugin
from common.log import logger
from common import functions
@functions.singleton
class PluginRegistry:
def __init__(self):
self.plugins = []
def register(self, name: str, desire_priority: int = 0, **kwargs):
def wrapper(plugin_cls):
plugin_cls.name = name
plugin_cls.priority = desire_priority
plugin_cls.desc = kwargs.get('desc')
plugin_cls.author = kwargs.get('author')
plugin_cls.version = kwargs.get('version') or "1.0"
plugin_cls.namecn = kwargs.get('namecn') or name
plugin_cls.hidden = kwargs.get('hidden') or False
plugin_cls.enabled = kwargs.get('enabled') or True
logger.info(f"Plugin {name}_v{plugin_cls.version} registered")
return plugin_cls
return wrapper
def register_from_module(self, module):
plugins = []
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, Plugin) and obj != Plugin:
plugin_name = getattr(obj, "name", None)
if plugin_name:
plugin = obj()
plugin.name = plugin_name
plugin.priority = getattr(obj, "priority", 0)
plugin.desc = getattr(obj, "desc", None)
plugin.author = getattr(obj, "author", None)
plugin.version = getattr(obj, "version", "1.0")
plugin.namecn = getattr(obj, "namecn", plugin_name)
plugin.hidden = getattr(obj, "hidden", False)
plugin.enabled = getattr(obj, "enabled", True)
# Sort the list of plugins by priority
self.plugins.append(plugin)
self.plugins.sort(key=lambda x: x.priority, reverse=True)
def get_plugin(self, name):
plugin = next((p for p in self.plugins if p.name.upper() == name.upper()), None)
return plugin
def list_plugins(self):
return [plugin for plugin in self.plugins]

View File

@@ -0,0 +1,12 @@
{
"selector":[
{
"model":"bing",
"prefix":["@bing"]
},
{
"model":"chatgpt",
"prefix":["@gpt"]
}
]
}

View File

@@ -0,0 +1,40 @@
# encoding:utf-8
import os
import plugins
from plugins import *
from common import log
from common import functions
@plugins.register(name="Selector", desire_priority=99, hidden=True, desc="A model selector", version="0.1", author="RegimenArsenic")
class Selector(Plugin):
def __init__(self):
super().__init__()
curdir = os.path.dirname(__file__)
try:
self.config = functions.load_json_file(curdir, "selector.json")
except Exception as e:
log.warn("[Selector] init failed")
raise e
self.handlers[Event.ON_HANDLE_CONTEXT] = self.select_model
self.handlers[Event.ON_BRIDGE_HANDLE_STREAM_CONTEXT] = self.select_model
log.info("[Selector] inited")
def get_events(self):
return self.handlers
def select_model(self, e_context: EventContext):
model=e_context['args'].get('model')
for selector in self.config.get("selector", []):
prefix = selector.get('prefix', [])
check_prefix=functions.check_prefix(e_context["context"], prefix)
if (check_prefix):
model=selector.get('model')
if isinstance(check_prefix, str):
e_context["context"] = e_context["context"].split(check_prefix, 1)[1].strip()
break
log.debug(f"[Selector] select model {model}")
e_context.action = EventAction.CONTINUE # 事件继续,交付给下个插件或默认逻辑
e_context['args']['model']=model
return e_context