diff --git a/src/main.py b/src/main.py index aa6f908bf..4cb195e86 100644 --- a/src/main.py +++ b/src/main.py @@ -63,7 +63,7 @@ class MainSystem: asyncio.create_task(person_info_manager.personal_habit_deduction()) # 启动愿望管理器 - await willing_manager.ensure_started() + await willing_manager.async_task_starter() # 启动消息处理器 if not self._message_manager_started: diff --git a/src/plugins/chat_module/reasoning_chat/reasoning_chat.py b/src/plugins/chat_module/reasoning_chat/reasoning_chat.py index 683bef463..d6f106d27 100644 --- a/src/plugins/chat_module/reasoning_chat/reasoning_chat.py +++ b/src/plugins/chat_module/reasoning_chat/reasoning_chat.py @@ -54,7 +54,6 @@ class ReasoningChat: ) message_manager.add_message(thinking_message) - willing_manager.change_reply_willing_sent(chat) return thinking_id @@ -126,7 +125,7 @@ class ReasoningChat: ) message_manager.add_message(bot_message) - async def _update_relationship(self, message, response_set): + async def _update_relationship(self, message: MessageRecv, response_set): """更新关系情绪""" ori_response = ",".join(response_set) stance, emotion = await self.gpt._get_emotion_tags(ori_response, message.processed_plain_text) @@ -178,7 +177,16 @@ class ReasoningChat: # 查询缓冲器结果,会整合前面跳过的消息,改变processed_plain_text buffer_result = await message_buffer.query_buffer_result(message) + + # 处理提及 + is_mentioned, reply_probability = is_mentioned_bot_in_message(message) + + # 意愿管理器:设置当前message信息 + willing_manager.setup(message, chat, is_mentioned, interested_rate) + + # 处理缓冲器结果 if not buffer_result: + await willing_manager.bombing_buffer_message_handle(message.message_info.message_id) if message.message_segment.type == "text": logger.info(f"触发缓冲,已炸飞消息:{message.processed_plain_text}") elif message.message_segment.type == "image": @@ -187,45 +195,21 @@ class ReasoningChat: logger.info("触发缓冲,已炸飞消息列") return - # 处理提及 - is_mentioned, reply_probability = is_mentioned_bot_in_message(message) + # 获取回复概率 + if reply_probability != 1: + reply_probability = await willing_manager.get_reply_probability(message.message_info.message_id) - # 计算回复意愿 - current_willing = willing_manager.get_willing(chat_stream=chat) - willing_manager.set_willing(chat.stream_id, current_willing) - - # 意愿激活 - timer1 = time.time() - real_reply_probability = await willing_manager.change_reply_willing_received( - chat_stream=chat, - is_mentioned_bot=is_mentioned, - config=global_config, - is_emoji=message.is_emoji, - interested_rate=interested_rate, - sender_id=str(message.message_info.user_info.user_id), - ) - if reply_probability != 1 or (groupinfo and (groupinfo.group_id not in global_config.talk_allowed_groups)): - reply_probability = real_reply_probability - timer2 = time.time() - timing_results["意愿激活"] = timer2 - timer1 - - # 打印消息信息 - mes_name = chat.group_info.group_name if chat.group_info else "私聊" - current_time = time.strftime("%H:%M:%S", time.localtime(messageinfo.time)) - logger.info( - f"[{current_time}][{mes_name}]" - f"{chat.user_info.user_nickname}:" - f"{message.processed_plain_text}[回复意愿:{current_willing:.2f}][概率:{reply_probability * 100:.1f}%]" - ) - - if message.message_info.additional_config: - if "maimcore_reply_probability_gain" in message.message_info.additional_config.keys(): - reply_probability += message.message_info.additional_config["maimcore_reply_probability_gain"] + if message.message_info.additional_config: + if "maimcore_reply_probability_gain" in message.message_info.additional_config.keys(): + reply_probability += message.message_info.additional_config["maimcore_reply_probability_gain"] do_reply = False if random() < reply_probability: do_reply = True + # 回复前处理 + await willing_manager.before_generate_reply_handle(message.message_info.message_id) + # 创建思考消息 timer1 = time.time() thinking_id = await self._create_thinking_message(message, chat, userinfo, messageinfo) @@ -260,12 +244,21 @@ class ReasoningChat: timer2 = time.time() timing_results["更新关系情绪"] = timer2 - timer1 + # 回复后处理 + await willing_manager.after_generate_reply_handle(message.message_info.message_id) + # 输出性能计时结果 if do_reply: timing_str = " | ".join([f"{step}: {duration:.2f}秒" for step, duration in timing_results.items()]) trigger_msg = message.processed_plain_text response_msg = " ".join(response_set) if response_set else "无回复" logger.info(f"触发消息: {trigger_msg[:20]}... | 推理消息: {response_msg[:20]}... | 性能计时: {timing_str}") + else: + # 不回复处理 + await willing_manager.not_reply_handle(message.message_info.message_id) + + # 意愿管理器:注销当前message信息 + willing_manager.delete(message.message_info.message_id) def _check_ban_words(self, text: str, chat, userinfo) -> bool: """检查消息中是否包含过滤词""" diff --git a/src/plugins/chat_module/think_flow_chat/think_flow_chat.py b/src/plugins/chat_module/think_flow_chat/think_flow_chat.py index f845770d3..7351bffed 100644 --- a/src/plugins/chat_module/think_flow_chat/think_flow_chat.py +++ b/src/plugins/chat_module/think_flow_chat/think_flow_chat.py @@ -55,7 +55,6 @@ class ThinkFlowChat: ) message_manager.add_message(thinking_message) - willing_manager.change_reply_willing_sent(chat) return thinking_id @@ -146,7 +145,7 @@ class ThinkFlowChat: await heartflow.get_subheartflow(stream_id).do_thinking_after_reply(response_set, chat_talking_prompt) - async def _update_relationship(self, message, response_set): + async def _update_relationship(self, message: MessageRecv, response_set): """更新关系情绪""" ori_response = ",".join(response_set) stance, emotion = await self.gpt._get_emotion_tags(ori_response, message.processed_plain_text) @@ -203,7 +202,16 @@ class ThinkFlowChat: # 查询缓冲器结果,会整合前面跳过的消息,改变processed_plain_text buffer_result = await message_buffer.query_buffer_result(message) + + # 处理提及 + is_mentioned, reply_probability = is_mentioned_bot_in_message(message) + + # 意愿管理器:设置当前message信息 + willing_manager.setup(message, chat, is_mentioned, interested_rate) + + # 处理缓冲器结果 if not buffer_result: + await willing_manager.bombing_buffer_message_handle(message.message_info.message_id) if message.message_segment.type == "text": logger.info(f"触发缓冲,已炸飞消息:{message.processed_plain_text}") elif message.message_segment.type == "image": @@ -212,52 +220,29 @@ class ThinkFlowChat: logger.info("触发缓冲,已炸飞消息列") return - # 处理提及 - is_mentioned, reply_probability = is_mentioned_bot_in_message(message) - # 计算回复意愿 - current_willing_old = willing_manager.get_willing(chat_stream=chat) - # current_willing_new = (heartflow.get_subheartflow(chat.stream_id).current_state.willing - 5) / 4 - # current_willing = (current_willing_old + current_willing_new) / 2 - # 有点bug - current_willing = current_willing_old + # current_willing_old = willing_manager.get_willing(chat_stream=chat) + # # current_willing_new = (heartflow.get_subheartflow(chat.stream_id).current_state.willing - 5) / 4 + # # current_willing = (current_willing_old + current_willing_new) / 2 + # # 有点bug + # current_willing = current_willing_old - willing_manager.set_willing(chat.stream_id, current_willing) + # 获取回复概率 + if reply_probability != 1: + reply_probability = await willing_manager.get_reply_probability(message.message_info.message_id) - # 意愿激活 - timer1 = time.time() - real_reply_probability = await willing_manager.change_reply_willing_received( - chat_stream=chat, - is_mentioned_bot=is_mentioned, - config=global_config, - is_emoji=message.is_emoji, - interested_rate=interested_rate, - sender_id=str(message.message_info.user_info.user_id), - ) - if reply_probability != 1 or (groupinfo and (groupinfo.group_id not in global_config.talk_allowed_groups)): - reply_probability = real_reply_probability - timer2 = time.time() - timing_results["意愿激活"] = timer2 - timer1 - logger.debug(f"意愿激活: {reply_probability}") - - # 打印消息信息 - mes_name = chat.group_info.group_name if chat.group_info else "私聊" - current_time = time.strftime("%H:%M:%S", time.localtime(messageinfo.time)) - logger.info( - f"[{current_time}][{mes_name}]" - f"{chat.user_info.user_nickname}:" - f"{message.processed_plain_text}[回复意愿:{current_willing:.2f}][概率:{reply_probability * 100:.1f}%]" - ) - - if message.message_info.additional_config: - if "maimcore_reply_probability_gain" in message.message_info.additional_config.keys(): - reply_probability += message.message_info.additional_config["maimcore_reply_probability_gain"] + if message.message_info.additional_config: + if "maimcore_reply_probability_gain" in message.message_info.additional_config.keys(): + reply_probability += message.message_info.additional_config["maimcore_reply_probability_gain"] do_reply = False if random() < reply_probability: try: do_reply = True + # 回复前处理 + await willing_manager.before_generate_reply_handle(message.message_info.message_id) + # 创建思考消息 try: timer1 = time.time() @@ -333,6 +318,9 @@ class ThinkFlowChat: except Exception as e: logger.error(f"心流更新关系情绪失败: {e}") + # 回复后处理 + await willing_manager.after_generate_reply_handle(message.message_info.message_id) + except Exception as e: logger.error(f"心流处理消息失败: {e}") @@ -342,6 +330,12 @@ class ThinkFlowChat: trigger_msg = message.processed_plain_text response_msg = " ".join(response_set) if response_set else "无回复" logger.info(f"触发消息: {trigger_msg[:20]}... | 思维消息: {response_msg[:20]}... | 性能计时: {timing_str}") + else: + # 不回复处理 + await willing_manager.not_reply_handle(message.message_info.message_id) + + # 意愿管理器:注销当前message信息 + willing_manager.delete(message.message_info.message_id) def _check_ban_words(self, text: str, chat, userinfo) -> bool: """检查消息中是否包含过滤词""" diff --git a/src/plugins/willing/willing_manager.py b/src/plugins/willing/willing_manager.py index 06aaebc13..696e8be69 100644 --- a/src/plugins/willing/willing_manager.py +++ b/src/plugins/willing/willing_manager.py @@ -1,22 +1,144 @@ -from typing import Optional -from src.common.logger import get_module_logger -from ..config.config import global_config -from .mode_classical import WillingManager as ClassicalWillingManager -from .mode_dynamic import WillingManager as DynamicWillingManager -from .mode_custom import WillingManager as CustomWillingManager -from src.common.logger import LogConfig, WILLING_STYLE_CONFIG +from src.common.logger import LogConfig, WILLING_STYLE_CONFIG, LoguruLogger, get_module_logger +from dataclasses import dataclass +from ..config.config import global_config, BotConfig +from ..chat.chat_stream import ChatStream, GroupInfo +from ..chat.message import MessageRecv +from ..person_info.person_info import person_info_manager, PersonInfoManager +from abc import ABC, abstractmethod +import importlib +from typing import Dict, Optional +import asyncio + +""" +基类方法概览: +async_task_starter 在程序启动时执行,在其中用asyncio.create_task启动你想要执行的异步任务 +before_generate_reply_handle 确定要回复后,在生成回复前的处理 +after_generate_reply_handle 确定要回复后,在生成回复后的处理 +not_reply_handle 确定不回复后的处理 +get_reply_probability 获取回复概率(必须实现) +bombing_buffer_message_handle 缓冲器炸飞消息后的处理 +get_willing 获取某聊天流意愿 +set_willing 设置某聊天流意愿 +规范说明: +模块文件命名: `mode_{manager_type}.py` +示例: 若 `manager_type="aggressive"`,则模块文件应为 `mode_aggressive.py` +类命名: `{manager_type}WillingManager` (首字母大写) +示例: 在 `mode_aggressive.py` 中,类名应为 `AggressiveWillingManager` +""" willing_config = LogConfig( # 使用消息发送专用样式 console_format=WILLING_STYLE_CONFIG["console_format"], file_format=WILLING_STYLE_CONFIG["file_format"], ) - logger = get_module_logger("willing", config=willing_config) +@dataclass +class WillingInfo: + """此类保存意愿模块常用的参数 + + Attributes: + message (MessageRecv): 原始消息对象 + chat (ChatStream): 聊天流对象 + person_info_manager (PersonInfoManager): 用户信息管理对象 + chat_id (str): 当前聊天流的标识符 + person_id (str): 发送者的个人信息的标识符 + group_id (str): 群组ID(如果是私聊则为空) + is_mentioned_bot (bool): 是否提及了bot + is_emoji (bool): 是否为表情包 + interested_rate (float): 兴趣度 + """ + message: MessageRecv + chat: ChatStream + person_info_manager: PersonInfoManager + chat_id: str + person_id: str + group_info: Optional[GroupInfo] + is_mentioned_bot: bool + is_emoji: bool + interested_rate: float + # current_mood: float 当前心情? -def init_willing_manager() -> Optional[object]: +class BaseWillingManager(ABC): + """回复意愿管理基类""" + + @classmethod + def create(cls, manager_type: str) -> 'BaseWillingManager': + try: + module = importlib.import_module(f".mode_{manager_type}", __package__) + manager_class = getattr(module, f"{manager_type.capitalize()}WillingManager") + if not issubclass(manager_class, cls): + manager_class = getattr(module, "ClassicalWillingManager") + logger.info("未找到当前意愿模式对应文件,使用经典配方~") + return manager_class() + except (ImportError, AttributeError) as e: + logger.error(f"Failed to create willing manager: {str(e)}") + raise ValueError(f"Invalid willing manager type: {manager_type}") from e + + def __init__(self): + self.chat_reply_willing: Dict[str, float] = {} # 存储每个聊天流的回复意愿(chat_id) + self.ongoing_messages: Dict[str, WillingInfo] = {} # 当前正在进行的消息(message_id) + self.lock = asyncio.Lock() + self.global_config: BotConfig = global_config + self.logger: LoguruLogger = logger + + def setup(self, message: MessageRecv, chat: ChatStream, is_mentioned_bot: bool, interested_rate: float): + person_id = person_info_manager.get_person_id(chat.platform, chat.user_info.user_id) + self.ongoing_messages[message.message_info.message_id] = WillingInfo( + message=message, + chat=chat, + person_info_manager=person_info_manager, + chat_id=chat.stream_id, + person_id=person_id, + group_info=chat.group_info, + is_mentioned_bot=is_mentioned_bot, + is_emoji=message.is_emoji, + interested_rate=interested_rate, + ) + + def delete(self, message_id: str): + del_message = self.ongoing_messages.pop(message_id, None) + if not del_message: + logger.debug(f"删除异常,当前消息{message_id}不存在") + + async def async_task_starter(self) -> None: + """抽象方法:异步任务启动器""" + pass + + async def before_generate_reply_handle(self, message_id: str): + """抽象方法:回复前处理""" + pass + + async def after_generate_reply_handle(self, message_id: str): + """抽象方法:回复后处理""" + pass + + async def not_reply_handle(self, message_id: str): + """抽象方法:不回复处理""" + pass + + @abstractmethod + async def get_reply_probability(self, message_id: str): + """抽象方法:获取回复概率""" + raise NotImplementedError + + async def bombing_buffer_message_handle(self, message_id: str): + """抽象方法:炸飞消息处理""" + pass + + async def get_willing(self, chat_id: str): + """获取指定聊天流的回复意愿""" + async with self.lock: + return self.chat_reply_willing.get(chat_id, 0) + + async def set_willing(self, chat_id: str, willing: float): + """设置指定聊天流的回复意愿""" + async with self.lock: + self.chat_reply_willing[chat_id] = willing + + +def init_willing_manager() -> BaseWillingManager: """ 根据配置初始化并返回对应的WillingManager实例 @@ -24,20 +146,7 @@ def init_willing_manager() -> Optional[object]: 对应mode的WillingManager实例 """ mode = global_config.willing_mode.lower() - - if mode == "classical": - logger.info("使用经典回复意愿管理器") - return ClassicalWillingManager() - elif mode == "dynamic": - logger.info("使用动态回复意愿管理器") - return DynamicWillingManager() - elif mode == "custom": - logger.warning(f"自定义的回复意愿管理器模式: {mode}") - return CustomWillingManager() - else: - logger.warning(f"未知的回复意愿管理器模式: {mode}, 将使用经典模式") - return ClassicalWillingManager() - + return BaseWillingManager.create(mode) # 全局willing_manager对象 willing_manager = init_willing_manager()