按 chatgpt-on-wechat 插件的思路对 bot-on-anything 进行插件化,期望能实现插件的共享使用,但是由于两个项目的架构存在较大差异,只能尽最大可能兼容 chatgpt-on-wechat 的插件,部分功能还需进行适配。
事件顺序为 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
插件。
在plugins
目录下创建一个插件文件夹selector
。然后,在该文件夹中创建同名selector.py
文件。
plugins/
└── selector
└── selector.py
在selector.py
文件中,创建插件类Selector
,它继承自Plugin
。
在类定义之前需要使用@plugins.register
装饰器注册插件,并填写插件的相关信息,其中desire_priority
表示插件默认的优先级,越大优先级越高。Selector
插件加载时读取了同目录下的selector.json
文件,从中取出对应的模型和触发前缀,Selector
插件为事件ON_HANDLE_CONTEXT
和ON_BRIDGE_HANDLE_STREAM_CONTEXT
绑定了一个处理函数select_model
,它表示在模型桥调用指定模型之前,都会先由select_model
函数预处理。
@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")
事件处理函数接收一个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
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