diff --git a/docs/plugins/action-components.md b/docs/plugins/action-components.md index 56608454c..d23df823c 100644 --- a/docs/plugins/action-components.md +++ b/docs/plugins/action-components.md @@ -68,15 +68,15 @@ class ExampleAction(BaseAction): Action采用**两层决策机制**来优化性能和决策质量: -> 设计目的:在加载许多插件的时候降低LLM决策压力,避免让麦麦在过多的选项中纠结。 +> 设计目的:在加载许多插件的时候降低LLM决策压力,避免让MoFox-Bot在过多的选项中纠结。 **第一层:激活控制(Activation Control)** -激活决定麦麦是否 **“知道”** 这个Action的存在,即这个Action是否进入决策候选池。不被激活的Action麦麦永远不会选择。 +激活决定MoFox-Bot是否 **“知道”** 这个Action的存在,即这个Action是否进入决策候选池。不被激活的ActionMoFox-Bot永远不会选择。 **第二层:使用决策(Usage Decision)** -在Action被激活后,使用条件决定麦麦什么时候会 **“选择”** 使用这个Action。 +在Action被激活后,使用条件决定MoFox-Bot什么时候会 **“选择”** 使用这个Action。 ### 决策参数详解 🔧 @@ -84,8 +84,8 @@ Action采用**两层决策机制**来优化性能和决策质量: | 激活类型 | 说明 | 使用场景 | | ----------- | ---------------------------------------- | ---------------------- | -| [`NEVER`](#never-激活) | 从不激活,Action对麦麦不可见 | 临时禁用某个Action | -| [`ALWAYS`](#always-激活) | 永远激活,Action总是在麦麦的候选池中 | 核心功能,如回复、不回复 | +| [`NEVER`](#never-激活) | 从不激活,Action对MoFox-Bot不可见 | 临时禁用某个Action | +| [`ALWAYS`](#always-激活) | 永远激活,Action总是在MoFox-Bot的候选池中 | 核心功能,如回复、不回复 | | [`LLM_JUDGE`](#llm_judge-激活) | 通过LLM智能判断当前情境是否需要激活此Action | 需要智能判断的复杂场景 | | `RANDOM` | 基于随机概率决定是否激活 | 增加行为随机性的功能 | | `KEYWORD` | 当检测到特定关键词时激活 | 明确触发条件的功能 | @@ -184,13 +184,13 @@ class GreetingAction(BaseAction): #### 第二层:使用决策 -**在Action被激活后,使用条件决定麦麦什么时候会"选择"使用这个Action**。 +**在Action被激活后,使用条件决定MoFox-Bot什么时候会"选择"使用这个Action**。 这一层由以下因素综合决定: - `action_require`:使用场景描述,帮助LLM判断何时选择 - `action_parameters`:所需参数,影响Action的可执行性 -- 当前聊天上下文和麦麦的决策逻辑 +- 当前聊天上下文和MoFox-Bot的决策逻辑 --- @@ -214,11 +214,11 @@ class EmojiAction(BaseAction): 1. **第一层激活判断**: - - 使用随机数进行决策,当`random.random() < self.random_activation_probability`时,麦麦才"知道"可以使用这个Action + - 使用随机数进行决策,当`random.random() < self.random_activation_probability`时,MoFox-Bot才"知道"可以使用这个Action 2. **第二层使用决策**: - - 即使Action被激活,麦麦还会根据 `action_require` 中的条件判断是否真正选择使用 - - 例如:如果刚刚已经发过表情,根据"不要连续发送多个表情"的要求,麦麦可能不会选择这个Action + - 即使Action被激活,MoFox-Bot还会根据 `action_require` 中的条件判断是否真正选择使用 + - 例如:如果刚刚已经发过表情,根据"不要连续发送多个表情"的要求,MoFox-Bot可能不会选择这个Action --- diff --git a/src/chat/chat_loop/cycle_processor.py b/src/chat/chat_loop/cycle_processor.py index 781d9dde4..4a946bb72 100644 --- a/src/chat/chat_loop/cycle_processor.py +++ b/src/chat/chat_loop/cycle_processor.py @@ -100,7 +100,7 @@ class CycleProcessor: from src.plugin_system.core.event_manager import event_manager from src.plugin_system.base.component_types import EventType # 触发 ON_PLAN 事件 - result = await event_manager.trigger_event(EventType.ON_PLAN, stream_id=self.context.stream_id) + result = await event_manager.trigger_event(EventType.ON_PLAN, plugin_name="SYSTEM", stream_id=self.context.stream_id) if result and not result.all_continue_process(): return diff --git a/src/chat/message_receive/bot.py b/src/chat/message_receive/bot.py index 760d69062..9a29998d8 100644 --- a/src/chat/message_receive/bot.py +++ b/src/chat/message_receive/bot.py @@ -437,7 +437,7 @@ class ChatBot: logger.info(f"命令处理完成,跳过后续消息处理: {cmd_result}") return - result = await event_manager.trigger_event(EventType.ON_MESSAGE,message=message) + result = await event_manager.trigger_event(EventType.ON_MESSAGE,plugin_name="SYSTEM",message=message) if not result.all_continue_process(): raise UserWarning(f"插件{result.get_summary().get('stopped_handlers','')}于消息到达时取消了消息处理") diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 9c9dc334f..77033472d 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -370,7 +370,7 @@ class DefaultReplyer: from src.plugin_system.core.event_manager import event_manager if not from_plugin: - result = await event_manager.trigger_event(EventType.POST_LLM,prompt=prompt,stream_id=stream_id) + result = await event_manager.trigger_event(EventType.POST_LLM,plugin_name="SYSTEM",prompt=prompt,stream_id=stream_id) if not result.all_continue_process(): raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于请求前中断了内容生成") @@ -390,7 +390,7 @@ class DefaultReplyer: } # 触发 AFTER_LLM 事件 if not from_plugin: - result = await event_manager.trigger_event(EventType.AFTER_LLM,prompt=prompt,llm_response=llm_response,stream_id=stream_id) + result = await event_manager.trigger_event(EventType.AFTER_LLM,plugin_name="SYSTEM",prompt=prompt,llm_response=llm_response,stream_id=stream_id) if not result.all_continue_process(): raise UserWarning(f"插件{result.get_summary().get('stopped_handlers','')}于请求后取消了内容生成") except UserWarning as e: diff --git a/src/main.py b/src/main.py index 7ac8cb76d..6a5f989b0 100644 --- a/src/main.py +++ b/src/main.py @@ -254,7 +254,7 @@ MoFox_Bot(第三方修改版) try: - await event_manager.trigger_event(EventType.ON_START) + await event_manager.trigger_event(EventType.ON_START,plugin_name="SYSTEM") init_time = int(1000 * (time.time() - init_start_time)) logger.info(f"初始化完成,神经元放电{init_time}次") except Exception as e: diff --git a/src/plugin_system/base/base_event.py b/src/plugin_system/base/base_event.py index d010d33dc..c527752d5 100644 --- a/src/plugin_system/base/base_event.py +++ b/src/plugin_system/base/base_event.py @@ -1,19 +1,20 @@ +import asyncio from typing import List, Dict, Any, Optional from src.common.logger import get_logger logger = get_logger("base_event") - + class HandlerResult: """事件处理器执行结果 所有事件处理器必须返回此类的实例 """ - def __init__(self, success: bool, continue_process: bool, message: str = "", handler_name: str = ""): + def __init__(self, success: bool, continue_process: bool, message: Any = {}, handler_name: str = ""): self.success = success self.continue_process = continue_process self.message = message self.handler_name = handler_name - + def __repr__(self): return f"HandlerResult(success={self.success}, continue_process={self.continue_process}, message='{self.message}', handler_name='{self.handler_name}')" @@ -66,13 +67,22 @@ class HandlerResultsCollection: } class BaseEvent: - def __init__(self, name: str): + def __init__( + self, + name: str, + allowed_subscribers: List[str]=[], + allowed_triggers: List[str]=[] + ): self.name = name self.enabled = True + self.allowed_subscribers = allowed_subscribers # 记录事件处理器名 + self.allowed_triggers = allowed_triggers # 记录插件名 from src.plugin_system.base.base_events_handler import BaseEventHandler self.subscribers: List["BaseEventHandler"] = [] # 订阅该事件的事件处理器列表 + self.event_handle_lock = asyncio.Lock() + def __name__(self): return self.name @@ -88,22 +98,45 @@ class BaseEvent: if not self.enabled: return HandlerResultsCollection([]) - # 按权重从高到低排序订阅者 - # 使用直接属性访问,-1代表自动权重 - sorted_subscribers = sorted(self.subscribers, key=lambda h: h.weight if hasattr(h, 'weight') and h.weight != -1 else 0, reverse=True) - - results = [] - for subscriber in sorted_subscribers: - try: - result = await subscriber.execute(params) - if not result.handler_name: - # 补充handler_name - result.handler_name = subscriber.handler_name if hasattr(subscriber, 'handler_name') else subscriber.__class__.__name__ - results.append(result) - except Exception as e: - # 处理执行异常 + # 使用锁确保同一个事件不能同时激活多次 + async with self.event_handle_lock: + # 按权重从高到低排序订阅者 + # 使用直接属性访问,-1代表自动权重 + sorted_subscribers = sorted(self.subscribers, key=lambda h: h.weight if hasattr(h, 'weight') and h.weight != -1 else 0, reverse=True) + + # 并行执行所有订阅者 + tasks = [] + for subscriber in sorted_subscribers: + # 为每个订阅者创建执行任务 + task = self._execute_subscriber(subscriber, params) + tasks.append(task) + + # 等待所有任务完成 + results = await asyncio.gather(*tasks, return_exceptions=True) + + # 处理执行结果 + processed_results = [] + for i, result in enumerate(results): + subscriber = sorted_subscribers[i] handler_name = subscriber.handler_name if hasattr(subscriber, 'handler_name') else subscriber.__class__.__name__ - logger.error(f"事件处理器 {handler_name} 执行失败: {e}") - results.append(HandlerResult(False, True, str(e), handler_name)) - - return HandlerResultsCollection(results) \ No newline at end of file + + if isinstance(result, Exception): + # 处理执行异常 + logger.error(f"事件处理器 {handler_name} 执行失败: {result}") + processed_results.append(HandlerResult(False, True, str(result), handler_name)) + else: + # 正常执行结果 + if not result.handler_name: + # 补充handler_name + result.handler_name = handler_name + processed_results.append(result) + + return HandlerResultsCollection(processed_results) + + async def _execute_subscriber(self, subscriber, params: dict) -> HandlerResult: + """执行单个订阅者处理器""" + try: + return await subscriber.execute(params) + except Exception as e: + # 异常会在 gather 中捕获,这里直接抛出让 gather 处理 + raise e \ No newline at end of file diff --git a/src/plugin_system/core/event_manager.py b/src/plugin_system/core/event_manager.py index ea3d04a70..38d5775da 100644 --- a/src/plugin_system/core/event_manager.py +++ b/src/plugin_system/core/event_manager.py @@ -2,7 +2,6 @@ 事件管理器 - 实现Event和EventHandler的单例管理 提供统一的事件注册、管理和触发接口 """ - from typing import Dict, Type, List, Optional, Any, Union from threading import Lock @@ -41,12 +40,18 @@ class EventManager: self._initialized = True logger.info("EventManager 单例初始化完成") - def register_event(self, event_name: Union[EventType, str]) -> bool: + def register_event( + self, + event_name: Union[EventType, str], + allowed_subscribers: List[str]=[], + allowed_triggers: List[str]=[] + ) -> bool: """注册一个新的事件 Args: event_name Union[EventType, str]: 事件名称 - + allowed_subscribers: List[str]: 事件订阅者白名单, + allowed_triggers: List[str]: 事件触发插件白名单 Returns: bool: 注册成功返回True,已存在返回False """ @@ -54,7 +59,7 @@ class EventManager: logger.warning(f"事件 {event_name} 已存在,跳过注册") return False - event = BaseEvent(event_name) + event = BaseEvent(event_name,allowed_subscribers,allowed_triggers) self._events[event_name] = event logger.info(f"事件 {event_name} 注册成功") @@ -211,7 +216,12 @@ class EventManager: if handler_instance in event.subscribers: logger.warning(f"事件处理器 {handler_name} 已经订阅了事件 {event_name},跳过重复订阅") return True - + + # 白名单检查 + if event.allowed_subscribers and handler_name not in event.allowed_subscribers: + logger.warning(f"事件处理器 {handler_name} 不在事件 {event_name} 的订阅者白名单中,无法订阅") + return False + event.subscribers.append(handler_instance) # 按权重从高到低排序订阅者 @@ -265,11 +275,12 @@ class EventManager: return {handler.handler_name: handler for handler in event.subscribers} - async def trigger_event(self, event_name: Union[EventType, str], **kwargs) -> Optional[HandlerResultsCollection]: + async def trigger_event(self, event_name: Union[EventType, str], plugin_name: Optional[str]="", **kwargs) -> Optional[HandlerResultsCollection]: """触发指定事件 Args: event_name Union[EventType, str]: 事件名称 + plugin_name str: 触发事件的插件名 **kwargs: 传递给处理器的参数 Returns: @@ -281,7 +292,15 @@ class EventManager: if event is None: logger.error(f"事件 {event_name} 不存在,无法触发") return None - + + # 插件白名单检查 + if event.allowed_triggers and not plugin_name: + logger.warning(f"事件 {event_name} 存在触发者白名单,缺少plugin_name无法验证权限,已拒绝触发!") + return None + elif event.allowed_triggers and plugin_name not in event.allowed_triggers: + logger.warning(f"插件 {plugin_name} 没有权限触发事件 {event_name},已拒绝触发!") + return None + return await event.activate(params) def init_default_events(self) -> None: @@ -294,12 +313,11 @@ class EventManager: EventType.POST_LLM, EventType.AFTER_LLM, EventType.POST_SEND, - EventType.AFTER_SEND, - EventType.UNKNOWN + EventType.AFTER_SEND ] for event_name in default_events: - self.register_event(event_name) + self.register_event(event_name,allowed_triggers=["SYSTEM"]) logger.info("默认事件初始化完成") diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index ebec87401..d3b997526 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,7 +1,7 @@ [inner] version = "6.5.7" -#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- +#----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读---- #如果你想要修改配置文件,请递增version的值 #如果新增项目,请阅读src/config/official_configs.py中的说明 # @@ -9,7 +9,7 @@ version = "6.5.7" # 主版本号:MMC版本更新 # 次版本号:配置文件内容大更新 # 修订号:配置文件内容小更新 -#----以上是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- +#----以上是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读---- [database]# 数据库配置 database_type = "sqlite" # 数据库类型,支持 "sqlite" 或 "mysql" @@ -48,9 +48,9 @@ master_users = []# ["qq", "123456789"], # 示例:QQ平台的Master用户 [bot] platform = "qq" -qq_account = 1145141919810 # 麦麦的QQ账号 -nickname = "麦麦" # 麦麦的昵称 -alias_names = ["麦叠", "牢麦"] # 麦麦的别名 +qq_account = 1145141919810 # MoFox-Bot的QQ账号 +nickname = "MoFox-Bot" # MoFox-Bot的昵称 +alias_names = ["麦叠", "牢麦"] # MoFox-Bot的别名 [command] command_prefixes = ['/', '!', '.', '#'] @@ -64,7 +64,7 @@ personality_side = "用一句话或几句话描述人格的侧面特质" # 可以描述外貌,性别,身高,职业,属性等等描述 identity = "年龄为19岁,是女孩子,身高为160cm,有黑色的短发" -# 描述麦麦说话的表达风格,表达习惯,如要修改,可以酌情新增内容 +# 描述MoFox-Bot说话的表达风格,表达习惯,如要修改,可以酌情新增内容 reply_style = "回复可以简短一些。可以参考贴吧,知乎和微博的回复风格,回复不要浮夸,不要用夸张修辞,平淡一些。" #回复的Prompt模式选择:s4u为原有s4u样式,normal为0.9之前的模式 @@ -109,12 +109,12 @@ learning_strength = 1.0 -[chat] #麦麦的聊天通用设置 +[chat] #MoFox-Bot的聊天通用设置 focus_value = 1 -# 麦麦的专注思考能力,越高越容易专注,可能消耗更多token +# MoFox-Bot的专注思考能力,越高越容易专注,可能消耗更多token # 专注时能更好把握发言时机,能够进行持久的连续对话 -talk_frequency = 1 # 麦麦活跃度,越高,麦麦回复越频繁 +talk_frequency = 1 # MoFox-Bot活跃度,越高,MoFox-Bot回复越频繁 # 强制私聊专注模式 force_focus_private = false # 是否强制私聊进入专注模式,开启后私聊将始终保持专注状态 @@ -124,7 +124,7 @@ group_chat_mode = "auto" # 群聊聊天模式:auto-自动切换,normal-强 max_context_size = 25 # 上下文长度 -thinking_timeout = 40 # 麦麦一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢) +thinking_timeout = 40 # MoFox-Bot一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢) replyer_random_probability = 0.5 # 首要replyer模型被选择的概率 mentioned_bot_inevitable_reply = true # 提及 bot 必然回复 @@ -181,7 +181,7 @@ delta_sigma = 120 # 正态分布的标准差,控制时间间隔的随机程度 [relationship] enable_relationship = true # 是否启用关系系统 -relation_frequency = 1 # 关系频率,麦麦构建关系的频率 +relation_frequency = 1 # 关系频率,MoFox-Bot构建关系的频率 [message_receive] @@ -238,30 +238,30 @@ enable_mood = true # 是否启用情绪系统 mood_update_threshold = 1 # 情绪更新阈值,越高,更新越慢 [emoji] -emoji_chance = 0.6 # 麦麦激活表情包动作的概率 +emoji_chance = 0.6 # MoFox-Bot激活表情包动作的概率 emoji_activate_type = "llm" # 表情包激活类型,可选:random,llm ; random下,表情包动作随机启用,llm下,表情包动作根据llm判断是否启用 max_reg_num = 60 # 表情包最大注册数量 do_replace = true # 开启则在达到最大数量时删除(替换)表情包,关闭则达到最大数量时不会继续收集表情包 check_interval = 10 # 检查表情包(注册,破损,删除)的时间间隔(分钟) -steal_emoji = true # 是否偷取表情包,让麦麦可以将一些表情包据为己有 +steal_emoji = true # 是否偷取表情包,让MoFox-Bot可以将一些表情包据为己有 content_filtration = false # 是否启用表情包过滤,只有符合该要求的表情包才会被保存 filtration_prompt = "符合公序良俗" # 表情包过滤要求,只有符合该要求的表情包才会被保存 enable_emotion_analysis = false # 是否启用表情包感情关键词二次识别,启用后表情包在第一次识别完毕后将送入第二次大模型识别来总结感情关键词,并构建进回复和决策器的上下文消息中 [memory] enable_memory = true # 是否启用记忆系统 -memory_build_interval = 600 # 记忆构建间隔 单位秒 间隔越低,麦麦学习越多,但是冗余信息也会增多 +memory_build_interval = 600 # 记忆构建间隔 单位秒 间隔越低,MoFox-Bot学习越多,但是冗余信息也会增多 memory_build_distribution = [6.0, 3.0, 0.6, 32.0, 12.0, 0.4] # 记忆构建分布,参数:分布1均值,标准差,权重,分布2均值,标准差,权重 memory_build_sample_num = 8 # 采样数量,数值越高记忆采样次数越多 memory_build_sample_length = 30 # 采样长度,数值越高一段记忆内容越丰富 memory_compress_rate = 0.1 # 记忆压缩率 控制记忆精简程度 建议保持默认,调高可以获得更多信息,但是冗余信息也会增多 -forget_memory_interval = 3000 # 记忆遗忘间隔 单位秒 间隔越低,麦麦遗忘越频繁,记忆更精简,但更难学习 +forget_memory_interval = 3000 # 记忆遗忘间隔 单位秒 间隔越低,MoFox-Bot遗忘越频繁,记忆更精简,但更难学习 memory_forget_time = 48 #多长时间后的记忆会被遗忘 单位小时 memory_forget_percentage = 0.008 # 记忆遗忘比例 控制记忆遗忘程度 越大遗忘越多 建议保持默认 -consolidate_memory_interval = 1000 # 记忆整合间隔 单位秒 间隔越低,麦麦整合越频繁,记忆更精简 +consolidate_memory_interval = 1000 # 记忆整合间隔 单位秒 间隔越低,MoFox-Bot整合越频繁,记忆更精简 consolidation_similarity_threshold = 0.7 # 相似度阈值 consolidation_check_percentage = 0.05 # 检查节点比例 @@ -273,7 +273,7 @@ enable_vector_instant_memory = true # 是否启用基于向量的瞬时记忆 memory_ban_words = [ "表情包", "图片", "回复", "聊天记录" ] [voice] -enable_asr = false # 是否启用语音识别,启用后麦麦可以识别语音消息,启用该功能需要配置语音识别模型[model.voice]s +enable_asr = false # 是否启用语音识别,启用后MoFox-Bot可以识别语音消息,启用该功能需要配置语音识别模型[model.voice]s [lpmm_knowledge] # lpmm知识库配置 enable = false # 是否启用lpmm知识库