From b10d51f222b7a496a1ad44deef897a611a6e21d3 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sat, 26 Jul 2025 14:29:01 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E4=BC=98=E5=8C=96=E7=9A=84no=5Fre?= =?UTF-8?q?ply=EF=BC=8C=E5=8C=BA=E5=88=86waiting=E5=92=8Cbreaking=EF=BC=8C?= =?UTF-8?q?=E5=87=8F=E5=B0=91=E6=97=A0=E7=AB=AF=E6=91=B8=E9=B1=BC=E9=A2=91?= =?UTF-8?q?=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/chat_loop/heartFC_chat.py | 26 +- src/plugins/built_in/core_actions/emoji.py | 7 +- src/plugins/built_in/core_actions/no_reply.py | 300 ++++++++++++------ src/plugins/built_in/core_actions/reply.py | 7 +- 4 files changed, 237 insertions(+), 103 deletions(-) diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index e541db075..2db7ca42a 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -215,15 +215,15 @@ class HeartFChatting: limit_mode="earliest", filter_bot=True, ) + if global_config.chat.focus_value != 0: + if len(new_messages_data) > 3 / pow(global_config.chat.focus_value,0.5): + self.loop_mode = ChatMode.FOCUS + self.energy_value = 10 + (len(new_messages_data) / (3 / pow(global_config.chat.focus_value,0.5))) * 10 + return True - if len(new_messages_data) > 3 / pow(global_config.chat.focus_value,0.5): - self.loop_mode = ChatMode.FOCUS - self.energy_value = 10 + (len(new_messages_data) / (3 / pow(global_config.chat.focus_value,0.5))) * 10 - return True - - if self.energy_value >= 30: - self.loop_mode = ChatMode.FOCUS - return True + if self.energy_value >= 30: + self.loop_mode = ChatMode.FOCUS + return True if new_messages_data: earliest_messages_data = new_messages_data[0] @@ -403,8 +403,18 @@ class HeartFChatting: if self.loop_mode == ChatMode.NORMAL: await self.willing_manager.after_generate_reply_handle(message_data.get("message_id", "")) + # 管理no_reply计数器:当执行了非no_reply动作时,重置计数器 if action_type != "no_reply" and action_type != "no_action": + # 导入NoReplyAction并重置计数器 + from src.plugins.built_in.core_actions.no_reply import NoReplyAction + NoReplyAction.reset_consecutive_count() + logger.info(f"{self.log_prefix} 执行了{action_type}动作,重置no_reply计数器") return True + elif action_type == "no_action": + # 当执行回复动作时,也重置no_reply计数器 + from src.plugins.built_in.core_actions.no_reply import NoReplyAction + NoReplyAction.reset_consecutive_count() + logger.info(f"{self.log_prefix} 执行了回复动作,重置no_reply计数器") return True diff --git a/src/plugins/built_in/core_actions/emoji.py b/src/plugins/built_in/core_actions/emoji.py index 4563b47f8..1c6b9c267 100644 --- a/src/plugins/built_in/core_actions/emoji.py +++ b/src/plugins/built_in/core_actions/emoji.py @@ -9,7 +9,8 @@ from src.common.logger import get_logger # 导入API模块 - 标准Python包方式 from src.plugin_system.apis import emoji_api, llm_api, message_api -from src.plugins.built_in.core_actions.no_reply import NoReplyAction +# 注释:不再需要导入NoReplyAction,因为计数器管理已移至heartFC_chat.py +# from src.plugins.built_in.core_actions.no_reply import NoReplyAction from src.config.config import global_config @@ -143,8 +144,8 @@ class EmojiAction(BaseAction): logger.error(f"{self.log_prefix} 表情包发送失败") return False, "表情包发送失败" - # 重置NoReplyAction的连续计数器 - NoReplyAction.reset_consecutive_count() + # 注释:重置NoReplyAction的连续计数器现在由heartFC_chat.py统一管理 + # NoReplyAction.reset_consecutive_count() return True, f"发送表情包: {emoji_description}" diff --git a/src/plugins/built_in/core_actions/no_reply.py b/src/plugins/built_in/core_actions/no_reply.py index bfa66dac8..eb584a23a 100644 --- a/src/plugins/built_in/core_actions/no_reply.py +++ b/src/plugins/built_in/core_actions/no_reply.py @@ -1,6 +1,7 @@ import random import time -from typing import Tuple +from typing import Tuple, List +from collections import deque # 导入新插件系统 from src.plugin_system import BaseAction, ActionActivationType, ChatMode @@ -17,11 +18,15 @@ logger = get_logger("no_reply_action") class NoReplyAction(BaseAction): - """不回复动作,根据新消息的兴趣值或数量决定何时结束等待. + """不回复动作,支持waiting和breaking两种形式. - 新的等待逻辑: - 1. 新消息累计兴趣值超过阈值 (默认10) 则结束等待 - 2. 累计新消息数量达到随机阈值 (默认5-10条) 则结束等待 + waiting形式: + - 只要有新消息就结束动作 + - 记录新消息的兴趣度到列表(最多保留最近三项) + - 如果最近三次动作都是no_reply,且最近新消息列表兴趣度之和小于阈值,就进入breaking形式 + + breaking形式: + - 和原有逻辑一致,需要消息满足一定数量或累计一定兴趣值才结束动作 """ focus_activation_type = ActionActivationType.NEVER @@ -35,10 +40,13 @@ class NoReplyAction(BaseAction): # 连续no_reply计数器 _consecutive_count = 0 + + # 最近三次no_reply的新消息兴趣度记录 + _recent_interest_records: deque = deque(maxlen=3) - # 新增:兴趣值退出阈值 + # 兴趣值退出阈值 _interest_exit_threshold = 3.0 - # 新增:消息数量退出阈值 + # 消息数量退出阈值 _min_exit_message_count = 3 _max_exit_message_count = 6 @@ -56,91 +64,22 @@ class NoReplyAction(BaseAction): import asyncio try: - # 增加连续计数 - NoReplyAction._consecutive_count += 1 - count = NoReplyAction._consecutive_count - reason = self.action_data.get("reason", "") start_time = self.action_data.get("loop_start_time", time.time()) - check_interval = 0.6 # 每秒检查一次 + check_interval = 0.6 - # 随机生成本次等待需要的新消息数量阈值 - exit_message_count_threshold = random.randint(self._min_exit_message_count, self._max_exit_message_count) + # 判断使用哪种形式 + form_type = self._determine_form_type() + + logger.info(f"{self.log_prefix} 选择不回复(第{NoReplyAction._consecutive_count + 1}次),使用{form_type}形式,原因: {reason}") - logger.info(f"{self.log_prefix} 选择不回复(第{count}次),开始摸鱼,原因: {reason}") + # 增加连续计数(在确定要执行no_reply时才增加) + NoReplyAction._consecutive_count += 1 - # 进入等待状态 - while True: - current_time = time.time() - elapsed_time = current_time - start_time - - # 1. 检查新消息 - recent_messages_dict = message_api.get_messages_by_time_in_chat( - chat_id=self.chat_id, - start_time=start_time, - end_time=current_time, - filter_mai=True, - filter_command=True, - ) - new_message_count = len(recent_messages_dict) - - # 2. 检查消息数量是否达到阈值 - talk_frequency = global_config.chat.get_current_talk_frequency(self.chat_id) - - modified_exit_count_threshold = (exit_message_count_threshold / talk_frequency) / global_config.chat.willing_amplifier - - if new_message_count >= modified_exit_count_threshold: - logger.info( - f"{self.log_prefix} 累计消息数量达到{new_message_count}条(>{modified_exit_count_threshold}),结束等待" - ) - exit_reason = f"{global_config.bot.nickname}(你)看到了{new_message_count}条新消息,可以考虑一下是否要进行回复" - await self.store_action_info( - action_build_into_prompt=False, - action_prompt_display=exit_reason, - action_done=True, - ) - return True, f"累计消息数量达到{new_message_count}条,结束等待 (等待时间: {elapsed_time:.1f}秒)" - - # 3. 检查累计兴趣值 - if new_message_count > 0: - accumulated_interest = 0.0 - for msg_dict in recent_messages_dict: - text = msg_dict.get("processed_plain_text", "") - interest_value = msg_dict.get("interest_value", 0.0) - if text: - accumulated_interest += interest_value * global_config.chat.willing_amplifier - - talk_frequency = global_config.chat.get_current_talk_frequency(self.chat_id) - # 只在兴趣值变化时输出log - if not hasattr(self, "_last_accumulated_interest") or accumulated_interest != self._last_accumulated_interest: - logger.info(f"{self.log_prefix} 当前累计兴趣值: {accumulated_interest:.2f}, 当前聊天频率: {talk_frequency:.2f}") - self._last_accumulated_interest = accumulated_interest - - if accumulated_interest >= self._interest_exit_threshold / talk_frequency: - logger.info( - f"{self.log_prefix} 累计兴趣值达到{accumulated_interest:.2f}(>{self._interest_exit_threshold / talk_frequency}),结束等待" - ) - exit_reason = f"{global_config.bot.nickname}(你)感觉到了大家浓厚的兴趣(兴趣值{accumulated_interest:.1f}),决定重新加入讨论" - await self.store_action_info( - action_build_into_prompt=False, - action_prompt_display=exit_reason, - action_done=True, - ) - return ( - True, - f"累计兴趣值达到{accumulated_interest:.2f},结束等待 (等待时间: {elapsed_time:.1f}秒)", - ) - - # 每10秒输出一次等待状态 - if int(elapsed_time) > 0 and int(elapsed_time) % 10 == 0: - logger.debug( - f"{self.log_prefix} 已等待{elapsed_time:.0f}秒,累计{new_message_count}条消息,继续等待..." - ) - # 使用 asyncio.sleep(1) 来避免在同一秒内重复打印日志 - await asyncio.sleep(1) - - # 短暂等待后继续检查 - await asyncio.sleep(check_interval) + if form_type == "waiting": + return await self._execute_waiting_form(start_time, check_interval) + else: + return await self._execute_breaking_form(start_time, check_interval) except Exception as e: logger.error(f"{self.log_prefix} 不回复动作执行失败: {e}") @@ -153,8 +92,191 @@ class NoReplyAction(BaseAction): ) return False, f"不回复动作执行失败: {e}" + def _determine_form_type(self) -> str: + """判断使用哪种形式的no_reply""" + # 如果连续no_reply次数少于3次,使用waiting形式 + if NoReplyAction._consecutive_count < 3: + return "waiting" + + # 如果最近三次记录不足,使用waiting形式 + if len(NoReplyAction._recent_interest_records) < 3: + return "waiting" + + # 计算最近三次记录的兴趣度总和 + total_recent_interest = sum(NoReplyAction._recent_interest_records) + + # 获取当前聊天频率和意愿系数 + talk_frequency = global_config.chat.get_current_talk_frequency(self.chat_id) + willing_amplifier = global_config.chat.willing_amplifier + + # 计算调整后的阈值 + adjusted_threshold = self._interest_exit_threshold / talk_frequency / willing_amplifier + + logger.info(f"{self.log_prefix} 最近三次兴趣度总和: {total_recent_interest:.2f}, 调整后阈值: {adjusted_threshold:.2f}") + + # 如果兴趣度总和小于阈值,进入breaking形式 + if total_recent_interest < adjusted_threshold: + logger.info(f"{self.log_prefix} 兴趣度不足,进入breaking形式") + return "breaking" + else: + logger.info(f"{self.log_prefix} 兴趣度充足,继续使用waiting形式") + return "waiting" + + async def _execute_waiting_form(self, start_time: float, check_interval: float) -> Tuple[bool, str]: + """执行waiting形式的no_reply""" + import asyncio + + logger.info(f"{self.log_prefix} 进入waiting形式,等待任何新消息") + + while True: + current_time = time.time() + elapsed_time = current_time - start_time + + # 检查新消息 + recent_messages_dict = message_api.get_messages_by_time_in_chat( + chat_id=self.chat_id, + start_time=start_time, + end_time=current_time, + filter_mai=True, + filter_command=True, + ) + new_message_count = len(recent_messages_dict) + + # waiting形式:只要有新消息就结束 + if new_message_count > 0: + # 计算新消息的总兴趣度 + total_interest = 0.0 + for msg_dict in recent_messages_dict: + interest_value = msg_dict.get("interest_value", 0.0) + if msg_dict.get("processed_plain_text", ""): + total_interest += interest_value * global_config.chat.willing_amplifier + + # 记录到最近兴趣度列表 + NoReplyAction._recent_interest_records.append(total_interest) + + logger.info( + f"{self.log_prefix} waiting形式检测到{new_message_count}条新消息,总兴趣度: {total_interest:.2f},结束等待" + ) + + exit_reason = f"{global_config.bot.nickname}(你)看到了{new_message_count}条新消息,可以考虑一下是否要进行回复" + await self.store_action_info( + action_build_into_prompt=False, + action_prompt_display=exit_reason, + action_done=True, + ) + return True, f"waiting形式检测到{new_message_count}条新消息,结束等待 (等待时间: {elapsed_time:.1f}秒)" + + # 每10秒输出一次等待状态 + if int(elapsed_time) > 0 and int(elapsed_time) % 10 == 0: + logger.debug(f"{self.log_prefix} waiting形式已等待{elapsed_time:.0f}秒,继续等待新消息...") + await asyncio.sleep(1) + + # 短暂等待后继续检查 + await asyncio.sleep(check_interval) + + async def _execute_breaking_form(self, start_time: float, check_interval: float) -> Tuple[bool, str]: + """执行breaking形式的no_reply(原有逻辑)""" + import asyncio + + # 随机生成本次等待需要的新消息数量阈值 + exit_message_count_threshold = random.randint(self._min_exit_message_count, self._max_exit_message_count) + + logger.info(f"{self.log_prefix} 进入breaking形式,需要{exit_message_count_threshold}条消息或足够兴趣度") + + while True: + current_time = time.time() + elapsed_time = current_time - start_time + + # 检查新消息 + recent_messages_dict = message_api.get_messages_by_time_in_chat( + chat_id=self.chat_id, + start_time=start_time, + end_time=current_time, + filter_mai=True, + filter_command=True, + ) + new_message_count = len(recent_messages_dict) + + # 检查消息数量是否达到阈值 + talk_frequency = global_config.chat.get_current_talk_frequency(self.chat_id) + modified_exit_count_threshold = (exit_message_count_threshold / talk_frequency) / global_config.chat.willing_amplifier + + if new_message_count >= modified_exit_count_threshold: + # 记录兴趣度到列表 + total_interest = 0.0 + for msg_dict in recent_messages_dict: + interest_value = msg_dict.get("interest_value", 0.0) + if msg_dict.get("processed_plain_text", ""): + total_interest += interest_value * global_config.chat.willing_amplifier + + NoReplyAction._recent_interest_records.append(total_interest) + + logger.info( + f"{self.log_prefix} breaking形式累计消息数量达到{new_message_count}条(>{modified_exit_count_threshold}),结束等待" + ) + exit_reason = f"{global_config.bot.nickname}(你)看到了{new_message_count}条新消息,可以考虑一下是否要进行回复" + await self.store_action_info( + action_build_into_prompt=False, + action_prompt_display=exit_reason, + action_done=True, + ) + return True, f"breaking形式累计消息数量达到{new_message_count}条,结束等待 (等待时间: {elapsed_time:.1f}秒)" + + # 检查累计兴趣值 + if new_message_count > 0: + accumulated_interest = 0.0 + for msg_dict in recent_messages_dict: + text = msg_dict.get("processed_plain_text", "") + interest_value = msg_dict.get("interest_value", 0.0) + if text: + accumulated_interest += interest_value * global_config.chat.willing_amplifier + + # 只在兴趣值变化时输出log + if not hasattr(self, "_last_accumulated_interest") or accumulated_interest != self._last_accumulated_interest: + logger.info(f"{self.log_prefix} breaking形式当前累计兴趣值: {accumulated_interest:.2f}, 当前聊天频率: {talk_frequency:.2f}") + self._last_accumulated_interest = accumulated_interest + + if accumulated_interest >= self._interest_exit_threshold / talk_frequency: + # 记录兴趣度到列表 + NoReplyAction._recent_interest_records.append(accumulated_interest) + + logger.info( + f"{self.log_prefix} breaking形式累计兴趣值达到{accumulated_interest:.2f}(>{self._interest_exit_threshold / talk_frequency}),结束等待" + ) + exit_reason = f"{global_config.bot.nickname}(你)感觉到了大家浓厚的兴趣(兴趣值{accumulated_interest:.1f}),决定重新加入讨论" + await self.store_action_info( + action_build_into_prompt=False, + action_prompt_display=exit_reason, + action_done=True, + ) + return ( + True, + f"breaking形式累计兴趣值达到{accumulated_interest:.2f},结束等待 (等待时间: {elapsed_time:.1f}秒)", + ) + + # 每10秒输出一次等待状态 + if int(elapsed_time) > 0 and int(elapsed_time) % 10 == 0: + logger.debug( + f"{self.log_prefix} breaking形式已等待{elapsed_time:.0f}秒,累计{new_message_count}条消息,继续等待..." + ) + await asyncio.sleep(1) + + # 短暂等待后继续检查 + await asyncio.sleep(check_interval) + @classmethod def reset_consecutive_count(cls): - """重置连续计数器""" + """重置连续计数器和兴趣度记录""" cls._consecutive_count = 0 - logger.debug("NoReplyAction连续计数器已重置") + cls._recent_interest_records.clear() + logger.debug("NoReplyAction连续计数器和兴趣度记录已重置") + + @classmethod + def get_recent_interest_records(cls) -> List[float]: + """获取最近的兴趣度记录""" + return list(cls._recent_interest_records) + + @classmethod + def get_consecutive_count(cls) -> int: + """获取连续计数""" + return cls._consecutive_count diff --git a/src/plugins/built_in/core_actions/reply.py b/src/plugins/built_in/core_actions/reply.py index d73337b29..887879066 100644 --- a/src/plugins/built_in/core_actions/reply.py +++ b/src/plugins/built_in/core_actions/reply.py @@ -13,7 +13,8 @@ from src.common.logger import get_logger # 导入API模块 - 标准Python包方式 from src.plugin_system.apis import generator_api, message_api -from src.plugins.built_in.core_actions.no_reply import NoReplyAction +# 注释:不再需要导入NoReplyAction,因为计数器管理已移至heartFC_chat.py +# from src.plugins.built_in.core_actions.no_reply import NoReplyAction from src.person_info.person_info import get_person_info_manager from src.mais4u.mai_think import mai_thinking_manager from src.mais4u.constant_s4u import ENABLE_S4U @@ -138,8 +139,8 @@ class ReplyAction(BaseAction): action_done=True, ) - # 重置NoReplyAction的连续计数器 - NoReplyAction.reset_consecutive_count() + # 注释:重置NoReplyAction的连续计数器现在由heartFC_chat.py统一管理 + # NoReplyAction.reset_consecutive_count() return success, reply_text