This commit is contained in:
雅诺狐
2025-08-28 18:36:11 +08:00
8 changed files with 115 additions and 64 deletions

View File

@@ -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
---

View File

@@ -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

View File

@@ -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','')}于消息到达时取消了消息处理")

View File

@@ -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:

View File

@@ -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:

View File

@@ -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)
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

View File

@@ -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("默认事件初始化完成")

View File

@@ -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" # 表情包激活类型可选randomllm ; 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知识库