From 202ff97652e3a8082661dbeddeb31ea87ce47a52 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Tue, 1 Jul 2025 18:43:07 +0800 Subject: [PATCH 01/21] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9=E8=89=BE?= =?UTF-8?q?=E7=89=B9=E4=BF=A1=E6=81=AF=E7=9A=84=E5=AD=98=E5=82=A8=E5=A4=84?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E4=BB=A5=E6=96=B9=E4=BE=BF=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/message_receive/storage.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/chat/message_receive/storage.py b/src/chat/message_receive/storage.py index 9cd357ab2..4f79c6aac 100644 --- a/src/chat/message_receive/storage.py +++ b/src/chat/message_receive/storage.py @@ -49,6 +49,11 @@ class MessageStorage: # 安全地获取 user_info, 如果为 None 则视为空字典 (以防万一) user_info_from_chat = chat_info_dict.get("user_info") or {} + # 使用正则表达式匹配 @ 格式 + pattern_at = r'@<([^:>]+):\d+>' + # 替换为 @XXX 格式(对含艾特的消息进行处理,使其符合原本展示的文本形态,方便引用回复) + filtered_processed_plain_text = re.sub(pattern_at, r'@\1', filtered_processed_plain_text) + Messages.create( message_id=msg_id, time=float(message.message_info.time), From c87f36973f1680089e910922aaeeffb678d75ebf Mon Sep 17 00:00:00 2001 From: A0000Xz <122650088+A0000Xz@users.noreply.github.com> Date: Fri, 11 Jul 2025 19:30:05 +0800 Subject: [PATCH 02/21] =?UTF-8?q?=E4=BC=98=E5=8C=96no=5Freply=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=92=8C=E5=8F=82=E6=95=B0=EF=BC=8C=E9=80=82=E9=85=8D?= =?UTF-8?q?=E7=A7=81=E8=81=8A=EF=BC=8C=E8=A7=A3=E5=86=B3=E6=8F=92=E7=A9=BA?= =?UTF-8?q?=E7=9A=84=E6=B6=88=E6=81=AF=E6=97=A0=E5=8F=8D=E5=BA=94=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/built_in/core_actions/no_reply.py | 55 ++++++++++++++++--- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/src/plugins/built_in/core_actions/no_reply.py b/src/plugins/built_in/core_actions/no_reply.py index 99337e515..f1c2b640b 100644 --- a/src/plugins/built_in/core_actions/no_reply.py +++ b/src/plugins/built_in/core_actions/no_reply.py @@ -1,5 +1,6 @@ import random import time +import asyncio from typing import Tuple # 导入新插件系统 @@ -15,6 +16,8 @@ from src.config.config import global_config logger = get_logger("core_actions") +#设置一个全局字典,确保同一个消息流的下一个NoReplyAction实例能够获取到上一次消息的时间戳 +_CHAT_START_TIMES = {} class NoReplyAction(BaseAction): """不回复动作,根据新消息的兴趣值或数量决定何时结束等待. @@ -39,29 +42,47 @@ class NoReplyAction(BaseAction): # 新增:兴趣值退出阈值 _interest_exit_threshold = 3.0 # 新增:消息数量退出阈值 - _min_exit_message_count = 5 - _max_exit_message_count = 10 + _min_exit_message_count = 4 + _max_exit_message_count = 8 # 动作参数定义 action_parameters = {"reason": "不回复的原因"} # 动作使用场景 - action_require = ["你发送了消息,目前无人回复"] + action_require = [ + "你发送了消息,目前无人回复", + "你觉得对方还没把话说完", + "你觉得当前话题不适合插嘴", + "你觉得自己说话太多了" + ] # 关联类型 associated_types = [] async def execute(self) -> Tuple[bool, str]: """执行不回复动作""" - import asyncio - try: + + # 获取或初始化当前消息的起始时间,因为用户消息是可能在刚决定好可用动作,但还没选择动作的时候发送的。原先的start_time设计会导致这种消息被漏掉,现在采用全局字典存储 + if self.chat_id not in _CHAT_START_TIMES: + # 如果对应消息流没有存储时间,就设置为当前时间 + _CHAT_START_TIMES[self.chat_id] = time.time() + start_time = _CHAT_START_TIMES[self.chat_id] + else: + message_current_time = time.time() + if message_current_time - _CHAT_START_TIMES[self.chat_id] > 600: + # 如果上一次NoReplyAction实例记录的最后消息时间戳距离现在时间戳超过了十分钟,将会把start_time设置为当前时间戳,避免在数据库内过度搜索 + start_time = message_current_time + logger.debug("距离上一次消息时间过长,已重置等待开始时间为当前时间") + else: + # 如果距离上一次noreply没有十分钟,就沿用上一次noreply退出时记录的最新消息时间戳 + start_time = _CHAT_START_TIMES[self.chat_id] + # 增加连续计数 NoReplyAction._consecutive_count += 1 count = NoReplyAction._consecutive_count reason = self.action_data.get("reason", "") - start_time = time.time() check_interval = 1.0 # 每秒检查一次 # 随机生成本次等待需要的新消息数量阈值 @@ -69,6 +90,9 @@ class NoReplyAction(BaseAction): logger.info( f"{self.log_prefix} 本次no_reply需要 {exit_message_count_threshold} 条新消息或累计兴趣值超过 {self._interest_exit_threshold} 才能打断" ) + if not self.is_group: + exit_message_count_threshold = 1 + logger.info(f"检测到当前环境为私聊,本次no_reply已更正为需要{exit_message_count_threshold}条新消息就能打断") logger.info(f"{self.log_prefix} 选择不回复(第{count}次),开始摸鱼,原因: {reason}") @@ -77,9 +101,9 @@ class NoReplyAction(BaseAction): current_time = time.time() elapsed_time = current_time - start_time - # 1. 检查新消息 + # 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 + chat_id=self.chat_id, start_time=start_time, end_time=current_time, filter_mai=True ) new_message_count = len(recent_messages_dict) @@ -89,11 +113,20 @@ class NoReplyAction(BaseAction): f"{self.log_prefix} 累计消息数量达到{new_message_count}条(>{exit_message_count_threshold}),结束等待" ) exit_reason = f"{global_config.bot.nickname}(你)看到了{new_message_count}条新消息,可以考虑一下是否要进行回复" + # 如果是私聊,就稍微改一下退出理由 + if not self.is_group: + 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, ) + + # 获取最后一条消息 + latest_message = recent_messages_dict[-1] + # 在退出时更新全局字典时间戳(加1微秒防止重复) + _CHAT_START_TIMES[self.chat_id] = latest_message['time'] + 0.000001 # 0.000001秒 = 1微秒 + return True, f"累计消息数量达到{new_message_count}条,结束等待 (等待时间: {elapsed_time:.1f}秒)" # 3. 检查累计兴趣值 @@ -115,6 +148,12 @@ class NoReplyAction(BaseAction): action_prompt_display=exit_reason, action_done=True, ) + + # 获取最后一条消息 + latest_message = recent_messages_dict[-1] + # 在退出时更新全局字典时间戳(加1微秒防止重复) + _CHAT_START_TIMES[self.chat_id] = latest_message['time'] + 0.000001 # 0.000001秒 = 1微秒 + return ( True, f"累计兴趣值达到{accumulated_interest:.2f},结束等待 (等待时间: {elapsed_time:.1f}秒)", From ef1ac55fe0a7071a1c362c5219005afcdb14d57f Mon Sep 17 00:00:00 2001 From: A0000Xz <122650088+A0000Xz@users.noreply.github.com> Date: Fri, 11 Jul 2025 19:31:07 +0800 Subject: [PATCH 03/21] =?UTF-8?q?=E7=8E=B0=E5=9C=A8=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=8F=AF=E4=BB=A5=E9=80=89=E6=8B=A9filter=5F?= =?UTF-8?q?mai=E5=8F=82=E6=95=B0=E6=9D=A5=E5=86=B3=E5=AE=9A=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E8=BF=87=E6=BB=A4=E9=BA=A6=E9=BA=A6=E8=87=AA=E5=B7=B1?= =?UTF-8?q?=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugin_system/apis/message_api.py | 50 +++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/plugin_system/apis/message_api.py b/src/plugin_system/apis/message_api.py index a4241ab53..d3e319595 100644 --- a/src/plugin_system/apis/message_api.py +++ b/src/plugin_system/apis/message_api.py @@ -9,6 +9,7 @@ """ from typing import List, Dict, Any, Tuple, Optional +from src.config.config import global_config import time from src.chat.utils.chat_message_builder import ( get_raw_msg_by_timestamp, @@ -34,7 +35,7 @@ from src.chat.utils.chat_message_builder import ( def get_messages_by_time( - start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest" + start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False ) -> List[Dict[str, Any]]: """ 获取指定时间范围内的消息 @@ -44,15 +45,18 @@ def get_messages_by_time( end_time: 结束时间戳 limit: 限制返回的消息数量,0为不限制 limit_mode: 当limit>0时生效,'earliest'表示获取最早的记录,'latest'表示获取最新的记录 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ + if filter_mai: + return filter_mai_messages(get_raw_msg_by_timestamp(start_time, end_time, limit, limit_mode)) return get_raw_msg_by_timestamp(start_time, end_time, limit, limit_mode) def get_messages_by_time_in_chat( - chat_id: str, start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest" + chat_id: str, start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False ) -> List[Dict[str, Any]]: """ 获取指定聊天中指定时间范围内的消息 @@ -63,15 +67,18 @@ def get_messages_by_time_in_chat( end_time: 结束时间戳 limit: 限制返回的消息数量,0为不限制 limit_mode: 当limit>0时生效,'earliest'表示获取最早的记录,'latest'表示获取最新的记录 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ + if filter_mai: + return filter_mai_messages(get_raw_msg_by_timestamp_with_chat(chat_id, start_time, end_time, limit, limit_mode)) return get_raw_msg_by_timestamp_with_chat(chat_id, start_time, end_time, limit, limit_mode) def get_messages_by_time_in_chat_inclusive( - chat_id: str, start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest" + chat_id: str, start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False ) -> List[Dict[str, Any]]: """ 获取指定聊天中指定时间范围内的消息(包含边界) @@ -82,10 +89,13 @@ def get_messages_by_time_in_chat_inclusive( end_time: 结束时间戳(包含) limit: 限制返回的消息数量,0为不限制 limit_mode: 当limit>0时生效,'earliest'表示获取最早的记录,'latest'表示获取最新的记录 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ + if filter_mai: + return filter_mai_messages(get_raw_msg_by_timestamp_with_chat_inclusive(chat_id, start_time, end_time, limit, limit_mode)) return get_raw_msg_by_timestamp_with_chat_inclusive(chat_id, start_time, end_time, limit, limit_mode) @@ -115,7 +125,7 @@ def get_messages_by_time_in_chat_for_users( def get_random_chat_messages( - start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest" + start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False ) -> List[Dict[str, Any]]: """ 随机选择一个聊天,返回该聊天在指定时间范围内的消息 @@ -125,10 +135,13 @@ def get_random_chat_messages( end_time: 结束时间戳 limit: 限制返回的消息数量,0为不限制 limit_mode: 当limit>0时生效,'earliest'表示获取最早的记录,'latest'表示获取最新的记录 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ + if filter_mai: + return filter_mai_messages(get_raw_msg_by_timestamp_random(start_time, end_time, limit, limit_mode)) return get_raw_msg_by_timestamp_random(start_time, end_time, limit, limit_mode) @@ -151,21 +164,24 @@ def get_messages_by_time_for_users( return get_raw_msg_by_timestamp_with_users(start_time, end_time, person_ids, limit, limit_mode) -def get_messages_before_time(timestamp: float, limit: int = 0) -> List[Dict[str, Any]]: +def get_messages_before_time(timestamp: float, limit: int = 0, filter_mai: bool = False) -> List[Dict[str, Any]]: """ 获取指定时间戳之前的消息 Args: timestamp: 时间戳 limit: 限制返回的消息数量,0为不限制 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ + if filter_mai: + return filter_mai_messages(get_raw_msg_before_timestamp(timestamp, limit)) return get_raw_msg_before_timestamp(timestamp, limit) -def get_messages_before_time_in_chat(chat_id: str, timestamp: float, limit: int = 0) -> List[Dict[str, Any]]: +def get_messages_before_time_in_chat(chat_id: str, timestamp: float, limit: int = 0, filter_mai: bool = False) -> List[Dict[str, Any]]: """ 获取指定聊天中指定时间戳之前的消息 @@ -173,10 +189,13 @@ def get_messages_before_time_in_chat(chat_id: str, timestamp: float, limit: int chat_id: 聊天ID timestamp: 时间戳 limit: 限制返回的消息数量,0为不限制 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ + if filter_mai: + return filter_mai_messages(get_raw_msg_before_timestamp_with_chat(chat_id, timestamp, limit)) return get_raw_msg_before_timestamp_with_chat(chat_id, timestamp, limit) @@ -196,7 +215,7 @@ def get_messages_before_time_for_users(timestamp: float, person_ids: list, limit def get_recent_messages( - chat_id: str, hours: float = 24.0, limit: int = 100, limit_mode: str = "latest" + chat_id: str, hours: float = 24.0, limit: int = 100, limit_mode: str = "latest", filter_mai: bool = False ) -> List[Dict[str, Any]]: """ 获取指定聊天中最近一段时间的消息 @@ -206,12 +225,15 @@ def get_recent_messages( hours: 最近多少小时,默认24小时 limit: 限制返回的消息数量,默认100条 limit_mode: 当limit>0时生效,'earliest'表示获取最早的记录,'latest'表示获取最新的记录 + filter_mai: 是否过滤麦麦自身的消息,默认为False Returns: 消息列表 """ now = time.time() start_time = now - hours * 3600 + if filter_mai: + return filter_mai_messages(get_raw_msg_by_timestamp_with_chat(chat_id, start_time, now, limit, limit_mode)) return get_raw_msg_by_timestamp_with_chat(chat_id, start_time, now, limit, limit_mode) @@ -319,3 +341,17 @@ async def get_person_ids_from_messages(messages: List[Dict[str, Any]]) -> List[s 用户ID列表 """ return await get_person_id_list(messages) + +# ============================================================================= +# 消息过滤函数 +# ============================================================================= + +def filter_mai_messages(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """ + 从消息列表中移除麦麦的消息 + Args: + messages: 消息列表,每个元素是消息字典 + Returns: + 过滤后的消息列表 + """ + return [msg for msg in messages if msg.get("user_id") != str(global_config.bot.qq_account)] From 9dfc15e61c22270267c2cfb5dab812c65e20b538 Mon Sep 17 00:00:00 2001 From: A0000Xz <122650088+A0000Xz@users.noreply.github.com> Date: Fri, 11 Jul 2025 19:32:18 +0800 Subject: [PATCH 04/21] =?UTF-8?q?=E7=A7=81=E8=81=8A=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E5=8F=AF=E8=83=BD=E9=80=80=E5=87=BAfocus=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8Dfocus=E9=80=80=E5=87=BA=E9=98=88?= =?UTF-8?q?=E5=80=BC=E5=8F=8D=E5=90=91=E8=AE=A1=E7=AE=97=E7=9A=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/focus_chat/heartFC_chat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index 08008bfe9..30c0dbb20 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -459,7 +459,7 @@ class HeartFChatting: logger.debug(f"{self.log_prefix} 从action_data中获取系统命令: {command}") # 新增:消息计数和疲惫检查 - if action == "reply" and success: + if action == "reply" and success and self.chat_stream.context.message.message_info.group_info: self._message_count += 1 current_threshold = self._get_current_fatigue_threshold() logger.info( @@ -501,7 +501,7 @@ class HeartFChatting: Returns: int: 当前的疲惫阈值 """ - return max(10, int(30 / global_config.chat.exit_focus_threshold)) + return max(10, int(30 * global_config.chat.exit_focus_threshold)) def get_message_count_info(self) -> dict: """获取消息计数信息 From 0cdf53fb85ad3f67e31fc0023e08a2e407312388 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Fri, 11 Jul 2025 21:51:30 +0800 Subject: [PATCH 05/21] =?UTF-8?q?feat=EF=BC=9A=E8=BF=9B=E4=B8=80=E6=AD=A5?= =?UTF-8?q?=E5=90=88=E5=B9=B6normal=E5=92=8Cfocus=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E9=99=A4interest=5Fdict=EF=BC=88=E9=99=84?= =?UTF-8?q?=E5=B8=A6=E5=85=B6=E4=BB=96=E5=90=88=E7=90=86=E6=80=A7=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/focus_chat/hfc_performance_logger.py | 161 ------- src/chat/focus_chat/hfc_utils.py | 48 -- src/chat/heart_flow/heartflow.py | 4 +- .../heart_flow/heartflow_message_processor.py | 3 +- src/chat/heart_flow/sub_heartflow.py | 149 +++--- src/chat/normal_chat/normal_chat.py | 432 ++++++++---------- src/main.py | 8 - src/mais4u/mais4u_chat/s4u_mood_manager.py | 8 +- src/plugin_system/base/base_action.py | 2 - 9 files changed, 245 insertions(+), 570 deletions(-) delete mode 100644 src/chat/focus_chat/hfc_performance_logger.py diff --git a/src/chat/focus_chat/hfc_performance_logger.py b/src/chat/focus_chat/hfc_performance_logger.py deleted file mode 100644 index 64e65ff85..000000000 --- a/src/chat/focus_chat/hfc_performance_logger.py +++ /dev/null @@ -1,161 +0,0 @@ -import json -from datetime import datetime -from typing import Dict, Any -from pathlib import Path -from src.common.logger import get_logger - -logger = get_logger("hfc_performance") - - -class HFCPerformanceLogger: - """HFC性能记录管理器""" - - # 版本号常量,可在启动时修改 - INTERNAL_VERSION = "v7.0.0" - - def __init__(self, chat_id: str): - self.chat_id = chat_id - self.version = self.INTERNAL_VERSION - self.log_dir = Path("log/hfc_loop") - self.session_start_time = datetime.now() - - # 确保目录存在 - self.log_dir.mkdir(parents=True, exist_ok=True) - - # 当前会话的日志文件,包含版本号 - version_suffix = self.version.replace(".", "_") - self.session_file = ( - self.log_dir / f"{chat_id}_{version_suffix}_{self.session_start_time.strftime('%Y%m%d_%H%M%S')}.json" - ) - self.current_session_data = [] - - def record_cycle(self, cycle_data: Dict[str, Any]): - """记录单次循环数据""" - try: - # 构建记录数据 - record = { - "timestamp": datetime.now().isoformat(), - "version": self.version, - "cycle_id": cycle_data.get("cycle_id"), - "chat_id": self.chat_id, - "action_type": cycle_data.get("action_type", "unknown"), - "total_time": cycle_data.get("total_time", 0), - "step_times": cycle_data.get("step_times", {}), - "reasoning": cycle_data.get("reasoning", ""), - "success": cycle_data.get("success", False), - } - - # 添加到当前会话数据 - self.current_session_data.append(record) - - # 立即写入文件(防止数据丢失) - self._write_session_data() - - # 构建详细的日志信息 - log_parts = [ - f"cycle_id={record['cycle_id']}", - f"action={record['action_type']}", - f"time={record['total_time']:.2f}s", - ] - - logger.debug(f"记录HFC循环数据: {', '.join(log_parts)}") - - except Exception as e: - logger.error(f"记录HFC循环数据失败: {e}") - - def _write_session_data(self): - """写入当前会话数据到文件""" - try: - with open(self.session_file, "w", encoding="utf-8") as f: - json.dump(self.current_session_data, f, ensure_ascii=False, indent=2) - except Exception as e: - logger.error(f"写入会话数据失败: {e}") - - def get_current_session_stats(self) -> Dict[str, Any]: - """获取当前会话的基本信息""" - if not self.current_session_data: - return {} - - return { - "chat_id": self.chat_id, - "version": self.version, - "session_file": str(self.session_file), - "record_count": len(self.current_session_data), - "start_time": self.session_start_time.isoformat(), - } - - def finalize_session(self): - """结束会话""" - try: - if self.current_session_data: - logger.info(f"完成会话,当前会话 {len(self.current_session_data)} 条记录") - except Exception as e: - logger.error(f"结束会话失败: {e}") - - @classmethod - def cleanup_old_logs(cls, max_size_mb: float = 50.0): - """ - 清理旧的HFC日志文件,保持目录大小在指定限制内 - - Args: - max_size_mb: 最大目录大小限制(MB) - """ - log_dir = Path("log/hfc_loop") - if not log_dir.exists(): - logger.info("HFC日志目录不存在,跳过日志清理") - return - - # 获取所有日志文件及其信息 - log_files = [] - total_size = 0 - - for log_file in log_dir.glob("*.json"): - try: - file_stat = log_file.stat() - log_files.append({"path": log_file, "size": file_stat.st_size, "mtime": file_stat.st_mtime}) - total_size += file_stat.st_size - except Exception as e: - logger.warning(f"无法获取文件信息 {log_file}: {e}") - - if not log_files: - logger.info("没有找到HFC日志文件") - return - - max_size_bytes = max_size_mb * 1024 * 1024 - current_size_mb = total_size / (1024 * 1024) - - logger.info(f"HFC日志目录当前大小: {current_size_mb:.2f}MB,限制: {max_size_mb}MB") - - if total_size <= max_size_bytes: - logger.info("HFC日志目录大小在限制范围内,无需清理") - return - - # 按修改时间排序(最早的在前面) - log_files.sort(key=lambda x: x["mtime"]) - - deleted_count = 0 - deleted_size = 0 - - for file_info in log_files: - if total_size <= max_size_bytes: - break - - try: - file_size = file_info["size"] - file_path = file_info["path"] - - file_path.unlink() - total_size -= file_size - deleted_size += file_size - deleted_count += 1 - - logger.info(f"删除旧日志文件: {file_path.name} ({file_size / 1024:.1f}KB)") - - except Exception as e: - logger.error(f"删除日志文件失败 {file_info['path']}: {e}") - - final_size_mb = total_size / (1024 * 1024) - deleted_size_mb = deleted_size / (1024 * 1024) - - logger.info(f"HFC日志清理完成: 删除了{deleted_count}个文件,释放{deleted_size_mb:.2f}MB空间") - logger.info(f"清理后目录大小: {final_size_mb:.2f}MB") diff --git a/src/chat/focus_chat/hfc_utils.py b/src/chat/focus_chat/hfc_utils.py index 11b04c801..5820d8eb4 100644 --- a/src/chat/focus_chat/hfc_utils.py +++ b/src/chat/focus_chat/hfc_utils.py @@ -9,8 +9,6 @@ from typing import Dict, Any logger = get_logger(__name__) -log_dir = "log/log_cycle_debug/" - class CycleDetail: """循环信息记录类""" @@ -104,34 +102,6 @@ class CycleDetail: self.loop_action_info = loop_info["loop_action_info"] -async def create_empty_anchor_message( - platform: str, group_info: dict, chat_stream: ChatStream -) -> Optional[MessageRecv]: - """ - 重构观察到的最后一条消息作为回复的锚点, - 如果重构失败或观察为空,则创建一个占位符。 - """ - - placeholder_id = f"mid_pf_{int(time.time() * 1000)}" - placeholder_user = UserInfo(user_id="system_trigger", user_nickname="System Trigger", platform=platform) - placeholder_msg_info = BaseMessageInfo( - message_id=placeholder_id, - platform=platform, - group_info=group_info, - user_info=placeholder_user, - time=time.time(), - ) - placeholder_msg_dict = { - "message_info": placeholder_msg_info.to_dict(), - "processed_plain_text": "[System Trigger Context]", - "raw_message": "", - "time": placeholder_msg_info.time, - } - anchor_message = MessageRecv(placeholder_msg_dict) - anchor_message.update_chat_stream(chat_stream) - - return anchor_message - def parse_thinking_id_to_timestamp(thinking_id: str) -> float: """ @@ -143,21 +113,3 @@ def parse_thinking_id_to_timestamp(thinking_id: str) -> float: ts_str = thinking_id[3:] return float(ts_str) - -def get_keywords_from_json(json_str: str) -> list[str]: - # 提取JSON内容 - start = json_str.find("{") - end = json_str.rfind("}") + 1 - if start == -1 or end == 0: - logger.error("未找到有效的JSON内容") - return [] - - json_content = json_str[start:end] - - # 解析JSON - try: - json_data = json.loads(json_content) - return json_data.get("keywords", []) - except json.JSONDecodeError as e: - logger.error(f"JSON解析失败: {e}") - return [] diff --git a/src/chat/heart_flow/heartflow.py b/src/chat/heart_flow/heartflow.py index ca6e8be7b..cac19f780 100644 --- a/src/chat/heart_flow/heartflow.py +++ b/src/chat/heart_flow/heartflow.py @@ -1,3 +1,4 @@ +import traceback from src.chat.heart_flow.sub_heartflow import SubHeartflow, ChatState from src.common.logger import get_logger from typing import Any, Optional @@ -30,11 +31,12 @@ class Heartflow: # 注册子心流 self.subheartflows[subheartflow_id] = new_subflow heartflow_name = get_chat_manager().get_stream_name(subheartflow_id) or subheartflow_id - logger.debug(f"[{heartflow_name}] 开始接收消息") + logger.info(f"[{heartflow_name}] 开始接收消息") return new_subflow except Exception as e: logger.error(f"创建子心流 {subheartflow_id} 失败: {e}", exc_info=True) + traceback.print_exc() return None async def force_change_subheartflow_status(self, subheartflow_id: str, status: ChatState) -> None: diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index ba75bc350..2722e1de4 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -108,13 +108,14 @@ class HeartFCMessageReceiver: interested_rate, is_mentioned = await _calculate_interest(message) message.interest_value = interested_rate + message.is_mentioned = is_mentioned await self.storage.store_message(message, chat) subheartflow = await heartflow.get_or_create_subheartflow(chat.stream_id) message.update_chat_stream(chat) - subheartflow.add_message_to_normal_chat_cache(message, interested_rate, is_mentioned) + # subheartflow.add_message_to_normal_chat_cache(message, interested_rate, is_mentioned) chat_mood = mood_manager.get_mood_by_chat_id(subheartflow.chat_id) asyncio.create_task(chat_mood.update_mood_by_message(message, interested_rate)) diff --git a/src/chat/heart_flow/sub_heartflow.py b/src/chat/heart_flow/sub_heartflow.py index 9ef357379..0e4655952 100644 --- a/src/chat/heart_flow/sub_heartflow.py +++ b/src/chat/heart_flow/sub_heartflow.py @@ -39,16 +39,21 @@ class SubHeartflow: self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_id) self.log_prefix = get_chat_manager().get_stream_name(self.subheartflow_id) or self.subheartflow_id - # 兴趣消息集合 - self.interest_dict: Dict[str, tuple[MessageRecv, float, bool]] = {} - + # focus模式退出冷却时间管理 self.last_focus_exit_time: float = 0 # 上次退出focus模式的时间 # 随便水群 normal_chat 和 认真水群 focus_chat 实例 # CHAT模式激活 随便水群 FOCUS模式激活 认真水群 - self.heart_fc_instance: Optional[HeartFChatting] = None # 该sub_heartflow的HeartFChatting实例 - self.normal_chat_instance: Optional[NormalChat] = None # 该sub_heartflow的NormalChat实例 + self.heart_fc_instance: Optional[HeartFChatting] = HeartFChatting( + chat_id=self.subheartflow_id, + on_stop_focus_chat=self._handle_stop_focus_chat_request, + ) # 该sub_heartflow的HeartFChatting实例 + self.normal_chat_instance: Optional[NormalChat] = NormalChat( + chat_stream=get_chat_manager().get_stream(self.chat_id), + on_switch_to_focus_callback=self._handle_switch_to_focus_request, + get_cooldown_progress_callback=self.get_cooldown_progress, + ) # 该sub_heartflow的NormalChat实例 async def initialize(self): """异步初始化方法,创建兴趣流并确定聊天类型""" @@ -79,10 +84,6 @@ class SubHeartflow: # 使用更短的超时时间,强制快速停止 await asyncio.wait_for(self.normal_chat_instance.stop_chat(), timeout=3.0) logger.debug(f"{self.log_prefix} stop_chat() 调用完成") - except asyncio.TimeoutError: - logger.warning(f"{self.log_prefix} 停止 NormalChat 超时,强制清理") - # 超时时强制清理实例 - self.normal_chat_instance = None except Exception as e: logger.error(f"{self.log_prefix} 停止 NormalChat 监控任务时出错: {e}") # 出错时也要清理实例,避免状态不一致 @@ -93,8 +94,10 @@ class SubHeartflow: logger.warning(f"{self.log_prefix} 强制清理 NormalChat 实例") self.normal_chat_instance = None logger.debug(f"{self.log_prefix} _stop_normal_chat 完成") + else: + logger.info(f"{self.log_prefix} 没有normal聊天实例,无需停止normal聊天") - async def _start_normal_chat(self, rewind=False) -> bool: + async def _start_normal_chat(self) -> bool: """ 启动 NormalChat 实例,并进行异步初始化。 进入 CHAT 状态时使用。 @@ -102,30 +105,23 @@ class SubHeartflow: """ await self._stop_heart_fc_chat() # 确保 专注聊天已停止 - self.interest_dict.clear() - - log_prefix = self.log_prefix try: # 获取聊天流并创建 NormalChat 实例 (同步部分) chat_stream = get_chat_manager().get_stream(self.chat_id) - if not chat_stream: - logger.error(f"{log_prefix} 无法获取 chat_stream,无法启动 NormalChat。") - return False - # 在 rewind 为 True 或 NormalChat 实例尚未创建时,创建新实例 - if rewind or not self.normal_chat_instance: + # 在 NormalChat 实例尚未创建时,创建新实例 + if not self.normal_chat_instance: # 提供回调函数,用于接收需要切换到focus模式的通知 self.normal_chat_instance = NormalChat( chat_stream=chat_stream, - interest_dict=self.interest_dict, on_switch_to_focus_callback=self._handle_switch_to_focus_request, get_cooldown_progress_callback=self.get_cooldown_progress, ) - logger.info(f"{log_prefix} 开始普通聊天,随便水群...") + logger.info(f"[{self.log_prefix}] 开始普通聊天") await self.normal_chat_instance.start_chat() # start_chat now ensures init is called again if needed return True except Exception as e: - logger.error(f"{log_prefix} 启动 NormalChat 或其初始化时出错: {e}") + logger.error(f"[{self.log_prefix}] 启动 NormalChat 或其初始化时出错: {e}") logger.error(traceback.format_exc()) self.normal_chat_instance = None # 启动/初始化失败,清理实例 return False @@ -173,68 +169,36 @@ class SubHeartflow: async def _stop_heart_fc_chat(self): """停止并清理 HeartFChatting 实例""" - if self.heart_fc_instance: - logger.debug(f"{self.log_prefix} 结束专注聊天...") + if self.heart_fc_instance.running: + logger.info(f"{self.log_prefix} 结束专注聊天...") try: await self.heart_fc_instance.shutdown() except Exception as e: logger.error(f"{self.log_prefix} 关闭 HeartFChatting 实例时出错: {e}") logger.error(traceback.format_exc()) - finally: - # 无论是否成功关闭,都清理引用 - self.heart_fc_instance = None + else: + logger.info(f"{self.log_prefix} 没有专注聊天实例,无需停止专注聊天") async def _start_heart_fc_chat(self) -> bool: """启动 HeartFChatting 实例,确保 NormalChat 已停止""" - logger.debug(f"{self.log_prefix} 开始启动 HeartFChatting") - try: - # 确保普通聊天监控已停止 - await self._stop_normal_chat() - self.interest_dict.clear() - - log_prefix = self.log_prefix - # 如果实例已存在,检查其循环任务状态 - if self.heart_fc_instance: - logger.debug(f"{log_prefix} HeartFChatting 实例已存在,检查状态") - # 如果任务已完成或不存在,则尝试重新启动 - if self.heart_fc_instance._loop_task is None or self.heart_fc_instance._loop_task.done(): - logger.info(f"{log_prefix} HeartFChatting 实例存在但循环未运行,尝试启动...") - try: - # 添加超时保护 - await asyncio.wait_for(self.heart_fc_instance.start(), timeout=15.0) - logger.info(f"{log_prefix} HeartFChatting 循环已启动。") - return True - except Exception as e: - logger.error(f"{log_prefix} 尝试启动现有 HeartFChatting 循环时出错: {e}") - logger.error(traceback.format_exc()) - # 出错时清理实例,准备重新创建 - self.heart_fc_instance = None - else: - # 任务正在运行 - logger.debug(f"{log_prefix} HeartFChatting 已在运行中。") - return True # 已经在运行 - - # 如果实例不存在,则创建并启动 - logger.info(f"{log_prefix} 麦麦准备开始专注聊天...") - try: - logger.debug(f"{log_prefix} 创建新的 HeartFChatting 实例") - self.heart_fc_instance = HeartFChatting( - chat_id=self.subheartflow_id, - on_stop_focus_chat=self._handle_stop_focus_chat_request, - ) - - logger.debug(f"{log_prefix} 启动 HeartFChatting 实例") - # 添加超时保护 - await asyncio.wait_for(self.heart_fc_instance.start(), timeout=15.0) - logger.debug(f"{log_prefix} 麦麦已成功进入专注聊天模式 (新实例已启动)。") - return True - - except Exception as e: - logger.error(f"{log_prefix} 创建或启动 HeartFChatting 实例时出错: {e}") - logger.error(traceback.format_exc()) - self.heart_fc_instance = None # 创建或初始化异常,清理实例 - return False + # 如果任务已完成或不存在,则尝试重新启动 + if self.heart_fc_instance._loop_task is None or self.heart_fc_instance._loop_task.done(): + logger.info(f"{self.log_prefix} HeartFChatting 实例存在但循环未运行,尝试启动...") + try: + # 添加超时保护 + await asyncio.wait_for(self.heart_fc_instance.start(), timeout=15.0) + logger.info(f"{self.log_prefix} HeartFChatting 循环已启动。") + return True + except Exception as e: + logger.error(f"{self.log_prefix} 尝试启动现有 HeartFChatting 循环时出错: {e}") + logger.error(traceback.format_exc()) + # 出错时清理实例,准备重新创建 + self.heart_fc_instance = None + else: + # 任务正在运行 + logger.debug(f"{self.log_prefix} HeartFChatting 已在运行中。") + return True # 已经在运行 except Exception as e: logger.error(f"{self.log_prefix} _start_heart_fc_chat 执行时出错: {e}") @@ -248,39 +212,36 @@ class SubHeartflow: """ current_state = self.chat_state.chat_status state_changed = False - log_prefix = f"[{self.log_prefix}]" + if new_state == ChatState.NORMAL: - logger.debug(f"{log_prefix} 准备进入 normal聊天 状态") - if await self._start_normal_chat(): - logger.debug(f"{log_prefix} 成功进入或保持 NormalChat 状态。") - state_changed = True - else: - logger.error(f"{log_prefix} 启动 NormalChat 失败,无法进入 CHAT 状态。") - # 启动失败时,保持当前状态 + if self.normal_chat_instance.running: + logger.info(f"{self.log_prefix} 当前状态已经为normal") return + else: + if await self._start_normal_chat(): + logger.debug(f"{self.log_prefix} 成功进入或保持 NormalChat 状态。") + state_changed = True + else: + logger.error(f"{self.log_prefix} 启动 NormalChat 失败,无法进入 CHAT 状态。") + return elif new_state == ChatState.FOCUSED: - logger.debug(f"{log_prefix} 准备进入 focus聊天 状态") + if self.heart_fc_instance.running: + logger.info(f"{self.log_prefix} 当前状态已经为focused") + return if await self._start_heart_fc_chat(): - logger.debug(f"{log_prefix} 成功进入或保持 HeartFChatting 状态。") + logger.debug(f"{self.log_prefix} 成功进入或保持 HeartFChatting 状态。") state_changed = True else: - logger.error(f"{log_prefix} 启动 HeartFChatting 失败,无法进入 FOCUSED 状态。") + logger.error(f"{self.log_prefix} 启动 HeartFChatting 失败,无法进入 FOCUSED 状态。") # 启动失败时,保持当前状态 return - elif new_state == ChatState.ABSENT: - logger.info(f"{log_prefix} 进入 ABSENT 状态,停止所有聊天活动...") - self.interest_dict.clear() - await self._stop_normal_chat() - await self._stop_heart_fc_chat() - state_changed = True - # --- 记录focus模式退出时间 --- if state_changed and current_state == ChatState.FOCUSED and new_state != ChatState.FOCUSED: self.last_focus_exit_time = time.time() - logger.debug(f"{log_prefix} 记录focus模式退出时间: {self.last_focus_exit_time}") + logger.debug(f"{self.log_prefix} 记录focus模式退出时间: {self.last_focus_exit_time}") # --- 更新状态和最后活动时间 --- if state_changed: @@ -292,7 +253,7 @@ class SubHeartflow: self.chat_state_changed_time = time.time() else: logger.debug( - f"{log_prefix} 尝试将状态从 {current_state.value} 变为 {new_state.value},但未成功或未执行更改。" + f"{self.log_prefix} 尝试将状态从 {current_state.value} 变为 {new_state.value},但未成功或未执行更改。" ) def add_message_to_normal_chat_cache(self, message: MessageRecv, interest_value: float, is_mentioned: bool): diff --git a/src/chat/normal_chat/normal_chat.py b/src/chat/normal_chat/normal_chat.py index 883765c72..32fb24966 100644 --- a/src/chat/normal_chat/normal_chat.py +++ b/src/chat/normal_chat/normal_chat.py @@ -11,7 +11,7 @@ from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager from src.chat.utils.timer_calculator import Timer from src.common.message_repository import count_messages from src.chat.utils.prompt_builder import global_prompt_manager -from ..message_receive.message import MessageSending, MessageRecv, MessageThinking, MessageSet +from ..message_receive.message import MessageSending, MessageThinking, MessageSet, MessageRecv,message_from_db_dict from src.chat.message_receive.normal_message_sender import message_manager from src.chat.normal_chat.willing.willing_manager import get_willing_manager from src.chat.planner_actions.action_manager import ActionManager @@ -20,6 +20,7 @@ from .priority_manager import PriorityManager import traceback from src.chat.planner_actions.planner import ActionPlanner from src.chat.planner_actions.action_modifier import ActionModifier +from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat_inclusive from src.chat.utils.utils import get_chat_type_and_target_info from src.mood.mood_manager import mood_manager @@ -28,6 +29,7 @@ willing_manager = get_willing_manager() logger = get_logger("normal_chat") +LOOP_INTERVAL = 0.3 class NormalChat: """ @@ -38,7 +40,6 @@ class NormalChat: def __init__( self, chat_stream: ChatStream, - interest_dict: dict = None, on_switch_to_focus_callback=None, get_cooldown_progress_callback=None, ): @@ -50,14 +51,12 @@ class NormalChat: """ self.chat_stream = chat_stream self.stream_id = chat_stream.stream_id + self.last_read_time = time.time()-1 self.stream_name = get_chat_manager().get_stream_name(self.stream_id) or self.stream_id self.relationship_builder = relationship_builder_manager.get_or_create_builder(self.stream_id) - # Interest dict - self.interest_dict = interest_dict - self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.stream_id) self.willing_amplifier = 1 @@ -65,6 +64,8 @@ class NormalChat: self.mood_manager = mood_manager self.start_time = time.time() + + self.running = False self._initialized = False # Track initialization status @@ -93,14 +94,13 @@ class NormalChat: # 任务管理 self._chat_task: Optional[asyncio.Task] = None + self._priority_chat_task: Optional[asyncio.Task] = None # for priority mode consumer self._disabled = False # 停用标志 # 新增:回复模式和优先级管理器 self.reply_mode = self.chat_stream.context.get_priority_mode() if self.reply_mode == "priority": - interest_dict = interest_dict or {} self.priority_manager = PriorityManager( - interest_dict=interest_dict, normal_queue_max_size=5, ) else: @@ -114,34 +114,90 @@ class NormalChat: if self.reply_mode == "priority" and self._priority_chat_task and not self._priority_chat_task.done(): self._priority_chat_task.cancel() logger.info(f"[{self.stream_name}] NormalChat 已停用。") + + async def _interest_mode_loopbody(self): + try: + await asyncio.sleep(LOOP_INTERVAL) + + if self._disabled: + return False - async def _priority_chat_loop_add_message(self): - while not self._disabled: + now = time.time() + new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( + chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" + ) + + if new_messages_data: + self.last_read_time = now + + for msg_data in new_messages_data: + try: + self.adjust_reply_frequency() + await self.normal_response( + message_data=msg_data, + is_mentioned=msg_data.get("is_mentioned", False), + interested_rate=msg_data.get("interest_rate", 0.0) * self.willing_amplifier, + ) + return True + except Exception as e: + logger.error(f"[{self.stream_name}] 处理消息时出错: {e} {traceback.format_exc()}") + + + except asyncio.CancelledError: + logger.info(f"[{self.stream_name}] 兴趣模式轮询任务被取消") + return False + except Exception: + logger.error(f"[{self.stream_name}] 兴趣模式轮询循环出现错误: {traceback.format_exc()}", exc_info=True) + await asyncio.sleep(10) + + async def _priority_mode_loopbody(self): try: - # 创建字典条目的副本以避免在迭代时发生修改 - items_to_process = list(self.interest_dict.items()) - for msg_id, value in items_to_process: - # 尝试从原始字典中弹出条目,如果它已被其他任务处理,则跳过 - if self.interest_dict.pop(msg_id, None) is None: - continue # 条目已被其他任务处理 + await asyncio.sleep(LOOP_INTERVAL) - message, interest_value, _ = value - if not self._disabled: - # 更新消息段信息 - # self._update_user_message_segments(message) + if self._disabled: + return False - # 添加消息到优先级管理器 - if self.priority_manager: - self.priority_manager.add_message(message, interest_value) - - except Exception: - logger.error( - f"[{self.stream_name}] 优先级聊天循环添加消息时出现错误: {traceback.format_exc()}", exc_info=True + now = time.time() + new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( + chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" ) - print(traceback.format_exc()) - # 出现错误时,等待一段时间再重试 - raise - await asyncio.sleep(0.1) + + if new_messages_data: + self.last_read_time = now + + for msg_data in new_messages_data: + try: + if self.priority_manager: + self.priority_manager.add_message(msg_data, msg_data.get("interest_rate", 0.0)) + return True + except Exception as e: + logger.error(f"[{self.stream_name}] 添加消息到优先级队列时出错: {e} {traceback.format_exc()}") + + + except asyncio.CancelledError: + logger.info(f"[{self.stream_name}] 优先级消息生产者任务被取消") + return False + except Exception: + logger.error(f"[{self.stream_name}] 优先级消息生产者循环出现错误: {traceback.format_exc()}", exc_info=True) + await asyncio.sleep(10) + + async def _interest_message_polling_loop(self): + """ + [Interest Mode] 通过轮询数据库获取新消息并直接处理。 + """ + logger.info(f"[{self.stream_name}] 兴趣模式消息轮询任务开始") + try: + while not self._disabled: + success = await self._interest_mode_loopbody() + + if not success: + break + + except asyncio.CancelledError: + logger.info(f"[{self.stream_name}] 兴趣模式消息轮询任务被优雅地取消了") + + + async def _priority_chat_loop(self): """ @@ -149,16 +205,16 @@ class NormalChat: """ while not self._disabled: try: - if not self.priority_manager.is_empty(): - # 获取最高优先级的消息 - message = self.priority_manager.get_highest_priority_message() + if self.priority_manager and not self.priority_manager.is_empty(): + # 获取最高优先级的消息,现在是字典 + message_data = self.priority_manager.get_highest_priority_message() - if message: + if message_data: logger.info( - f"[{self.stream_name}] 从队列中取出消息进行处理: User {message.message_info.user_info.user_id}, Time: {time.strftime('%H:%M:%S', time.localtime(message.message_info.time))}" + f"[{self.stream_name}] 从队列中取出消息进行处理: User {message_data.get('user_id')}, Time: {time.strftime('%H:%M:%S', time.localtime(message_data.get('time')))}" ) - do_reply = await self.reply_one_message(message) + do_reply = await self.reply_one_message(message_data) response_set = do_reply if do_reply else [] factor = 0.5 cnt = sum([len(r) for r in response_set]) @@ -176,14 +232,12 @@ class NormalChat: await asyncio.sleep(10) # 改为实例方法 - async def _create_thinking_message(self, message: MessageRecv, timestamp: Optional[float] = None) -> str: + async def _create_thinking_message(self, message_data: dict, timestamp: Optional[float] = None) -> str: """创建思考消息""" - messageinfo = message.message_info - bot_user_info = UserInfo( user_id=global_config.bot.qq_account, user_nickname=global_config.bot.nickname, - platform=messageinfo.platform, + platform=message_data.get("chat_info_platform"), ) thinking_time_point = round(time.time(), 2) @@ -192,7 +246,7 @@ class NormalChat: message_id=thinking_id, chat_stream=self.chat_stream, bot_user_info=bot_user_info, - reply=message, + reply=None, thinking_start_time=thinking_time_point, timestamp=timestamp if timestamp is not None else None, ) @@ -202,7 +256,7 @@ class NormalChat: # 改为实例方法 async def _add_messages_to_manager( - self, message: MessageRecv, response_set: List[str], thinking_id + self, message_data: dict, response_set: List[str], thinking_id ) -> Optional[MessageSending]: """发送回复消息""" container = await message_manager.get_container(self.stream_id) # 使用 self.stream_id @@ -221,6 +275,15 @@ class NormalChat: thinking_start_time = thinking_message.thinking_start_time message_set = MessageSet(self.chat_stream, thinking_id) # 使用 self.chat_stream + sender_info = UserInfo( + user_id=message_data.get("user_id"), + user_nickname=message_data.get("user_nickname"), + platform=message_data.get("chat_info_platform"), + ) + + reply = message_from_db_dict(message_data) + + mark_head = False first_bot_msg = None for msg in response_set: @@ -233,11 +296,11 @@ class NormalChat: bot_user_info=UserInfo( user_id=global_config.bot.qq_account, user_nickname=global_config.bot.nickname, - platform=message.message_info.platform, + platform=message_data.get("chat_info_platform"), ), - sender_info=message.message_info.user_info, + sender_info=sender_info, message_segment=message_segment, - reply=message, + reply=reply, is_head=not mark_head, is_emoji=False, thinking_start_time=thinking_start_time, @@ -252,122 +315,8 @@ class NormalChat: return first_bot_msg - async def _reply_interested_message(self) -> None: - """ - 后台任务方法,轮询当前实例关联chat的兴趣消息 - 通常由start_monitoring_interest()启动 - """ - logger.debug(f"[{self.stream_name}] 兴趣监控任务开始") - - try: - while True: - # 第一层检查:立即检查取消和停用状态 - if self._disabled: - logger.info(f"[{self.stream_name}] 检测到停用标志,退出兴趣监控") - break - - # 检查当前任务是否已被取消 - current_task = asyncio.current_task() - if current_task and current_task.cancelled(): - logger.info(f"[{self.stream_name}] 当前任务已被取消,退出") - break - - try: - # 短暂等待,让出控制权 - await asyncio.sleep(0.1) - - # 第二层检查:睡眠后再次检查状态 - if self._disabled: - logger.info(f"[{self.stream_name}] 睡眠后检测到停用标志,退出") - break - - # 获取待处理消息 - items_to_process = list(self.interest_dict.items()) - if not items_to_process: - # 没有消息时继续下一轮循环 - continue - - # 第三层检查:在处理消息前最后检查一次 - if self._disabled: - logger.info(f"[{self.stream_name}] 处理消息前检测到停用标志,退出") - break - - # 使用异步上下文管理器处理消息 - try: - async with global_prompt_manager.async_message_scope( - self.chat_stream.context.get_template_name() - ): - # 在上下文内部再次检查取消状态 - if self._disabled: - logger.info(f"[{self.stream_name}] 在处理上下文中检测到停止信号,退出") - break - - semaphore = asyncio.Semaphore(5) - - async def process_and_acquire( - msg_id, message, interest_value, is_mentioned, semaphore=semaphore - ): - """处理单个兴趣消息并管理信号量""" - async with semaphore: - try: - # 在处理每个消息前检查停止状态 - if self._disabled: - logger.debug( - f"[{self.stream_name}] 处理消息时检测到停用,跳过消息 {msg_id}" - ) - return - - # 处理消息 - self.adjust_reply_frequency() - - await self.normal_response( - message=message, - is_mentioned=is_mentioned, - interested_rate=interest_value * self.willing_amplifier, - ) - except asyncio.CancelledError: - logger.debug(f"[{self.stream_name}] 处理消息 {msg_id} 时被取消") - raise # 重新抛出取消异常 - except Exception as e: - logger.error(f"[{self.stream_name}] 处理兴趣消息{msg_id}时出错: {e}") - # 不打印完整traceback,避免日志污染 - finally: - # 无论如何都要清理消息 - self.interest_dict.pop(msg_id, None) - - tasks = [ - process_and_acquire(msg_id, message, interest_value, is_mentioned) - for msg_id, (message, interest_value, is_mentioned) in items_to_process - ] - - if tasks: - await asyncio.gather(*tasks, return_exceptions=True) - - except asyncio.CancelledError: - logger.info(f"[{self.stream_name}] 处理上下文时任务被取消") - break - except Exception as e: - logger.error(f"[{self.stream_name}] 处理上下文时出错: {e}") - # 出错后短暂等待,避免快速重试 - await asyncio.sleep(0.5) - - except asyncio.CancelledError: - logger.info(f"[{self.stream_name}] 主循环中任务被取消") - break - except Exception as e: - logger.error(f"[{self.stream_name}] 主循环出错: {e}") - # 出错后等待一秒再继续 - await asyncio.sleep(1.0) - - except asyncio.CancelledError: - logger.info(f"[{self.stream_name}] 兴趣监控任务被取消") - except Exception as e: - logger.error(f"[{self.stream_name}] 兴趣监控任务严重错误: {e}") - finally: - logger.debug(f"[{self.stream_name}] 兴趣监控任务结束") - # 改为实例方法, 移除 chat 参数 - async def normal_response(self, message: MessageRecv, is_mentioned: bool, interested_rate: float) -> None: + async def normal_response(self, message_data: dict, is_mentioned: bool, interested_rate: float) -> None: """ 处理接收到的消息。 在"兴趣"模式下,判断是否回复并生成内容。 @@ -396,22 +345,23 @@ class NormalChat: ) # 如果被提及,且开启了提及必回复,则基础概率为1,否则需要意愿判断 # 意愿管理器:设置当前message信息 - willing_manager.setup(message, self.chat_stream, is_mentioned, interested_rate) + willing_manager.setup(message_data, self.chat_stream) + # TODO: willing_manager 也需要修改以接收字典 # 获取回复概率 # is_willing = False # 仅在未被提及或基础概率不为1时查询意愿概率 if reply_probability < 1: # 简化逻辑,如果未提及 (reply_probability 为 0),则获取意愿概率 # is_willing = True - reply_probability = await willing_manager.get_reply_probability(message.message_info.message_id) + reply_probability = await willing_manager.get_reply_probability(message_data.get("message_id")) - 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"] - reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间 + additional_config = message_data.get("additional_config", {}) + if additional_config and "maimcore_reply_probability_gain" in additional_config: + reply_probability += additional_config["maimcore_reply_probability_gain"] + reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间 # 处理表情包 - if message.is_emoji or message.is_picid: + if message_data.get("is_emoji") or message_data.get("is_picid"): reply_probability = 0 # 应用疲劳期回复频率调整 @@ -427,53 +377,50 @@ class NormalChat: # 打印消息信息 mes_name = self.chat_stream.group_info.group_name if self.chat_stream.group_info else "私聊" - # current_time = time.strftime("%H:%M:%S", time.localtime(message.message_info.time)) - # 使用 self.stream_id - # willing_log = f"[激活值:{await willing_manager.get_willing(self.stream_id):.2f}]" if is_willing else "" if reply_probability > 0.1: logger.info( f"[{mes_name}]" - f"{message.message_info.user_info.user_nickname}:" # 使用 self.chat_stream - f"{message.processed_plain_text}[兴趣:{interested_rate:.2f}][回复概率:{reply_probability * 100:.1f}%]" + f"{message_data.get('user_nickname')}:" + f"{message_data.get('processed_plain_text')}[兴趣:{interested_rate:.2f}][回复概率:{reply_probability * 100:.1f}%]" ) do_reply = False response_set = None # 初始化 response_set if random() < reply_probability: with Timer("获取回复", timing_results): - await willing_manager.before_generate_reply_handle(message.message_info.message_id) - do_reply = await self.reply_one_message(message) + await willing_manager.before_generate_reply_handle(message_data.get("message_id")) + do_reply = await self.reply_one_message(message_data) response_set = do_reply if do_reply else None # 输出性能计时结果 if do_reply and response_set: # 确保 response_set 不是 None timing_str = " | ".join([f"{step}: {duration:.2f}秒" for step, duration in timing_results.items()]) - trigger_msg = message.processed_plain_text + trigger_msg = message_data.get("processed_plain_text") response_msg = " ".join([item[1] for item in response_set if item[0] == "text"]) logger.info( f"[{self.stream_name}]回复消息: {trigger_msg[:30]}... | 回复内容: {response_msg[:30]}... | 计时: {timing_str}" ) - await willing_manager.after_generate_reply_handle(message.message_info.message_id) + await willing_manager.after_generate_reply_handle(message_data.get("message_id")) elif not do_reply: # 不回复处理 - await willing_manager.not_reply_handle(message.message_info.message_id) + await willing_manager.not_reply_handle(message_data.get("message_id")) # 意愿管理器:注销当前message信息 (无论是否回复,只要处理过就删除) - willing_manager.delete(message.message_info.message_id) + willing_manager.delete(message_data.get("message_id")) async def _generate_normal_response( - self, message: MessageRecv, available_actions: Optional[list] + self, message_data: dict, available_actions: Optional[list] ) -> Optional[list]: """生成普通回复""" try: person_info_manager = get_person_info_manager() person_id = person_info_manager.get_person_id( - message.chat_stream.user_info.platform, message.chat_stream.user_info.user_id + message_data.get("chat_info_platform"), message_data.get("user_id") ) person_name = await person_info_manager.get_value(person_id, "person_name") - reply_to_str = f"{person_name}:{message.processed_plain_text}" + reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}" success, reply_set = await generator_api.generate_reply( - chat_stream=message.chat_stream, + chat_stream=self.chat_stream, reply_to=reply_to_str, available_actions=available_actions, enable_tool=global_config.tool.enable_in_normal_chat, @@ -481,7 +428,7 @@ class NormalChat: ) if not success or not reply_set: - logger.info(f"对 {message.processed_plain_text} 的回复生成失败") + logger.info(f"对 {message_data.get('processed_plain_text')} 的回复生成失败") return None return reply_set @@ -490,7 +437,7 @@ class NormalChat: logger.error(f"[{self.stream_name}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") return None - async def _plan_and_execute_actions(self, message: MessageRecv, thinking_id: str) -> Optional[dict]: + async def _plan_and_execute_actions(self, message_data: dict, thinking_id: str) -> Optional[dict]: """规划和执行额外动作""" no_action = { "action_result": { @@ -539,7 +486,7 @@ class NormalChat: return no_action # 执行额外的动作(不影响回复生成) - action_result = await self._execute_action(action_type, action_data, message, thinking_id) + action_result = await self._execute_action(action_type, action_data, message_data, thinking_id) if action_result is not None: logger.info(f"[{self.stream_name}] 额外动作 {action_type} 执行完成") else: @@ -556,17 +503,17 @@ class NormalChat: logger.error(f"[{self.stream_name}] Planner执行失败: {e}") return no_action - async def reply_one_message(self, message: MessageRecv) -> None: + async def reply_one_message(self, message_data: dict) -> None: # 回复前处理 await self.relationship_builder.build_relation() - thinking_id = await self._create_thinking_message(message) + thinking_id = await self._create_thinking_message(message_data) # 如果启用planner,预先修改可用actions(避免在并行任务中重复调用) available_actions = None if self.enable_planner: try: - await self.action_modifier.modify_actions(mode="normal", message_content=message.processed_plain_text) + await self.action_modifier.modify_actions(mode="normal", message_content=message_data.get("processed_plain_text")) available_actions = self.action_manager.get_using_actions_for_mode("normal") except Exception as e: logger.warning(f"[{self.stream_name}] 获取available_actions失败: {e}") @@ -576,8 +523,8 @@ class NormalChat: self.action_type = None # 初始化动作类型 self.is_parallel_action = False # 初始化并行动作标志 - gen_task = asyncio.create_task(self._generate_normal_response(message, available_actions)) - plan_task = asyncio.create_task(self._plan_and_execute_actions(message, thinking_id)) + gen_task = asyncio.create_task(self._generate_normal_response(message_data, available_actions)) + plan_task = asyncio.create_task(self._plan_and_execute_actions(message_data, thinking_id)) try: gather_timeout = global_config.chat.thinking_timeout @@ -661,7 +608,7 @@ class NormalChat: return False # 发送回复 (不再需要传入 chat) - first_bot_msg = await self._add_messages_to_manager(message, reply_texts, thinking_id) + first_bot_msg = await self._add_messages_to_manager(message_data, reply_texts, thinking_id) # 检查 first_bot_msg 是否为 None (例如思考消息已被移除的情况) if first_bot_msg: @@ -670,13 +617,13 @@ class NormalChat: # 记录回复信息到最近回复列表中 reply_info = { "time": time.time(), - "user_message": message.processed_plain_text, + "user_message": message_data.get("processed_plain_text"), "user_info": { - "user_id": message.message_info.user_info.user_id, - "user_nickname": message.message_info.user_info.user_nickname, + "user_id": message_data.get("user_id"), + "user_nickname": message_data.get("user_nickname"), }, "response": response_set, - "is_reference_reply": message.reply is not None, # 判断是否为引用回复 + "is_reference_reply": message_data.get("reply") is not None, # 判断是否为引用回复 } self.recent_replies.append(reply_info) # 保持最近回复历史在限定数量内 @@ -688,8 +635,6 @@ class NormalChat: async def start_chat(self): """启动聊天任务。""" - logger.debug(f"[{self.stream_name}] 开始启动聊天任务") - # 重置停用标志 self._disabled = False @@ -701,104 +646,90 @@ class NormalChat: # 清理可能存在的已完成任务引用 if self._chat_task and self._chat_task.done(): self._chat_task = None + if self._priority_chat_task and self._priority_chat_task.done(): + self._priority_chat_task = None try: logger.info(f"[{self.stream_name}] 创建新的聊天轮询任务,模式: {self.reply_mode}") + if self.reply_mode == "priority": - polling_task_send = asyncio.create_task(self._priority_chat_loop()) - polling_task_recv = asyncio.create_task(self._priority_chat_loop_add_message()) - print("555") - polling_task = asyncio.gather(polling_task_send, polling_task_recv) - print("666") + # Start producer loop + producer_task = asyncio.create_task(self._priority_message_producer_loop()) + self._chat_task = producer_task + self._chat_task.add_done_callback(lambda t: self._handle_task_completion(t, "priority_producer")) - else: # 默认或 "interest" 模式 - polling_task = asyncio.create_task(self._reply_interested_message()) + # Start consumer loop + consumer_task = asyncio.create_task(self._priority_chat_loop()) + self._priority_chat_task = consumer_task + self._priority_chat_task.add_done_callback(lambda t: self._handle_task_completion(t, "priority_consumer")) + else: # Interest mode + polling_task = asyncio.create_task(self._interest_message_polling_loop()) + self._chat_task = polling_task + self._chat_task.add_done_callback(lambda t: self._handle_task_completion(t, "interest_polling")) - # 设置回调 - polling_task.add_done_callback(lambda t: self._handle_task_completion(t)) - - # 保存任务引用 - self._chat_task = polling_task + self.running = True logger.debug(f"[{self.stream_name}] 聊天任务启动完成") except Exception as e: logger.error(f"[{self.stream_name}] 启动聊天任务失败: {e}") self._chat_task = None + self._priority_chat_task = None raise - def _handle_task_completion(self, task: asyncio.Task): + def _handle_task_completion(self, task: asyncio.Task, task_name: str = "unknown"): """任务完成回调处理""" try: - # 简化回调逻辑,避免复杂的异常处理 - logger.debug(f"[{self.stream_name}] 任务完成回调被调用") + logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 完成回调被调用") - # 检查是否是我们管理的任务 - if task is not self._chat_task: - # 如果已经不是当前任务(可能在stop_chat中已被清空),直接返回 - logger.debug(f"[{self.stream_name}] 回调的任务不是当前管理的任务") + if task is self._chat_task: + self._chat_task = None + elif task is self._priority_chat_task: + self._priority_chat_task = None + else: + logger.debug(f"[{self.stream_name}] 回调的任务 '{task_name}' 不是当前管理的任务") return - # 清理任务引用 - self._chat_task = None - logger.debug(f"[{self.stream_name}] 任务引用已清理") + logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 引用已清理") - # 简单记录任务状态,不进行复杂处理 if task.cancelled(): - logger.debug(f"[{self.stream_name}] 任务已取消") + logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 已取消") elif task.done(): - try: - # 尝试获取异常,但不抛出 - exc = task.exception() - if exc: - logger.error(f"[{self.stream_name}] 任务异常: {type(exc).__name__}: {exc}", exc_info=exc) - else: - logger.debug(f"[{self.stream_name}] 任务正常完成") - except Exception as e: - # 获取异常时也可能出错,静默处理 - logger.debug(f"[{self.stream_name}] 获取任务异常时出错: {e}") + exc = task.exception() + if exc: + logger.error(f"[{self.stream_name}] 任务 '{task_name}' 异常: {type(exc).__name__}: {exc}", exc_info=exc) + else: + logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 正常完成") except Exception as e: - # 回调函数中的任何异常都要捕获,避免影响系统 logger.error(f"[{self.stream_name}] 任务完成回调处理出错: {e}") - # 确保任务引用被清理 self._chat_task = None + self._priority_chat_task = None # 改为实例方法, 移除 stream_id 参数 async def stop_chat(self): """停止当前实例的兴趣监控任务。""" logger.debug(f"[{self.stream_name}] 开始停止聊天任务") - # 立即设置停用标志,防止新任务启动 self._disabled = True - # 如果没有运行中的任务,直接返回 - if not self._chat_task or self._chat_task.done(): - logger.debug(f"[{self.stream_name}] 没有运行中的任务,直接完成停止") - self._chat_task = None - return + if self._chat_task and not self._chat_task.done(): + self._chat_task.cancel() + if self._priority_chat_task and not self._priority_chat_task.done(): + self._priority_chat_task.cancel() - # 保存任务引用并立即清空,避免回调中的循环引用 - task_to_cancel = self._chat_task self._chat_task = None + self._priority_chat_task = None - logger.debug(f"[{self.stream_name}] 取消聊天任务") - - # 尝试优雅取消任务 - task_to_cancel.cancel() - - # 异步清理思考消息,不阻塞当前流程 asyncio.create_task(self._cleanup_thinking_messages_async()) async def _cleanup_thinking_messages_async(self): """异步清理思考消息,避免阻塞主流程""" try: - # 添加短暂延迟,让任务有时间响应取消 await asyncio.sleep(0.1) container = await message_manager.get_container(self.stream_id) if container: - # 查找并移除所有 MessageThinking 类型的消息 thinking_messages = [msg for msg in container.messages[:] if isinstance(msg, MessageThinking)] if thinking_messages: for msg in thinking_messages: @@ -806,7 +737,6 @@ class NormalChat: logger.info(f"[{self.stream_name}] 清理了 {len(thinking_messages)} 条未处理的思考消息。") except Exception as e: logger.error(f"[{self.stream_name}] 异步清理思考消息时出错: {e}") - # 不打印完整栈跟踪,避免日志污染 def adjust_reply_frequency(self): """ @@ -879,7 +809,7 @@ class NormalChat: ) async def _execute_action( - self, action_type: str, action_data: dict, message: MessageRecv, thinking_id: str + self, action_type: str, action_data: dict, message_data: dict, thinking_id: str ) -> Optional[bool]: """执行具体的动作,只返回执行成功与否""" try: diff --git a/src/main.py b/src/main.py index 64129814e..bd9005394 100644 --- a/src/main.py +++ b/src/main.py @@ -23,9 +23,6 @@ from rich.traceback import install # 导入新的插件管理器 from src.plugin_system.core.plugin_manager import plugin_manager -# 导入HFC性能记录器用于日志清理 -from src.chat.focus_chat.hfc_performance_logger import HFCPerformanceLogger - # 导入消息API和traceback模块 from src.common.message import get_global_api @@ -69,11 +66,6 @@ class MainSystem: """初始化其他组件""" init_start_time = time.time() - # 清理HFC旧日志文件(保持目录大小在50MB以内) - logger.info("开始清理HFC旧日志文件...") - HFCPerformanceLogger.cleanup_old_logs(max_size_mb=50.0) - logger.info("HFC日志清理完成") - # 添加在线时间统计任务 await async_task_manager.add_task(OnlineTimeRecordTask()) diff --git a/src/mais4u/mais4u_chat/s4u_mood_manager.py b/src/mais4u/mais4u_chat/s4u_mood_manager.py index f9846c9be..6b9704e94 100644 --- a/src/mais4u/mais4u_chat/s4u_mood_manager.py +++ b/src/mais4u/mais4u_chat/s4u_mood_manager.py @@ -38,7 +38,7 @@ def init_prompt(): 现在,发送了消息,引起了你的注意,你对其进行了阅读和思考,请你输出一句话描述你新的情绪状态,不要输出任何其他内容 请只输出情绪状态,不要输出其他内容: """, - "change_mood_prompt", + "change_mood_prompt_vtb", ) Prompt( """ @@ -51,7 +51,7 @@ def init_prompt(): 距离你上次关注直播间消息已经过去了一段时间,你冷静了下来,请你输出一句话描述你现在的情绪状态 请只输出情绪状态,不要输出其他内容: """, - "regress_mood_prompt", + "regress_mood_prompt_vtb", ) Prompt( """ @@ -183,7 +183,7 @@ class ChatMood: async def _update_text_mood(): prompt = await global_prompt_manager.format_prompt( - "change_mood_prompt", + "change_mood_prompt_vtb", chat_talking_prompt=chat_talking_prompt, indentify_block=indentify_block, mood_state=self.mood_state, @@ -257,7 +257,7 @@ class ChatMood: async def _regress_text_mood(): prompt = await global_prompt_manager.format_prompt( - "regress_mood_prompt", + "regress_mood_prompt_vtb", chat_talking_prompt=chat_talking_prompt, indentify_block=indentify_block, mood_state=self.mood_state, diff --git a/src/plugin_system/base/base_action.py b/src/plugin_system/base/base_action.py index cc5cbc261..2f34c3570 100644 --- a/src/plugin_system/base/base_action.py +++ b/src/plugin_system/base/base_action.py @@ -33,7 +33,6 @@ class BaseAction(ABC): thinking_id: str, chat_stream=None, log_prefix: str = "", - shutting_down: bool = False, plugin_config: dict = None, **kwargs, ): @@ -59,7 +58,6 @@ class BaseAction(ABC): self.cycle_timers = cycle_timers self.thinking_id = thinking_id self.log_prefix = log_prefix - self.shutting_down = shutting_down # 保存插件配置 self.plugin_config = plugin_config or {} From a0efb89d987cda74393de26178eee601f6e4115f Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Fri, 11 Jul 2025 21:51:47 +0800 Subject: [PATCH 06/21] =?UTF-8?q?feat=EF=BC=9A=E5=B0=86normal=E6=8A=BD?= =?UTF-8?q?=E8=B1=A1=E4=B8=BA=E5=BE=AA=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/focus_chat/heartFC_chat.py | 533 +++++++----------- src/chat/message_receive/message.py | 52 ++ src/chat/message_receive/storage.py | 15 + src/chat/normal_chat/priority_manager.py | 38 +- .../normal_chat/willing/willing_manager.py | 12 +- src/common/database/database_model.py | 8 + 6 files changed, 292 insertions(+), 366 deletions(-) diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index 08008bfe9..872a800a8 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -14,11 +14,26 @@ from src.chat.planner_actions.planner import ActionPlanner from src.chat.planner_actions.action_modifier import ActionModifier from src.chat.planner_actions.action_manager import ActionManager from src.config.config import global_config -from src.chat.focus_chat.hfc_performance_logger import HFCPerformanceLogger from src.person_info.relationship_builder_manager import relationship_builder_manager from src.chat.focus_chat.hfc_utils import CycleDetail +ERROR_LOOP_INFO = { + "loop_plan_info": { + "action_result": { + "action_type": "error", + "action_data": {}, + "reasoning": "循环处理失败", + }, + }, + "loop_action_info": { + "action_taken": False, + "reply_text": "", + "command": "", + "taken_time": time.time(), + }, +} + install(extra_lines=3) # 注释:原来的动作修改超时常量已移除,因为改为顺序执行 @@ -66,17 +81,14 @@ class HeartFChatting: self.action_planner = ActionPlanner(chat_id=self.stream_id, action_manager=self.action_manager) self.action_modifier = ActionModifier(action_manager=self.action_manager, chat_id=self.stream_id) - self._processing_lock = asyncio.Lock() - # 循环控制内部状态 - self._loop_active: bool = False # 循环是否正在运行 + self.running: bool = False self._loop_task: Optional[asyncio.Task] = None # 主循环任务 # 添加循环信息管理相关的属性 self._cycle_counter = 0 self._cycle_history: Deque[CycleDetail] = deque(maxlen=10) # 保留最近10个循环的信息 self._current_cycle_detail: Optional[CycleDetail] = None - self._shutting_down: bool = False # 关闭标志位 # 存储回调函数 self.on_stop_focus_chat = on_stop_focus_chat @@ -84,11 +96,6 @@ class HeartFChatting: self.reply_timeout_count = 0 self.plan_timeout_count = 0 - # 初始化性能记录器 - # 如果没有指定版本号,则使用全局版本管理器的版本号 - - self.performance_logger = HFCPerformanceLogger(chat_id) - logger.info( f"{self.log_prefix} HeartFChatting 初始化完成,消息疲惫阈值: {self._message_threshold}条(基于exit_focus_threshold={global_config.chat.exit_focus_threshold}计算,仅在auto模式下生效)" ) @@ -97,36 +104,23 @@ class HeartFChatting: """检查是否需要启动主循环,如果未激活则启动。""" # 如果循环已经激活,直接返回 - if self._loop_active: + if self.running: logger.debug(f"{self.log_prefix} HeartFChatting 已激活,无需重复启动") return try: # 重置消息计数器,开始新的focus会话 self.reset_message_count() - # 标记为活动状态,防止重复启动 - self._loop_active = True + self.running = True - # 检查是否已有任务在运行(理论上不应该,因为 _loop_active=False) - if self._loop_task and not self._loop_task.done(): - logger.warning(f"{self.log_prefix} 发现之前的循环任务仍在运行(不符合预期)。取消旧任务。") - self._loop_task.cancel() - try: - # 等待旧任务确实被取消 - await asyncio.wait_for(self._loop_task, timeout=5.0) - except Exception as e: - logger.warning(f"{self.log_prefix} 等待旧任务取消时出错: {e}") - self._loop_task = None # 清理旧任务引用 - - logger.debug(f"{self.log_prefix} 创建新的 HeartFChatting 主循环任务") - self._loop_task = asyncio.create_task(self._run_focus_chat()) + self._loop_task = asyncio.create_task(self._main_chat_loop()) self._loop_task.add_done_callback(self._handle_loop_completion) - logger.debug(f"{self.log_prefix} HeartFChatting 启动完成") + logger.info(f"{self.log_prefix} HeartFChatting 启动完成") except Exception as e: # 启动失败时重置状态 - self._loop_active = False + self.running = False self._loop_task = None logger.error(f"{self.log_prefix} HeartFChatting 启动失败: {e}") raise @@ -143,264 +137,151 @@ class HeartFChatting: except asyncio.CancelledError: logger.info(f"{self.log_prefix} HeartFChatting: 脱离了聊天(任务取消)") finally: - self._loop_active = False + self.running = False self._loop_task = None - if self._processing_lock.locked(): - logger.warning(f"{self.log_prefix} HeartFChatting: 处理锁在循环结束时仍被锁定,强制释放。") - self._processing_lock.release() + + def start_cycle(self): + self._cycle_counter += 1 + self._current_cycle_detail = CycleDetail(self._cycle_counter) + self._current_cycle_detail.prefix = self.log_prefix + thinking_id = "tid" + str(round(time.time(), 2)) + self._current_cycle_detail.set_thinking_id(thinking_id) + cycle_timers = {} + return cycle_timers, thinking_id + + def end_cycle(self,loop_info,cycle_timers): + self._current_cycle_detail.set_loop_info(loop_info) + self.loop_info.add_loop_info(self._current_cycle_detail) + self._current_cycle_detail.timers = cycle_timers + self._current_cycle_detail.complete_cycle() + self._cycle_history.append(self._current_cycle_detail) + + def print_cycle_info(self,cycle_timers): + # 记录循环信息和计时器结果 + timer_strings = [] + for name, elapsed in cycle_timers.items(): + formatted_time = f"{elapsed * 1000:.2f}毫秒" if elapsed < 1 else f"{elapsed:.2f}秒" + timer_strings.append(f"{name}: {formatted_time}") - async def _run_focus_chat(self): - """主循环,持续进行计划并可能回复消息,直到被外部取消。""" - try: - while True: # 主循环 - logger.debug(f"{self.log_prefix} 开始第{self._cycle_counter}次循环") - - # 检查关闭标志 - if self._shutting_down: - logger.info(f"{self.log_prefix} 检测到关闭标志,退出 Focus Chat 循环。") - break - - # 创建新的循环信息 - self._cycle_counter += 1 - self._current_cycle_detail = CycleDetail(self._cycle_counter) - self._current_cycle_detail.prefix = self.log_prefix - - # 初始化周期状态 - cycle_timers = {} - - # 执行规划和处理阶段 - try: - async with self._get_cycle_context(): - thinking_id = "tid" + str(round(time.time(), 2)) - self._current_cycle_detail.set_thinking_id(thinking_id) - - # 使用异步上下文管理器处理消息 - try: - async with global_prompt_manager.async_message_scope( - self.chat_stream.context.get_template_name() - ): - # 在上下文内部检查关闭状态 - if self._shutting_down: - logger.info(f"{self.log_prefix} 在处理上下文中检测到关闭信号,退出") - break - - logger.debug(f"模板 {self.chat_stream.context.get_template_name()}") - loop_info = await self._observe_process_plan_action_loop(cycle_timers, thinking_id) - - if loop_info["loop_action_info"]["command"] == "stop_focus_chat": - logger.info(f"{self.log_prefix} 麦麦决定停止专注聊天") - - # 如果设置了回调函数,则调用它 - if self.on_stop_focus_chat: - try: - await self.on_stop_focus_chat() - logger.info(f"{self.log_prefix} 成功调用回调函数处理停止专注聊天") - except Exception as e: - logger.error(f"{self.log_prefix} 调用停止专注聊天回调函数时出错: {e}") - logger.error(traceback.format_exc()) - break - - except asyncio.CancelledError: - logger.info(f"{self.log_prefix} 处理上下文时任务被取消") - break - except Exception as e: - logger.error(f"{self.log_prefix} 处理上下文时出错: {e}") - # 为当前循环设置错误状态,防止后续重复报错 - error_loop_info = { - "loop_plan_info": { - "action_result": { - "action_type": "error", - "action_data": {}, - }, - }, - "loop_action_info": { - "action_taken": False, - "reply_text": "", - "command": "", - "taken_time": time.time(), - }, - } - self._current_cycle_detail.set_loop_info(error_loop_info) - self._current_cycle_detail.complete_cycle() - - # 上下文处理失败,跳过当前循环 - await asyncio.sleep(1) - continue - - self._current_cycle_detail.set_loop_info(loop_info) - - self.loop_info.add_loop_info(self._current_cycle_detail) - - self._current_cycle_detail.timers = cycle_timers - - # 完成当前循环并保存历史 - self._current_cycle_detail.complete_cycle() - self._cycle_history.append(self._current_cycle_detail) - - # 记录循环信息和计时器结果 - timer_strings = [] - for name, elapsed in cycle_timers.items(): - formatted_time = f"{elapsed * 1000:.2f}毫秒" if elapsed < 1 else f"{elapsed:.2f}秒" - timer_strings.append(f"{name}: {formatted_time}") - - logger.info( - f"{self.log_prefix} 第{self._current_cycle_detail.cycle_id}次思考," - f"耗时: {self._current_cycle_detail.end_time - self._current_cycle_detail.start_time:.1f}秒, " - f"选择动作: {self._current_cycle_detail.loop_plan_info.get('action_result', {}).get('action_type', '未知动作')}" - + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") - ) - - # 记录性能数据 - try: - action_result = self._current_cycle_detail.loop_plan_info.get("action_result", {}) - cycle_performance_data = { - "cycle_id": self._current_cycle_detail.cycle_id, - "action_type": action_result.get("action_type", "unknown"), - "total_time": self._current_cycle_detail.end_time - self._current_cycle_detail.start_time, - "step_times": cycle_timers.copy(), - "reasoning": action_result.get("reasoning", ""), - "success": self._current_cycle_detail.loop_action_info.get("action_taken", False), - } - self.performance_logger.record_cycle(cycle_performance_data) - except Exception as perf_e: - logger.warning(f"{self.log_prefix} 记录性能数据失败: {perf_e}") - - await asyncio.sleep(global_config.focus_chat.think_interval) - - except asyncio.CancelledError: - logger.info(f"{self.log_prefix} 循环处理时任务被取消") - break - except Exception as e: - logger.error(f"{self.log_prefix} 循环处理时出错: {e}") - logger.error(traceback.format_exc()) - - # 如果_current_cycle_detail存在但未完成,为其设置错误状态 - if self._current_cycle_detail and not hasattr(self._current_cycle_detail, "end_time"): - error_loop_info = { - "loop_plan_info": { - "action_result": { - "action_type": "error", - "action_data": {}, - "reasoning": f"循环处理失败: {e}", - }, - }, - "loop_action_info": { - "action_taken": False, - "reply_text": "", - "command": "", - "taken_time": time.time(), - }, - } - try: - self._current_cycle_detail.set_loop_info(error_loop_info) - self._current_cycle_detail.complete_cycle() - except Exception as inner_e: - logger.error(f"{self.log_prefix} 设置错误状态时出错: {inner_e}") - - await asyncio.sleep(1) # 出错后等待一秒再继续 - - except asyncio.CancelledError: - # 设置了关闭标志位后被取消是正常流程 - if not self._shutting_down: - logger.warning(f"{self.log_prefix} 麦麦Focus聊天模式意外被取消") - else: - logger.info(f"{self.log_prefix} 麦麦已离开Focus聊天模式") - except Exception as e: - logger.error(f"{self.log_prefix} 麦麦Focus聊天模式意外错误: {e}") - print(traceback.format_exc()) - - @contextlib.asynccontextmanager - async def _get_cycle_context(self): - """ - 循环周期的上下文管理器 - - 用于确保资源的正确获取和释放: - 1. 获取处理锁 - 2. 执行操作 - 3. 释放锁 - """ - acquired = False - try: - await self._processing_lock.acquire() - acquired = True - yield acquired - finally: - if acquired and self._processing_lock.locked(): - self._processing_lock.release() - - async def _observe_process_plan_action_loop(self, cycle_timers: dict, thinking_id: str) -> dict: - try: - loop_start_time = time.time() - await self.loop_info.observe() - - await self.relationship_builder.build_relation() - - # 顺序执行调整动作和处理器阶段 - # 第一步:动作修改 - with Timer("动作修改", cycle_timers): - try: - # 调用完整的动作修改流程 - await self.action_modifier.modify_actions( - loop_info=self.loop_info, - mode="focus", - ) - except Exception as e: - logger.error(f"{self.log_prefix} 动作修改失败: {e}") - # 继续执行,不中断流程 - - with Timer("规划器", cycle_timers): - plan_result = await self.action_planner.plan() - - loop_plan_info = { - "action_result": plan_result.get("action_result", {}), - } - - action_type, action_data, reasoning = ( - plan_result.get("action_result", {}).get("action_type", "error"), - plan_result.get("action_result", {}).get("action_data", {}), - plan_result.get("action_result", {}).get("reasoning", "未提供理由"), + logger.info( + f"{self.log_prefix} 第{self._current_cycle_detail.cycle_id}次思考," + f"耗时: {self._current_cycle_detail.end_time - self._current_cycle_detail.start_time:.1f}秒, " + f"选择动作: {self._current_cycle_detail.loop_plan_info.get('action_result', {}).get('action_type', '未知动作')}" + + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") ) + - action_data["loop_start_time"] = loop_start_time + + async def _focus_mode_loopbody(self): + logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次循环") - if action_type == "reply": - action_str = "回复" - elif action_type == "no_reply": - action_str = "不回复" - else: - action_str = action_type + # 创建新的循环信息 + cycle_timers, thinking_id = self.start_cycle() - logger.debug(f"{self.log_prefix} 麦麦想要:'{action_str}',理由是:{reasoning}") + # 执行规划和处理阶段 + try: + async with global_prompt_manager.async_message_scope( + self.chat_stream.context.get_template_name() + ): - # 动作执行计时 - with Timer("动作执行", cycle_timers): - success, reply_text, command = await self._handle_action( - action_type, reasoning, action_data, cycle_timers, thinking_id + loop_start_time = time.time() + await self.loop_info.observe() + await self.relationship_builder.build_relation() + + # 第一步:动作修改 + with Timer("动作修改", cycle_timers): + try: + await self.action_modifier.modify_actions( + loop_info=self.loop_info, + mode="focus", + ) + except Exception as e: + logger.error(f"{self.log_prefix} 动作修改失败: {e}") + + with Timer("规划器", cycle_timers): + plan_result = await self.action_planner.plan() + + action_result = plan_result.get("action_result", {}) + action_type, action_data, reasoning = ( + action_result.get("action_type", "error"), + action_result.get("action_data", {}), + action_result.get("reasoning", "未提供理由"), ) - loop_action_info = { - "action_taken": success, - "reply_text": reply_text, - "command": command, - "taken_time": time.time(), + action_data["loop_start_time"] = loop_start_time + + # 动作执行计时 + with Timer("动作执行", cycle_timers): + success, reply_text, command = await self._handle_action( + action_type, reasoning, action_data, cycle_timers, thinking_id + ) + + loop_info = { + "loop_plan_info": { + "action_result": plan_result.get("action_result", {}), + }, + "loop_action_info": { + "action_taken": success, + "reply_text": reply_text, + "command": command, + "taken_time": time.time(), + }, } - loop_info = { - "loop_plan_info": loop_plan_info, - "loop_action_info": loop_action_info, - } + if loop_info["loop_action_info"]["command"] == "stop_focus_chat": + logger.info(f"{self.log_prefix} 麦麦决定停止专注聊天") + return False + #停止该聊天模式的循环 - return loop_info + self.end_cycle(loop_info,cycle_timers) + self.print_cycle_info(cycle_timers) + await asyncio.sleep(global_config.focus_chat.think_interval) + + return True + + + except asyncio.CancelledError: + logger.info(f"{self.log_prefix} focus循环任务被取消") + return False except Exception as e: - logger.error(f"{self.log_prefix} FOCUS聊天处理失败: {e}") + logger.error(f"{self.log_prefix} 循环处理时出错: {e}") logger.error(traceback.format_exc()) - return { - "loop_plan_info": { - "action_result": {"action_type": "error", "action_data": {}, "reasoning": f"处理失败: {e}"}, - }, - "loop_action_info": {"action_taken": False, "reply_text": "", "command": "", "taken_time": time.time()}, - } + + # 如果_current_cycle_detail存在但未完成,为其设置错误状态 + if self._current_cycle_detail and not hasattr(self._current_cycle_detail, "end_time"): + error_loop_info = ERROR_LOOP_INFO + try: + self._current_cycle_detail.set_loop_info(error_loop_info) + self._current_cycle_detail.complete_cycle() + except Exception as inner_e: + logger.error(f"{self.log_prefix} 设置错误状态时出错: {inner_e}") + + await asyncio.sleep(1) # 出错后等待一秒再继续\ + return False + + + async def _main_chat_loop(self): + """主循环,持续进行计划并可能回复消息,直到被外部取消。""" + try: + loop_mode = "focus" + loop_mode_loopbody = self._focus_mode_loopbody + + + while self.running: # 主循环 + success = await loop_mode_loopbody() + if not success: + break + + logger.info(f"{self.log_prefix} 麦麦已强制离开 {loop_mode} 聊天模式") + + + except asyncio.CancelledError: + # 设置了关闭标志位后被取消是正常流程 + logger.info(f"{self.log_prefix} 麦麦已强制离开 {loop_mode} 聊天模式") + except Exception as e: + logger.error(f"{self.log_prefix} 麦麦 {loop_mode} 聊天模式意外错误: {e}") + print(traceback.format_exc()) async def _handle_action( self, @@ -434,7 +315,6 @@ class HeartFChatting: thinking_id=thinking_id, chat_stream=self.chat_stream, log_prefix=self.log_prefix, - shutting_down=self._shutting_down, ) except Exception as e: logger.error(f"{self.log_prefix} 创建动作处理器时出错: {e}") @@ -453,40 +333,16 @@ class HeartFChatting: success, reply_text = result command = "" - # 检查action_data中是否有系统命令,优先使用系统命令 - if "_system_command" in action_data: - command = action_data["_system_command"] - logger.debug(f"{self.log_prefix} 从action_data中获取系统命令: {command}") - - # 新增:消息计数和疲惫检查 - if action == "reply" and success: - self._message_count += 1 - current_threshold = self._get_current_fatigue_threshold() - logger.info( - f"{self.log_prefix} 已发送第 {self._message_count} 条消息(动态阈值: {current_threshold}, exit_focus_threshold: {global_config.chat.exit_focus_threshold})" - ) - - # 检查是否达到疲惫阈值(只有在auto模式下才会自动退出) - if ( - global_config.chat.chat_mode == "auto" - and self._message_count >= current_threshold - and not self._fatigue_triggered - ): - self._fatigue_triggered = True - logger.info( - f"{self.log_prefix} [auto模式] 已发送 {self._message_count} 条消息,达到疲惫阈值 {current_threshold},麦麦感到疲惫了,准备退出专注聊天模式" + command = self._count_reply_and_exit_focus_chat(action,success) + + if reply_text == "timeout": + self.reply_timeout_count += 1 + if self.reply_timeout_count > 5: + logger.warning( + f"[{self.log_prefix} ] 连续回复超时次数过多,{global_config.chat.thinking_timeout}秒 内大模型没有返回有效内容,请检查你的api是否速度过慢或配置错误。建议不要使用推理模型,推理模型生成速度过慢。或者尝试拉高thinking_timeout参数,这可能导致回复时间过长。" ) - # 设置系统命令,在下次循环检查时触发退出 - command = "stop_focus_chat" - else: - if reply_text == "timeout": - self.reply_timeout_count += 1 - if self.reply_timeout_count > 5: - logger.warning( - f"[{self.log_prefix} ] 连续回复超时次数过多,{global_config.chat.thinking_timeout}秒 内大模型没有返回有效内容,请检查你的api是否速度过慢或配置错误。建议不要使用推理模型,推理模型生成速度过慢。或者尝试拉高thinking_timeout参数,这可能导致回复时间过长。" - ) - logger.warning(f"{self.log_prefix} 回复生成超时{global_config.chat.thinking_timeout}s,已跳过") - return False, "", "" + logger.warning(f"{self.log_prefix} 回复生成超时{global_config.chat.thinking_timeout}s,已跳过") + return False, "", "" return success, reply_text, command @@ -494,6 +350,33 @@ class HeartFChatting: logger.error(f"{self.log_prefix} 处理{action}时出错: {e}") traceback.print_exc() return False, "", "" + + def _count_reply_and_exit_focus_chat(self,action,success): + # 新增:消息计数和疲惫检查 + if action == "reply" and success: + self._message_count += 1 + current_threshold = self._get_current_fatigue_threshold() + logger.info( + f"{self.log_prefix} 已发送第 {self._message_count} 条消息(动态阈值: {current_threshold}, exit_focus_threshold: {global_config.chat.exit_focus_threshold})" + ) + + # 检查是否达到疲惫阈值(只有在auto模式下才会自动退出) + if ( + global_config.chat.chat_mode == "auto" + and self._message_count >= current_threshold + and not self._fatigue_triggered + ): + self._fatigue_triggered = True + logger.info( + f"{self.log_prefix} [auto模式] 已发送 {self._message_count} 条消息,达到疲惫阈值 {current_threshold},麦麦感到疲惫了,准备退出专注聊天模式" + ) + # 设置系统命令,在下次循环检查时触发退出 + command = "stop_focus_chat" + + return command + return "" + + def _get_current_fatigue_threshold(self) -> int: """动态获取当前的疲惫阈值,基于exit_focus_threshold配置 @@ -503,19 +386,6 @@ class HeartFChatting: """ return max(10, int(30 / global_config.chat.exit_focus_threshold)) - def get_message_count_info(self) -> dict: - """获取消息计数信息 - - Returns: - dict: 包含消息计数信息的字典 - """ - current_threshold = self._get_current_fatigue_threshold() - return { - "current_count": self._message_count, - "threshold": current_threshold, - "fatigue_triggered": self._fatigue_triggered, - "remaining": max(0, current_threshold - self._message_count), - } def reset_message_count(self): """重置消息计数器(用于重新启动focus模式时)""" @@ -526,7 +396,7 @@ class HeartFChatting: async def shutdown(self): """优雅关闭HeartFChatting实例,取消活动循环任务""" logger.info(f"{self.log_prefix} 正在关闭HeartFChatting...") - self._shutting_down = True # <-- 在开始关闭时设置标志位 + self.running = False # <-- 在开始关闭时设置标志位 # 记录最终的消息统计 if self._message_count > 0: @@ -549,34 +419,11 @@ class HeartFChatting: logger.info(f"{self.log_prefix} 没有活动的HeartFChatting循环任务") # 清理状态 - self._loop_active = False + self.running = False self._loop_task = None - if self._processing_lock.locked(): - self._processing_lock.release() - logger.warning(f"{self.log_prefix} 已释放处理锁") - - # 完成性能统计 - try: - self.performance_logger.finalize_session() - logger.info(f"{self.log_prefix} 性能统计已完成") - except Exception as e: - logger.warning(f"{self.log_prefix} 完成性能统计时出错: {e}") # 重置消息计数器,为下次启动做准备 self.reset_message_count() logger.info(f"{self.log_prefix} HeartFChatting关闭完成") - def get_cycle_history(self, last_n: Optional[int] = None) -> List[Dict[str, Any]]: - """获取循环历史记录 - - 参数: - last_n: 获取最近n个循环的信息,如果为None则获取所有历史记录 - - 返回: - List[Dict[str, Any]]: 循环历史记录列表 - """ - history = list(self._cycle_history) - if last_n is not None: - history = history[-last_n:] - return [cycle.to_dict() for cycle in history] diff --git a/src/chat/message_receive/message.py b/src/chat/message_receive/message.py index 710d2525a..8cc06573c 100644 --- a/src/chat/message_receive/message.py +++ b/src/chat/message_receive/message.py @@ -441,3 +441,55 @@ class MessageSet: def __len__(self) -> int: return len(self.messages) + + +def message_recv_from_dict(message_dict: dict) -> MessageRecv: + return MessageRecv( + + message_dict + + ) + +def message_from_db_dict(db_dict: dict) -> MessageRecv: + """从数据库字典创建MessageRecv实例""" + # 转换扁平的数据库字典为嵌套结构 + message_info_dict = { + "platform": db_dict.get("chat_info_platform"), + "message_id": db_dict.get("message_id"), + "time": db_dict.get("time"), + "group_info": { + "platform": db_dict.get("chat_info_group_platform"), + "group_id": db_dict.get("chat_info_group_id"), + "group_name": db_dict.get("chat_info_group_name"), + }, + "user_info": { + "platform": db_dict.get("user_platform"), + "user_id": db_dict.get("user_id"), + "user_nickname": db_dict.get("user_nickname"), + "user_cardname": db_dict.get("user_cardname"), + }, + } + + processed_text = db_dict.get("processed_plain_text", "") + + # 构建 MessageRecv 需要的字典 + recv_dict = { + "message_info": message_info_dict, + "message_segment": {"type": "text", "data": processed_text}, # 从纯文本重建消息段 + "raw_message": None, # 数据库中未存储原始消息 + "processed_plain_text": processed_text, + "detailed_plain_text": db_dict.get("detailed_plain_text", ""), + } + + # 创建 MessageRecv 实例 + msg = MessageRecv(recv_dict) + + # 从数据库字典中填充其他可选字段 + msg.interest_value = db_dict.get("interest_value") + msg.is_mentioned = db_dict.get("is_mentioned") + msg.priority_mode = db_dict.get("priority_mode", "interest") + msg.priority_info = db_dict.get("priority_info") + msg.is_emoji = db_dict.get("is_emoji", False) + msg.is_picid = db_dict.get("is_picid", False) + + return msg \ No newline at end of file diff --git a/src/chat/message_receive/storage.py b/src/chat/message_receive/storage.py index 998e06f21..9524a2779 100644 --- a/src/chat/message_receive/storage.py +++ b/src/chat/message_receive/storage.py @@ -38,11 +38,21 @@ class MessageStorage: else: filtered_display_message = "" interest_value = 0 + is_mentioned = False reply_to = message.reply_to + priority_mode = "" + priority_info = {} + is_emoji = False + is_picid = False else: filtered_display_message = "" interest_value = message.interest_value + is_mentioned = message.is_mentioned reply_to = "" + priority_mode = message.priority_mode + priority_info = message.priority_info + is_emoji = message.is_emoji + is_picid = message.is_picid chat_info_dict = chat_stream.to_dict() user_info_dict = message.message_info.user_info.to_dict() @@ -61,6 +71,7 @@ class MessageStorage: chat_id=chat_stream.stream_id, # Flattened chat_info reply_to=reply_to, + is_mentioned=is_mentioned, chat_info_stream_id=chat_info_dict.get("stream_id"), chat_info_platform=chat_info_dict.get("platform"), chat_info_user_platform=user_info_from_chat.get("platform"), @@ -82,6 +93,10 @@ class MessageStorage: display_message=filtered_display_message, memorized_times=message.memorized_times, interest_value=interest_value, + priority_mode=priority_mode, + priority_info=priority_info, + is_emoji=is_emoji, + is_picid=is_picid, ) except Exception: logger.exception("存储消息失败") diff --git a/src/chat/normal_chat/priority_manager.py b/src/chat/normal_chat/priority_manager.py index 9e1ef76c2..facbecd23 100644 --- a/src/chat/normal_chat/priority_manager.py +++ b/src/chat/normal_chat/priority_manager.py @@ -1,8 +1,9 @@ import time import heapq import math +import json from typing import List, Dict, Optional -from ..message_receive.message import MessageRecv + from src.common.logger import get_logger logger = get_logger("normal_chat") @@ -11,8 +12,8 @@ logger = get_logger("normal_chat") class PrioritizedMessage: """带有优先级的消息对象""" - def __init__(self, message: MessageRecv, interest_scores: List[float], is_vip: bool = False): - self.message = message + def __init__(self, message_data: dict, interest_scores: List[float], is_vip: bool = False): + self.message_data = message_data self.arrival_time = time.time() self.interest_scores = interest_scores self.is_vip = is_vip @@ -38,25 +39,28 @@ class PriorityManager: 管理消息队列,根据优先级选择消息进行处理。 """ - def __init__(self, interest_dict: Dict[str, float], normal_queue_max_size: int = 5): + def __init__(self, normal_queue_max_size: int = 5): self.vip_queue: List[PrioritizedMessage] = [] # VIP 消息队列 (最大堆) self.normal_queue: List[PrioritizedMessage] = [] # 普通消息队列 (最大堆) - self.interest_dict = interest_dict if interest_dict is not None else {} self.normal_queue_max_size = normal_queue_max_size - def _get_interest_score(self, user_id: str) -> float: - """获取用户的兴趣分,默认为1.0""" - return self.interest_dict.get("interests", {}).get(user_id, 1.0) - - def add_message(self, message: MessageRecv, interest_score: Optional[float] = None): + def add_message(self, message_data: dict, interest_score: Optional[float] = None): """ 添加新消息到合适的队列中。 """ - user_id = message.message_info.user_info.user_id - is_vip = message.priority_info.get("message_type") == "vip" if message.priority_info else False - message_priority = message.priority_info.get("message_priority", 0.0) if message.priority_info else 0.0 + user_id = message_data.get("user_id") + + priority_info_raw = message_data.get("priority_info") + priority_info = {} + if isinstance(priority_info_raw, str): + priority_info = json.loads(priority_info_raw) + elif isinstance(priority_info_raw, dict): + priority_info = priority_info_raw - p_message = PrioritizedMessage(message, [interest_score, message_priority], is_vip) + is_vip = priority_info.get("message_type") == "vip" + message_priority = priority_info.get("message_priority", 0.0) + + p_message = PrioritizedMessage(message_data, [interest_score, message_priority], is_vip) if is_vip: heapq.heappush(self.vip_queue, p_message) @@ -75,7 +79,7 @@ class PriorityManager: f"消息来自普通用户 {user_id}, 已添加到普通队列. 当前普通队列长度: {len(self.normal_queue)}" ) - def get_highest_priority_message(self) -> Optional[MessageRecv]: + def get_highest_priority_message(self) -> Optional[dict]: """ 从VIP和普通队列中获取当前最高优先级的消息。 """ @@ -93,9 +97,9 @@ class PriorityManager: normal_msg = self.normal_queue[0] if self.normal_queue else None if vip_msg: - return heapq.heappop(self.vip_queue).message + return heapq.heappop(self.vip_queue).message_data elif normal_msg: - return heapq.heappop(self.normal_queue).message + return heapq.heappop(self.normal_queue).message_data else: return None diff --git a/src/chat/normal_chat/willing/willing_manager.py b/src/chat/normal_chat/willing/willing_manager.py index 0fa701f94..8b9191f74 100644 --- a/src/chat/normal_chat/willing/willing_manager.py +++ b/src/chat/normal_chat/willing/willing_manager.py @@ -91,19 +91,19 @@ class BaseWillingManager(ABC): self.lock = asyncio.Lock() self.logger = logger - def setup(self, message: MessageRecv, chat: ChatStream, is_mentioned_bot: bool, interested_rate: float): + def setup(self, message: dict, chat: ChatStream): person_id = PersonInfoManager.get_person_id(chat.platform, chat.user_info.user_id) - self.ongoing_messages[message.message_info.message_id] = WillingInfo( + self.ongoing_messages[message.get("message_id")] = WillingInfo( message=message, chat=chat, person_info_manager=get_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, - is_picid=message.is_picid, - interested_rate=interested_rate, + is_mentioned_bot=message.get("is_mentioned_bot", False), + is_emoji=message.get("is_emoji", False), + is_picid=message.get("is_picid", False), + interested_rate=message.get("interested_rate", 0), ) def delete(self, message_id: str): diff --git a/src/common/database/database_model.py b/src/common/database/database_model.py index 3485fedeb..1c19dcc3f 100644 --- a/src/common/database/database_model.py +++ b/src/common/database/database_model.py @@ -130,6 +130,7 @@ class Messages(BaseModel): reply_to = TextField(null=True) interest_value = DoubleField(null=True) + is_mentioned = BooleanField(null=True) # 从 chat_info 扁平化而来的字段 chat_info_stream_id = TextField() @@ -155,6 +156,13 @@ class Messages(BaseModel): detailed_plain_text = TextField(null=True) # 详细的纯文本消息 memorized_times = IntegerField(default=0) # 被记忆的次数 + priority_mode = TextField(null=True) + priority_info = TextField(null=True) + + additional_config = TextField(null=True) + is_emoji = BooleanField(default=False) + is_picid = BooleanField(default=False) + class Meta: # database = db # 继承自 BaseModel table_name = "messages" From b1f50628ad71b71c5be074e9746b1750b6c398c4 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Fri, 11 Jul 2025 21:54:31 +0800 Subject: [PATCH 07/21] =?UTF-8?q?Revert=20"=E4=BC=98=E5=8C=96no=5Freply?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=92=8C=E5=8F=82=E6=95=B0=EF=BC=8C=E9=80=82?= =?UTF-8?q?=E9=85=8D=E7=A7=81=E8=81=8A=EF=BC=8C=E8=A7=A3=E5=86=B3=E6=8F=92?= =?UTF-8?q?=E7=A9=BA=E7=9A=84=E6=B6=88=E6=81=AF=E6=97=A0=E5=8F=8D=E5=BA=94?= =?UTF-8?q?=E9=97=AE=E9=A2=98"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit c87f36973f1680089e910922aaeeffb678d75ebf. --- src/plugins/built_in/core_actions/no_reply.py | 55 +++---------------- 1 file changed, 8 insertions(+), 47 deletions(-) diff --git a/src/plugins/built_in/core_actions/no_reply.py b/src/plugins/built_in/core_actions/no_reply.py index f1c2b640b..99337e515 100644 --- a/src/plugins/built_in/core_actions/no_reply.py +++ b/src/plugins/built_in/core_actions/no_reply.py @@ -1,6 +1,5 @@ import random import time -import asyncio from typing import Tuple # 导入新插件系统 @@ -16,8 +15,6 @@ from src.config.config import global_config logger = get_logger("core_actions") -#设置一个全局字典,确保同一个消息流的下一个NoReplyAction实例能够获取到上一次消息的时间戳 -_CHAT_START_TIMES = {} class NoReplyAction(BaseAction): """不回复动作,根据新消息的兴趣值或数量决定何时结束等待. @@ -42,47 +39,29 @@ class NoReplyAction(BaseAction): # 新增:兴趣值退出阈值 _interest_exit_threshold = 3.0 # 新增:消息数量退出阈值 - _min_exit_message_count = 4 - _max_exit_message_count = 8 + _min_exit_message_count = 5 + _max_exit_message_count = 10 # 动作参数定义 action_parameters = {"reason": "不回复的原因"} # 动作使用场景 - action_require = [ - "你发送了消息,目前无人回复", - "你觉得对方还没把话说完", - "你觉得当前话题不适合插嘴", - "你觉得自己说话太多了" - ] + action_require = ["你发送了消息,目前无人回复"] # 关联类型 associated_types = [] async def execute(self) -> Tuple[bool, str]: """执行不回复动作""" + import asyncio + try: - - # 获取或初始化当前消息的起始时间,因为用户消息是可能在刚决定好可用动作,但还没选择动作的时候发送的。原先的start_time设计会导致这种消息被漏掉,现在采用全局字典存储 - if self.chat_id not in _CHAT_START_TIMES: - # 如果对应消息流没有存储时间,就设置为当前时间 - _CHAT_START_TIMES[self.chat_id] = time.time() - start_time = _CHAT_START_TIMES[self.chat_id] - else: - message_current_time = time.time() - if message_current_time - _CHAT_START_TIMES[self.chat_id] > 600: - # 如果上一次NoReplyAction实例记录的最后消息时间戳距离现在时间戳超过了十分钟,将会把start_time设置为当前时间戳,避免在数据库内过度搜索 - start_time = message_current_time - logger.debug("距离上一次消息时间过长,已重置等待开始时间为当前时间") - else: - # 如果距离上一次noreply没有十分钟,就沿用上一次noreply退出时记录的最新消息时间戳 - start_time = _CHAT_START_TIMES[self.chat_id] - # 增加连续计数 NoReplyAction._consecutive_count += 1 count = NoReplyAction._consecutive_count reason = self.action_data.get("reason", "") + start_time = time.time() check_interval = 1.0 # 每秒检查一次 # 随机生成本次等待需要的新消息数量阈值 @@ -90,9 +69,6 @@ class NoReplyAction(BaseAction): logger.info( f"{self.log_prefix} 本次no_reply需要 {exit_message_count_threshold} 条新消息或累计兴趣值超过 {self._interest_exit_threshold} 才能打断" ) - if not self.is_group: - exit_message_count_threshold = 1 - logger.info(f"检测到当前环境为私聊,本次no_reply已更正为需要{exit_message_count_threshold}条新消息就能打断") logger.info(f"{self.log_prefix} 选择不回复(第{count}次),开始摸鱼,原因: {reason}") @@ -101,9 +77,9 @@ class NoReplyAction(BaseAction): current_time = time.time() elapsed_time = current_time - start_time - # 1. 检查新消息,默认过滤麦麦自己的消息 + # 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 + chat_id=self.chat_id, start_time=start_time, end_time=current_time ) new_message_count = len(recent_messages_dict) @@ -113,20 +89,11 @@ class NoReplyAction(BaseAction): f"{self.log_prefix} 累计消息数量达到{new_message_count}条(>{exit_message_count_threshold}),结束等待" ) exit_reason = f"{global_config.bot.nickname}(你)看到了{new_message_count}条新消息,可以考虑一下是否要进行回复" - # 如果是私聊,就稍微改一下退出理由 - if not self.is_group: - 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, ) - - # 获取最后一条消息 - latest_message = recent_messages_dict[-1] - # 在退出时更新全局字典时间戳(加1微秒防止重复) - _CHAT_START_TIMES[self.chat_id] = latest_message['time'] + 0.000001 # 0.000001秒 = 1微秒 - return True, f"累计消息数量达到{new_message_count}条,结束等待 (等待时间: {elapsed_time:.1f}秒)" # 3. 检查累计兴趣值 @@ -148,12 +115,6 @@ class NoReplyAction(BaseAction): action_prompt_display=exit_reason, action_done=True, ) - - # 获取最后一条消息 - latest_message = recent_messages_dict[-1] - # 在退出时更新全局字典时间戳(加1微秒防止重复) - _CHAT_START_TIMES[self.chat_id] = latest_message['time'] + 0.000001 # 0.000001秒 = 1微秒 - return ( True, f"累计兴趣值达到{accumulated_interest:.2f},结束等待 (等待时间: {elapsed_time:.1f}秒)", From 8daab59ec818cedd6f4c7ebb78c75664d7ff1744 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Fri, 11 Jul 2025 21:58:38 +0800 Subject: [PATCH 08/21] =?UTF-8?q?Revert=20"=E7=A7=81=E8=81=8A=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E5=8F=AF=E8=83=BD=E9=80=80=E5=87=BAfocus=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=EF=BC=8C=E4=BF=AE=E5=A4=8Dfocus=E9=80=80=E5=87=BA?= =?UTF-8?q?=E9=98=88=E5=80=BC=E5=8F=8D=E5=90=91=E8=AE=A1=E7=AE=97=E7=9A=84?= =?UTF-8?q?BUG"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 9dfc15e61c22270267c2cfb5dab812c65e20b538. --- src/chat/focus_chat/heartFC_chat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index 30c0dbb20..08008bfe9 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -459,7 +459,7 @@ class HeartFChatting: logger.debug(f"{self.log_prefix} 从action_data中获取系统命令: {command}") # 新增:消息计数和疲惫检查 - if action == "reply" and success and self.chat_stream.context.message.message_info.group_info: + if action == "reply" and success: self._message_count += 1 current_threshold = self._get_current_fatigue_threshold() logger.info( @@ -501,7 +501,7 @@ class HeartFChatting: Returns: int: 当前的疲惫阈值 """ - return max(10, int(30 * global_config.chat.exit_focus_threshold)) + return max(10, int(30 / global_config.chat.exit_focus_threshold)) def get_message_count_info(self) -> dict: """获取消息计数信息 From ce1215158cb277043bc7b2a1735f5722c869a1b8 Mon Sep 17 00:00:00 2001 From: A0000Xz <629995608@qq.com> Date: Fri, 11 Jul 2025 22:01:46 +0800 Subject: [PATCH 09/21] Update storage.py --- src/chat/message_receive/storage.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/chat/message_receive/storage.py b/src/chat/message_receive/storage.py index 456b93df9..998e06f21 100644 --- a/src/chat/message_receive/storage.py +++ b/src/chat/message_receive/storage.py @@ -55,11 +55,6 @@ class MessageStorage: # 安全地获取 user_info, 如果为 None 则视为空字典 (以防万一) user_info_from_chat = chat_info_dict.get("user_info") or {} - # 使用正则表达式匹配 @ 格式 - pattern_at = r'@<([^:>]+):\d+>' - # 替换为 @XXX 格式(对含艾特的消息进行处理,使其符合原本展示的文本形态,方便引用回复) - filtered_processed_plain_text = re.sub(pattern_at, r'@\1', filtered_processed_plain_text) - Messages.create( message_id=msg_id, time=float(message.message_info.time), From 4255e64d35cc69e21039130b6ba4125d43175ab7 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sat, 12 Jul 2025 01:24:13 +0800 Subject: [PATCH 10/21] =?UTF-8?q?feat=EF=BC=9A=E6=95=B4=E5=90=88normal?= =?UTF-8?q?=E5=92=8Cfocus=E8=81=8A=E5=A4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/__init__.py | 2 - src/chat/focus_chat/heartFC_chat.py | 506 +++++++- src/chat/focus_chat/hfc_utils.py | 138 +- .../priority_manager.py | 2 +- src/chat/heart_flow/chat_state_info.py | 13 - src/chat/heart_flow/sub_heartflow.py | 181 +-- .../message_receive/normal_message_sender.py | 2 + .../message_receive/uni_message_sender.py | 43 +- src/chat/normal_chat/normal_chat.py | 1123 ++++++++--------- src/chat/planner_actions/action_modifier.py | 18 +- src/chat/planner_actions/planner.py | 13 +- src/chat/replyer/default_generator.py | 129 -- .../willing/mode_classical.py | 0 .../{normal_chat => }/willing/mode_custom.py | 0 .../{normal_chat => }/willing/mode_mxp.py | 0 .../willing/willing_manager.py | 0 src/main.py | 2 +- src/plugins/built_in/core_actions/no_reply.py | 2 - 18 files changed, 1153 insertions(+), 1021 deletions(-) rename src/chat/{normal_chat => focus_chat}/priority_manager.py (99%) delete mode 100644 src/chat/heart_flow/chat_state_info.py rename src/chat/{normal_chat => }/willing/mode_classical.py (100%) rename src/chat/{normal_chat => }/willing/mode_custom.py (100%) rename src/chat/{normal_chat => }/willing/mode_mxp.py (100%) rename src/chat/{normal_chat => }/willing/willing_manager.py (100%) diff --git a/src/chat/__init__.py b/src/chat/__init__.py index c69d5205e..a569c0226 100644 --- a/src/chat/__init__.py +++ b/src/chat/__init__.py @@ -5,11 +5,9 @@ MaiBot模块系统 from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.emoji_system.emoji_manager import get_emoji_manager -from src.chat.normal_chat.willing.willing_manager import get_willing_manager # 导出主要组件供外部使用 __all__ = [ "get_chat_manager", "get_emoji_manager", - "get_willing_manager", ] diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index 872a800a8..13361700d 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -1,9 +1,10 @@ import asyncio -import contextlib import time import traceback from collections import deque -from typing import List, Optional, Dict, Any, Deque, Callable, Awaitable +from typing import Optional, Deque, Callable, Awaitable + +from sqlalchemy import False_ from src.chat.message_receive.chat_stream import get_chat_manager from rich.traceback import install from src.chat.utils.prompt_builder import global_prompt_manager @@ -16,6 +17,16 @@ from src.chat.planner_actions.action_manager import ActionManager from src.config.config import global_config from src.person_info.relationship_builder_manager import relationship_builder_manager from src.chat.focus_chat.hfc_utils import CycleDetail +from random import random +from src.chat.focus_chat.hfc_utils import create_thinking_message_from_dict, add_messages_to_manager,get_recent_message_stats,cleanup_thinking_message_by_id +from src.person_info.person_info import get_person_info_manager +from src.plugin_system.apis import generator_api +from ..message_receive.message import MessageThinking +from src.chat.message_receive.normal_message_sender import message_manager +from src.chat.willing.willing_manager import get_willing_manager +from .priority_manager import PriorityManager +from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat_inclusive + ERROR_LOOP_INFO = { @@ -34,6 +45,17 @@ ERROR_LOOP_INFO = { }, } +NO_ACTION = { + "action_result": { + "action_type": "no_action", + "action_data": {}, + "reasoning": "规划器初始化默认", + "is_parallel": True, + }, + "chat_context": "", + "action_prompt": "", +} + install(extra_lines=3) # 注释:原来的动作修改超时常量已移除,因为改为顺序执行 @@ -51,7 +73,6 @@ class HeartFChatting: def __init__( self, chat_id: str, - on_stop_focus_chat: Optional[Callable[[], Awaitable[None]]] = None, ): """ HeartFChatting 初始化函数 @@ -68,6 +89,10 @@ class HeartFChatting: self.relationship_builder = relationship_builder_manager.get_or_create_builder(self.stream_id) + self.loop_mode = "normal" + + self.recent_replies = [] + # 新增:消息计数器和疲惫阈值 self._message_count = 0 # 发送的消息计数 # 基于exit_focus_threshold动态计算疲惫阈值 @@ -90,11 +115,32 @@ class HeartFChatting: self._cycle_history: Deque[CycleDetail] = deque(maxlen=10) # 保留最近10个循环的信息 self._current_cycle_detail: Optional[CycleDetail] = None - # 存储回调函数 - self.on_stop_focus_chat = on_stop_focus_chat - self.reply_timeout_count = 0 self.plan_timeout_count = 0 + + self.last_read_time = time.time()-1 + + + + self.willing_amplifier = 1 + + self.action_type: Optional[str] = None # 当前动作类型 + self.is_parallel_action: bool = False # 是否是可并行动作 + + self._chat_task: Optional[asyncio.Task] = None + self._priority_chat_task: Optional[asyncio.Task] = None # for priority mode consumer + + self.reply_mode = self.chat_stream.context.get_priority_mode() + if self.reply_mode == "priority": + self.priority_manager = PriorityManager( + normal_queue_max_size=5, + ) + else: + self.priority_manager = None + + self.willing_manager = get_willing_manager() + + logger.info( f"{self.log_prefix} HeartFChatting 初始化完成,消息疲惫阈值: {self._message_threshold}条(基于exit_focus_threshold={global_config.chat.exit_focus_threshold}计算,仅在auto模式下生效)" @@ -172,44 +218,169 @@ class HeartFChatting: - async def _focus_mode_loopbody(self): - logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次循环") + async def _loopbody(self): + if self.loop_mode == "focus": + logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次观察") + return await self._observe() + elif self.loop_mode == "normal": + now = time.time() + new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( + chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" + ) + + if new_messages_data: + self.last_read_time = now + + for msg_data in new_messages_data: + try: + self.adjust_reply_frequency() + logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次循环") + await self.normal_response(msg_data) + # TODO: 这个地方可能导致阻塞,需要优化 + return True + except Exception as e: + logger.error(f"[{self.log_prefix}] 处理消息时出错: {e} {traceback.format_exc()}") + else: + await asyncio.sleep(0.1) + + return True + + + async def _observe(self,message_data:dict = None): # 创建新的循环信息 cycle_timers, thinking_id = self.start_cycle() + + await create_thinking_message_from_dict(message_data,self.chat_stream,thinking_id) - # 执行规划和处理阶段 - try: - async with global_prompt_manager.async_message_scope( - self.chat_stream.context.get_template_name() - ): + async with global_prompt_manager.async_message_scope( + self.chat_stream.context.get_template_name() + ): - loop_start_time = time.time() - await self.loop_info.observe() - await self.relationship_builder.build_relation() - - # 第一步:动作修改 - with Timer("动作修改", cycle_timers): - try: + loop_start_time = time.time() + await self.loop_info.observe() + await self.relationship_builder.build_relation() + + # 第一步:动作修改 + with Timer("动作修改", cycle_timers): + try: + if self.loop_mode == "focus": await self.action_modifier.modify_actions( loop_info=self.loop_info, mode="focus", ) - except Exception as e: - logger.error(f"{self.log_prefix} 动作修改失败: {e}") + elif self.loop_mode == "normal": + await self.action_modifier.modify_actions(mode="normal") + available_actions = self.action_manager.get_using_actions_for_mode("normal") + except Exception as e: + logger.error(f"{self.log_prefix} 动作修改失败: {e}") + + #如果normal,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的) + if self.loop_mode == "normal": + gen_task = asyncio.create_task(self._generate_normal_response(message_data, available_actions)) + - with Timer("规划器", cycle_timers): - plan_result = await self.action_planner.plan() + with Timer("规划器", cycle_timers): + if self.loop_mode == "focus": + if self.action_modifier.should_skip_planning_for_no_reply(): + logger.info(f"[{self.log_prefix}] 没有可用动作,跳过规划") + action_type = "no_reply" + else: + plan_result = await self.action_planner.plan(mode="focus") + elif self.loop_mode == "normal": + if self.action_modifier.should_skip_planning_for_no_action(): + logger.info(f"[{self.log_prefix}] 没有可用动作,跳过规划") + action_type = "no_action" + else: + plan_result = await self.action_planner.plan(mode="normal") - action_result = plan_result.get("action_result", {}) - action_type, action_data, reasoning = ( - action_result.get("action_type", "error"), - action_result.get("action_data", {}), - action_result.get("reasoning", "未提供理由"), + + + action_result = plan_result.get("action_result", {}) + action_type, action_data, reasoning, is_parallel = ( + action_result.get("action_type", "error"), + action_result.get("action_data", {}), + action_result.get("reasoning", "未提供理由"), + action_result.get("is_parallel", True), + ) + + action_data["loop_start_time"] = loop_start_time + + if self.loop_mode == "normal": + if action_type == "no_action": + logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定进行回复") + elif is_parallel: + logger.info( + f"[{self.log_prefix}] {global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作" + ) + else: + logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定执行{action_type}动作") + + + + if action_type == "no_action": + gather_timeout = global_config.chat.thinking_timeout + results = await asyncio.wait_for( + asyncio.gather(gen_task, return_exceptions=True), + timeout=gather_timeout, ) + response_set = results[0] + + if response_set: + content = " ".join([item[1] for item in response_set if item[0] == "text"]) - action_data["loop_start_time"] = loop_start_time + + if not response_set or ( + action_type not in ["no_action"] and not is_parallel + ): + if not response_set: + logger.warning(f"[{self.log_prefix}] 模型未生成回复内容") + elif action_type not in ["no_action"] and not is_parallel: + logger.info( + f"[{self.log_prefix}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{self.action_type},不发表回复" + ) + # 如果模型未生成回复,移除思考消息 + await cleanup_thinking_message_by_id(self.chat_stream.stream_id,thinking_id,self.log_prefix) + return False + logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定的回复内容: {content}") + + # 提取回复文本 + reply_texts = [item[1] for item in response_set if item[0] == "text"] + if not reply_texts: + logger.info(f"[{self.log_prefix}] 回复内容中没有文本,不发送消息") + await cleanup_thinking_message_by_id(self.chat_stream.stream_id,thinking_id,self.log_prefix) + return False + + # 发送回复 (不再需要传入 chat) + first_bot_msg = await add_messages_to_manager(message_data, reply_texts, thinking_id,self.chat_stream.stream_id) + + # 检查 first_bot_msg 是否为 None (例如思考消息已被移除的情况) + if first_bot_msg: + # 消息段已在接收消息时更新,这里不需要额外处理 + + # 记录回复信息到最近回复列表中 + reply_info = { + "time": time.time(), + "user_message": message_data.get("processed_plain_text"), + "user_info": { + "user_id": message_data.get("user_id"), + "user_nickname": message_data.get("user_nickname"), + }, + "response": response_set, + "is_reference_reply": message_data.get("reply") is not None, # 判断是否为引用回复 + } + self.recent_replies.append(reply_info) + # 保持最近回复历史在限定数量内 + if len(self.recent_replies) > 10: + self.recent_replies = self.recent_replies[-10 :] + return response_set if response_set else False + + + + + + else: # 动作执行计时 with Timer("动作执行", cycle_timers): success, reply_text, command = await self._handle_action( @@ -233,55 +404,33 @@ class HeartFChatting: return False #停止该聊天模式的循环 - self.end_cycle(loop_info,cycle_timers) - self.print_cycle_info(cycle_timers) + self.end_cycle(loop_info,cycle_timers) + self.print_cycle_info(cycle_timers) - await asyncio.sleep(global_config.focus_chat.think_interval) - - return True - - - except asyncio.CancelledError: - logger.info(f"{self.log_prefix} focus循环任务被取消") - return False - except Exception as e: - logger.error(f"{self.log_prefix} 循环处理时出错: {e}") - logger.error(traceback.format_exc()) - - # 如果_current_cycle_detail存在但未完成,为其设置错误状态 - if self._current_cycle_detail and not hasattr(self._current_cycle_detail, "end_time"): - error_loop_info = ERROR_LOOP_INFO - try: - self._current_cycle_detail.set_loop_info(error_loop_info) - self._current_cycle_detail.complete_cycle() - except Exception as inner_e: - logger.error(f"{self.log_prefix} 设置错误状态时出错: {inner_e}") + if self.loop_mode == "normal": + await self.willing_manager.after_generate_reply_handle(message_data.get("message_id")) - await asyncio.sleep(1) # 出错后等待一秒再继续\ - return False - + return True + + async def _main_chat_loop(self): """主循环,持续进行计划并可能回复消息,直到被外部取消。""" try: - loop_mode = "focus" - loop_mode_loopbody = self._focus_mode_loopbody - - while self.running: # 主循环 - success = await loop_mode_loopbody() + success = await self._loopbody() if not success: break - logger.info(f"{self.log_prefix} 麦麦已强制离开 {loop_mode} 聊天模式") - - + logger.info(f"{self.log_prefix} 麦麦已强制离开聊天") except asyncio.CancelledError: # 设置了关闭标志位后被取消是正常流程 - logger.info(f"{self.log_prefix} 麦麦已强制离开 {loop_mode} 聊天模式") - except Exception as e: - logger.error(f"{self.log_prefix} 麦麦 {loop_mode} 聊天模式意外错误: {e}") + logger.info(f"{self.log_prefix} 麦麦已关闭聊天") + except Exception: + logger.error(f"{self.log_prefix} 麦麦聊天意外错误") print(traceback.format_exc()) + # 理论上不能到这里 + logger.error(f"{self.log_prefix} 麦麦聊天意外错误,结束了聊天循环") async def _handle_action( self, @@ -376,8 +525,6 @@ class HeartFChatting: return command return "" - - def _get_current_fatigue_threshold(self) -> int: """动态获取当前的疲惫阈值,基于exit_focus_threshold配置 @@ -427,3 +574,226 @@ class HeartFChatting: logger.info(f"{self.log_prefix} HeartFChatting关闭完成") + + def adjust_reply_frequency(self): + """ + 根据预设规则动态调整回复意愿(willing_amplifier)。 + - 评估周期:10分钟 + - 目标频率:由 global_config.chat.talk_frequency 定义(例如 1条/分钟) + - 调整逻辑: + - 0条回复 -> 5.0x 意愿 + - 达到目标回复数 -> 1.0x 意愿(基准) + - 达到目标2倍回复数 -> 0.2x 意愿 + - 中间值线性变化 + - 增益抑制:如果最近5分钟回复过快,则不增加意愿。 + """ + # --- 1. 定义参数 --- + evaluation_minutes = 10.0 + target_replies_per_min = global_config.chat.get_current_talk_frequency( + self.stream_id + ) # 目标频率:e.g. 1条/分钟 + target_replies_in_window = target_replies_per_min * evaluation_minutes # 10分钟内的目标回复数 + + if target_replies_in_window <= 0: + logger.debug(f"[{self.log_prefix}] 目标回复频率为0或负数,不调整意愿放大器。") + return + + # --- 2. 获取近期统计数据 --- + stats_10_min = get_recent_message_stats(minutes=evaluation_minutes, chat_id=self.stream_id) + bot_reply_count_10_min = stats_10_min["bot_reply_count"] + + # --- 3. 计算新的意愿放大器 (willing_amplifier) --- + # 基于回复数在 [0, target*2] 区间内进行分段线性映射 + if bot_reply_count_10_min <= target_replies_in_window: + # 在 [0, 目标数] 区间,意愿从 5.0 线性下降到 1.0 + new_amplifier = 5.0 + (bot_reply_count_10_min - 0) * (1.0 - 5.0) / (target_replies_in_window - 0) + elif bot_reply_count_10_min <= target_replies_in_window * 2: + # 在 [目标数, 目标数*2] 区间,意愿从 1.0 线性下降到 0.2 + over_target_cap = target_replies_in_window * 2 + new_amplifier = 1.0 + (bot_reply_count_10_min - target_replies_in_window) * (0.2 - 1.0) / ( + over_target_cap - target_replies_in_window + ) + else: + # 超过目标数2倍,直接设为最小值 + new_amplifier = 0.2 + + # --- 4. 检查是否需要抑制增益 --- + # "如果邻近5分钟内,回复数量 > 频率/2,就不再进行增益" + suppress_gain = False + if new_amplifier > self.willing_amplifier: # 仅在计算结果为增益时检查 + suppression_minutes = 5.0 + # 5分钟内目标回复数的一半 + suppression_threshold = (target_replies_per_min / 2) * suppression_minutes # e.g., (1/2)*5 = 2.5 + stats_5_min = get_recent_message_stats(minutes=suppression_minutes, chat_id=self.stream_id) + bot_reply_count_5_min = stats_5_min["bot_reply_count"] + + if bot_reply_count_5_min > suppression_threshold: + suppress_gain = True + + # --- 5. 更新意愿放大器 --- + if suppress_gain: + logger.debug( + f"[{self.log_prefix}] 回复增益被抑制。最近5分钟内回复数 ({bot_reply_count_5_min}) " + f"> 阈值 ({suppression_threshold:.1f})。意愿放大器保持在 {self.willing_amplifier:.2f}" + ) + # 不做任何改动 + else: + # 限制最终值在 [0.2, 5.0] 范围内 + self.willing_amplifier = max(0.2, min(5.0, new_amplifier)) + logger.debug( + f"[{self.log_prefix}] 调整回复意愿。10分钟内回复: {bot_reply_count_10_min} (目标: {target_replies_in_window:.0f}) -> " + f"意愿放大器更新为: {self.willing_amplifier:.2f}" + ) + + + + async def normal_response(self, message_data: dict) -> None: + """ + 处理接收到的消息。 + 在"兴趣"模式下,判断是否回复并生成内容。 + """ + + is_mentioned = message_data.get("is_mentioned", False) + interested_rate = message_data.get("interest_rate", 0.0) * self.willing_amplifier + + reply_probability = ( + 1.0 if is_mentioned and global_config.normal_chat.mentioned_bot_inevitable_reply else 0.0 + ) # 如果被提及,且开启了提及必回复,则基础概率为1,否则需要意愿判断 + + # 意愿管理器:设置当前message信息 + self.willing_manager.setup(message_data, self.chat_stream) + + # 获取回复概率 + # 仅在未被提及或基础概率不为1时查询意愿概率 + if reply_probability < 1: # 简化逻辑,如果未提及 (reply_probability 为 0),则获取意愿概率 + # is_willing = True + reply_probability = await self.willing_manager.get_reply_probability(message_data.get("message_id")) + + additional_config = message_data.get("additional_config", {}) + if additional_config and "maimcore_reply_probability_gain" in additional_config: + reply_probability += additional_config["maimcore_reply_probability_gain"] + reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间 + + # 处理表情包 + if message_data.get("is_emoji") or message_data.get("is_picid"): + reply_probability = 0 + + # 应用疲劳期回复频率调整 + fatigue_multiplier = self._get_fatigue_reply_multiplier() + original_probability = reply_probability + reply_probability *= fatigue_multiplier + + # 如果应用了疲劳调整,记录日志 + if fatigue_multiplier < 1.0: + logger.info( + f"[{self.log_prefix}] 疲劳期回复频率调整: {original_probability * 100:.1f}% -> {reply_probability * 100:.1f}% (系数: {fatigue_multiplier:.2f})" + ) + + # 打印消息信息 + mes_name = self.chat_stream.group_info.group_name if self.chat_stream.group_info else "私聊" + if reply_probability > 0.1: + logger.info( + f"[{mes_name}]" + f"{message_data.get('user_nickname')}:" + f"{message_data.get('processed_plain_text')}[兴趣:{interested_rate:.2f}][回复概率:{reply_probability * 100:.1f}%]" + ) + + if random() < reply_probability: + await self.willing_manager.before_generate_reply_handle(message_data.get("message_id")) + await self._observe(message_data = message_data) + + # 意愿管理器:注销当前message信息 (无论是否回复,只要处理过就删除) + self.willing_manager.delete(message_data.get("message_id")) + + return True + + + async def _generate_normal_response( + self, message_data: dict, available_actions: Optional[list] + ) -> Optional[list]: + """生成普通回复""" + try: + person_info_manager = get_person_info_manager() + person_id = person_info_manager.get_person_id( + message_data.get("chat_info_platform"), message_data.get("user_id") + ) + person_name = await person_info_manager.get_value(person_id, "person_name") + reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}" + + success, reply_set = await generator_api.generate_reply( + chat_stream=self.chat_stream, + reply_to=reply_to_str, + available_actions=available_actions, + enable_tool=global_config.tool.enable_in_normal_chat, + request_type="normal.replyer", + ) + + if not success or not reply_set: + logger.info(f"对 {message_data.get('processed_plain_text')} 的回复生成失败") + return None + + return reply_set + + except Exception as e: + logger.error(f"[{self.log_prefix}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") + return None + + + def _get_fatigue_reply_multiplier(self) -> float: + """获取疲劳期回复频率调整系数 + + Returns: + float: 回复频率调整系数,范围0.5-1.0 + """ + if not self.get_cooldown_progress_callback: + return 1.0 # 没有冷却进度回调,返回正常系数 + + try: + cooldown_progress = self.get_cooldown_progress_callback() + + if cooldown_progress >= 1.0: + return 1.0 # 冷却完成,正常回复频率 + + # 疲劳期间:从0.5逐渐恢复到1.0 + # progress=0时系数为0.5,progress=1时系数为1.0 + multiplier = 0.2 + (0.8 * cooldown_progress) + + return multiplier + except Exception as e: + logger.warning(f"[{self.log_prefix}] 获取疲劳调整系数时出错: {e}") + return 1.0 # 出错时返回正常系数 + + # async def _check_should_switch_to_focus(self) -> bool: + # """ + # 检查是否满足切换到focus模式的条件 + + # Returns: + # bool: 是否应该切换到focus模式 + # """ + # # 检查思考消息堆积情况 + # container = await message_manager.get_container(self.stream_id) + # if container: + # thinking_count = sum(1 for msg in container.messages if isinstance(msg, MessageThinking)) + # if thinking_count >= 4 * global_config.chat.auto_focus_threshold: # 如果堆积超过阈值条思考消息 + # logger.debug(f"[{self.stream_name}] 检测到思考消息堆积({thinking_count}条),切换到focus模式") + # return True + + # if not self.recent_replies: + # return False + + # current_time = time.time() + # time_threshold = 120 / global_config.chat.auto_focus_threshold + # reply_threshold = 6 * global_config.chat.auto_focus_threshold + + # one_minute_ago = current_time - time_threshold + + # # 统计指定时间内的回复数量 + # recent_reply_count = sum(1 for reply in self.recent_replies if reply["time"] > one_minute_ago) + + # should_switch = recent_reply_count > reply_threshold + # if should_switch: + # logger.debug( + # f"[{self.stream_name}] 检测到{time_threshold:.0f}秒内回复数量({recent_reply_count})大于{reply_threshold},满足切换到focus模式条件" + # ) + + # return should_switch \ No newline at end of file diff --git a/src/chat/focus_chat/hfc_utils.py b/src/chat/focus_chat/hfc_utils.py index 5820d8eb4..c36f06a77 100644 --- a/src/chat/focus_chat/hfc_utils.py +++ b/src/chat/focus_chat/hfc_utils.py @@ -1,11 +1,19 @@ import time from typing import Optional -from src.chat.message_receive.message import MessageRecv, BaseMessageInfo from src.chat.message_receive.chat_stream import ChatStream from src.chat.message_receive.message import UserInfo from src.common.logger import get_logger -import json from typing import Dict, Any +from src.config.config import global_config +from src.chat.message_receive.message import MessageThinking +from src.chat.message_receive.normal_message_sender import message_manager +from typing import List +from maim_message import Seg +from src.common.message_repository import count_messages +from ..message_receive.message import MessageSending, MessageSet, message_from_db_dict +from src.chat.message_receive.chat_stream import get_chat_manager + + logger = get_logger(__name__) @@ -113,3 +121,129 @@ def parse_thinking_id_to_timestamp(thinking_id: str) -> float: ts_str = thinking_id[3:] return float(ts_str) + +async def create_thinking_message_from_dict(message_data: dict, chat_stream: ChatStream, thinking_id: str) -> str: + """创建思考消息""" + bot_user_info = UserInfo( + user_id=global_config.bot.qq_account, + user_nickname=global_config.bot.nickname, + platform=message_data.get("chat_info_platform"), + ) + + thinking_message = MessageThinking( + message_id=thinking_id, + chat_stream=chat_stream, + bot_user_info=bot_user_info, + reply=None, + thinking_start_time=time.time(), + timestamp=time.time(), + ) + + await message_manager.add_message(thinking_message) + return thinking_id + +async def cleanup_thinking_message_by_id(chat_id: str, thinking_id: str, log_prefix: str): + """根据ID清理思考消息""" + try: + container = await message_manager.get_container(chat_id) + if container: + for msg in container.messages[:]: + if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: + container.messages.remove(msg) + logger.info(f"{log_prefix}已清理思考消息 {thinking_id}") + break + except Exception as e: + logger.error(f"{log_prefix} 清理思考消息 {thinking_id} 时出错: {e}") + + + +async def add_messages_to_manager( + message_data: dict, response_set: List[str], thinking_id, chat_id + ) -> Optional[MessageSending]: + """发送回复消息""" + + chat_stream = get_chat_manager().get_stream(chat_id) + + container = await message_manager.get_container(chat_id) # 使用 self.stream_id + thinking_message = None + + for msg in container.messages[:]: + # print(msg) + if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: + thinking_message = msg + container.messages.remove(msg) + break + + if not thinking_message: + logger.warning(f"[{chat_id}] 未找到对应的思考消息 {thinking_id},可能已超时被移除") + return None + + thinking_start_time = thinking_message.thinking_start_time + message_set = MessageSet(chat_stream, thinking_id) # 使用 self.chat_stream + + sender_info = UserInfo( + user_id=message_data.get("user_id"), + user_nickname=message_data.get("user_nickname"), + platform=message_data.get("chat_info_platform"), + ) + + reply = message_from_db_dict(message_data) + + + mark_head = False + first_bot_msg = None + for msg in response_set: + if global_config.debug.debug_show_chat_mode: + msg += "ⁿ" + message_segment = Seg(type="text", data=msg) + bot_message = MessageSending( + message_id=thinking_id, + chat_stream=chat_stream, # 使用 self.chat_stream + bot_user_info=UserInfo( + user_id=global_config.bot.qq_account, + user_nickname=global_config.bot.nickname, + platform=message_data.get("chat_info_platform"), + ), + sender_info=sender_info, + message_segment=message_segment, + reply=reply, + is_head=not mark_head, + is_emoji=False, + thinking_start_time=thinking_start_time, + apply_set_reply_logic=True, + ) + if not mark_head: + mark_head = True + first_bot_msg = bot_message + message_set.add_message(bot_message) + + await message_manager.add_message(message_set) + + return first_bot_msg + + +def get_recent_message_stats(minutes: int = 30, chat_id: str = None) -> dict: + """ + Args: + minutes (int): 检索的分钟数,默认30分钟 + chat_id (str, optional): 指定的chat_id,仅统计该chat下的消息。为None时统计全部。 + Returns: + dict: {"bot_reply_count": int, "total_message_count": int} + """ + + now = time.time() + start_time = now - minutes * 60 + bot_id = global_config.bot.qq_account + + filter_base = {"time": {"$gte": start_time}} + if chat_id is not None: + filter_base["chat_id"] = chat_id + + # 总消息数 + total_message_count = count_messages(filter_base) + # bot自身回复数 + bot_filter = filter_base.copy() + bot_filter["user_id"] = bot_id + bot_reply_count = count_messages(bot_filter) + + return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count} diff --git a/src/chat/normal_chat/priority_manager.py b/src/chat/focus_chat/priority_manager.py similarity index 99% rename from src/chat/normal_chat/priority_manager.py rename to src/chat/focus_chat/priority_manager.py index facbecd23..a3f379651 100644 --- a/src/chat/normal_chat/priority_manager.py +++ b/src/chat/focus_chat/priority_manager.py @@ -2,7 +2,7 @@ import time import heapq import math import json -from typing import List, Dict, Optional +from typing import List, Optional from src.common.logger import get_logger diff --git a/src/chat/heart_flow/chat_state_info.py b/src/chat/heart_flow/chat_state_info.py deleted file mode 100644 index 33936186b..000000000 --- a/src/chat/heart_flow/chat_state_info.py +++ /dev/null @@ -1,13 +0,0 @@ -import enum - - -class ChatState(enum.Enum): - ABSENT = "没在看群" - NORMAL = "随便水群" - FOCUSED = "认真水群" - - -class ChatStateInfo: - def __init__(self): - self.chat_status: ChatState = ChatState.NORMAL - self.current_state_time = 120 diff --git a/src/chat/heart_flow/sub_heartflow.py b/src/chat/heart_flow/sub_heartflow.py index 0e4655952..2b55f9fe6 100644 --- a/src/chat/heart_flow/sub_heartflow.py +++ b/src/chat/heart_flow/sub_heartflow.py @@ -1,13 +1,11 @@ import asyncio import time -from typing import Optional, List, Dict, Tuple +from typing import Optional, List, Tuple import traceback from src.common.logger import get_logger from src.chat.message_receive.message import MessageRecv from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.focus_chat.heartFC_chat import HeartFChatting -from src.chat.normal_chat.normal_chat import NormalChat -from src.chat.heart_flow.chat_state_info import ChatState, ChatStateInfo from src.chat.utils.utils import get_chat_type_and_target_info from src.config.config import global_config from rich.traceback import install @@ -31,11 +29,6 @@ class SubHeartflow: self.subheartflow_id = subheartflow_id self.chat_id = subheartflow_id - # 这个聊天流的状态 - self.chat_state: ChatStateInfo = ChatStateInfo() - self.chat_state_changed_time: float = time.time() - self.chat_state_last_time: float = 0 - self.history_chat_state: List[Tuple[ChatState, float]] = [] self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_id) self.log_prefix = get_chat_manager().get_stream_name(self.subheartflow_id) or self.subheartflow_id @@ -47,125 +40,14 @@ class SubHeartflow: # CHAT模式激活 随便水群 FOCUS模式激活 认真水群 self.heart_fc_instance: Optional[HeartFChatting] = HeartFChatting( chat_id=self.subheartflow_id, - on_stop_focus_chat=self._handle_stop_focus_chat_request, ) # 该sub_heartflow的HeartFChatting实例 - self.normal_chat_instance: Optional[NormalChat] = NormalChat( - chat_stream=get_chat_manager().get_stream(self.chat_id), - on_switch_to_focus_callback=self._handle_switch_to_focus_request, - get_cooldown_progress_callback=self.get_cooldown_progress, - ) # 该sub_heartflow的NormalChat实例 async def initialize(self): """异步初始化方法,创建兴趣流并确定聊天类型""" + await self.heart_fc_instance.start() - # 根据配置决定初始状态 - if not self.is_group_chat: - logger.debug(f"{self.log_prefix} 检测到是私聊,将直接尝试进入 FOCUSED 状态。") - await self.change_chat_state(ChatState.FOCUSED) - elif global_config.chat.chat_mode == "focus": - logger.debug(f"{self.log_prefix} 配置为 focus 模式,将直接尝试进入 FOCUSED 状态。") - await self.change_chat_state(ChatState.FOCUSED) - else: # "auto" 或其他模式保持原有逻辑或默认为 NORMAL - logger.debug(f"{self.log_prefix} 配置为 auto 或其他模式,将尝试进入 NORMAL 状态。") - await self.change_chat_state(ChatState.NORMAL) - def update_last_chat_state_time(self): - self.chat_state_last_time = time.time() - self.chat_state_changed_time - async def _stop_normal_chat(self): - """ - 停止 NormalChat 实例 - 切出 CHAT 状态时使用 - """ - if self.normal_chat_instance: - logger.info(f"{self.log_prefix} 离开normal模式") - try: - logger.debug(f"{self.log_prefix} 开始调用 stop_chat()") - # 使用更短的超时时间,强制快速停止 - await asyncio.wait_for(self.normal_chat_instance.stop_chat(), timeout=3.0) - logger.debug(f"{self.log_prefix} stop_chat() 调用完成") - except Exception as e: - logger.error(f"{self.log_prefix} 停止 NormalChat 监控任务时出错: {e}") - # 出错时也要清理实例,避免状态不一致 - self.normal_chat_instance = None - finally: - # 确保实例被清理 - if self.normal_chat_instance: - logger.warning(f"{self.log_prefix} 强制清理 NormalChat 实例") - self.normal_chat_instance = None - logger.debug(f"{self.log_prefix} _stop_normal_chat 完成") - else: - logger.info(f"{self.log_prefix} 没有normal聊天实例,无需停止normal聊天") - - async def _start_normal_chat(self) -> bool: - """ - 启动 NormalChat 实例,并进行异步初始化。 - 进入 CHAT 状态时使用。 - 确保 HeartFChatting 已停止。 - """ - await self._stop_heart_fc_chat() # 确保 专注聊天已停止 - - try: - # 获取聊天流并创建 NormalChat 实例 (同步部分) - chat_stream = get_chat_manager().get_stream(self.chat_id) - # 在 NormalChat 实例尚未创建时,创建新实例 - if not self.normal_chat_instance: - # 提供回调函数,用于接收需要切换到focus模式的通知 - self.normal_chat_instance = NormalChat( - chat_stream=chat_stream, - on_switch_to_focus_callback=self._handle_switch_to_focus_request, - get_cooldown_progress_callback=self.get_cooldown_progress, - ) - - logger.info(f"[{self.log_prefix}] 开始普通聊天") - await self.normal_chat_instance.start_chat() # start_chat now ensures init is called again if needed - return True - except Exception as e: - logger.error(f"[{self.log_prefix}] 启动 NormalChat 或其初始化时出错: {e}") - logger.error(traceback.format_exc()) - self.normal_chat_instance = None # 启动/初始化失败,清理实例 - return False - - async def _handle_switch_to_focus_request(self) -> bool: - """ - 处理来自NormalChat的切换到focus模式的请求 - - Args: - stream_id: 请求切换的stream_id - Returns: - bool: 切换成功返回True,失败返回False - """ - logger.info(f"{self.log_prefix} 收到NormalChat请求切换到focus模式") - - # 检查是否在focus冷却期内 - if self.is_in_focus_cooldown(): - logger.info(f"{self.log_prefix} 正在focus冷却期内,忽略切换到focus模式的请求") - return False - - # 切换到focus模式 - current_state = self.chat_state.chat_status - if current_state == ChatState.NORMAL: - await self.change_chat_state(ChatState.FOCUSED) - logger.info(f"{self.log_prefix} 已根据NormalChat请求从NORMAL切换到FOCUSED状态") - return True - else: - logger.warning(f"{self.log_prefix} 当前状态为{current_state.value},无法切换到FOCUSED状态") - return False - - async def _handle_stop_focus_chat_request(self) -> None: - """ - 处理来自HeartFChatting的停止focus模式的请求 - 当收到stop_focus_chat命令时被调用 - """ - logger.info(f"{self.log_prefix} 收到HeartFChatting请求停止focus模式") - - # 切换到normal模式 - current_state = self.chat_state.chat_status - if current_state == ChatState.FOCUSED: - await self.change_chat_state(ChatState.NORMAL) - logger.info(f"{self.log_prefix} 已根据HeartFChatting请求从FOCUSED切换到NORMAL状态") - else: - logger.warning(f"{self.log_prefix} 当前状态为{current_state.value},无法切换到NORMAL状态") async def _stop_heart_fc_chat(self): """停止并清理 HeartFChatting 实例""" @@ -204,64 +86,7 @@ class SubHeartflow: logger.error(f"{self.log_prefix} _start_heart_fc_chat 执行时出错: {e}") logger.error(traceback.format_exc()) return False - - async def change_chat_state(self, new_state: ChatState) -> None: - """ - 改变聊天状态。 - 如果转换到CHAT或FOCUSED状态时超过限制,会保持当前状态。 - """ - current_state = self.chat_state.chat_status - state_changed = False - - - if new_state == ChatState.NORMAL: - if self.normal_chat_instance.running: - logger.info(f"{self.log_prefix} 当前状态已经为normal") - return - else: - if await self._start_normal_chat(): - logger.debug(f"{self.log_prefix} 成功进入或保持 NormalChat 状态。") - state_changed = True - else: - logger.error(f"{self.log_prefix} 启动 NormalChat 失败,无法进入 CHAT 状态。") - return - - elif new_state == ChatState.FOCUSED: - if self.heart_fc_instance.running: - logger.info(f"{self.log_prefix} 当前状态已经为focused") - return - if await self._start_heart_fc_chat(): - logger.debug(f"{self.log_prefix} 成功进入或保持 HeartFChatting 状态。") - state_changed = True - else: - logger.error(f"{self.log_prefix} 启动 HeartFChatting 失败,无法进入 FOCUSED 状态。") - # 启动失败时,保持当前状态 - return - - # --- 记录focus模式退出时间 --- - if state_changed and current_state == ChatState.FOCUSED and new_state != ChatState.FOCUSED: - self.last_focus_exit_time = time.time() - logger.debug(f"{self.log_prefix} 记录focus模式退出时间: {self.last_focus_exit_time}") - - # --- 更新状态和最后活动时间 --- - if state_changed: - self.update_last_chat_state_time() - self.history_chat_state.append((current_state, self.chat_state_last_time)) - - self.chat_state.chat_status = new_state - self.chat_state_last_time = 0 - self.chat_state_changed_time = time.time() - else: - logger.debug( - f"{self.log_prefix} 尝试将状态从 {current_state.value} 变为 {new_state.value},但未成功或未执行更改。" - ) - - def add_message_to_normal_chat_cache(self, message: MessageRecv, interest_value: float, is_mentioned: bool): - self.interest_dict[message.message_info.message_id] = (message, interest_value, is_mentioned) - # 如果字典长度超过10,删除最旧的消息 - if len(self.interest_dict) > 30: - oldest_key = next(iter(self.interest_dict)) - self.interest_dict.pop(oldest_key) + def is_in_focus_cooldown(self) -> bool: """检查是否在focus模式的冷却期内 diff --git a/src/chat/message_receive/normal_message_sender.py b/src/chat/message_receive/normal_message_sender.py index aa6721db3..c8bf72107 100644 --- a/src/chat/message_receive/normal_message_sender.py +++ b/src/chat/message_receive/normal_message_sender.py @@ -13,6 +13,7 @@ from ..utils.utils import truncate_message, calculate_typing_time, count_message from src.common.logger import get_logger from rich.traceback import install +import traceback install(extra_lines=3) @@ -292,6 +293,7 @@ class MessageManager: await asyncio.gather(*tasks) except Exception as e: logger.error(f"消息处理循环 gather 出错: {e}") + print(traceback.format_exc()) # 等待一小段时间,避免CPU空转 try: diff --git a/src/chat/message_receive/uni_message_sender.py b/src/chat/message_receive/uni_message_sender.py index 0efcf16d8..1102ab655 100644 --- a/src/chat/message_receive/uni_message_sender.py +++ b/src/chat/message_receive/uni_message_sender.py @@ -1,6 +1,6 @@ import asyncio -from typing import Dict, Optional # 重新导入类型 -from src.chat.message_receive.message import MessageSending, MessageThinking +from typing import Dict # 重新导入类型 +from src.chat.message_receive.message import MessageSending from src.common.message.api import get_global_api from src.chat.message_receive.storage import MessageStorage from src.chat.utils.utils import truncate_message @@ -36,42 +36,6 @@ class HeartFCSender: def __init__(self): self.storage = MessageStorage() - # 用于存储活跃的思考消息 - self.thinking_messages: Dict[str, Dict[str, MessageThinking]] = {} - self._thinking_lock = asyncio.Lock() # 保护 thinking_messages 的锁 - - async def register_thinking(self, thinking_message: MessageThinking): - """注册一个思考中的消息。""" - if not thinking_message.chat_stream or not thinking_message.message_info.message_id: - logger.error("无法注册缺少 chat_stream 或 message_id 的思考消息") - return - - chat_id = thinking_message.chat_stream.stream_id - message_id = thinking_message.message_info.message_id - - async with self._thinking_lock: - if chat_id not in self.thinking_messages: - self.thinking_messages[chat_id] = {} - if message_id in self.thinking_messages[chat_id]: - logger.warning(f"[{chat_id}] 尝试注册已存在的思考消息 ID: {message_id}") - self.thinking_messages[chat_id][message_id] = thinking_message - logger.debug(f"[{chat_id}] Registered thinking message: {message_id}") - - async def complete_thinking(self, chat_id: str, message_id: str): - """完成并移除一个思考中的消息记录。""" - async with self._thinking_lock: - if chat_id in self.thinking_messages and message_id in self.thinking_messages[chat_id]: - del self.thinking_messages[chat_id][message_id] - logger.debug(f"[{chat_id}] Completed thinking message: {message_id}") - if not self.thinking_messages[chat_id]: - del self.thinking_messages[chat_id] - logger.debug(f"[{chat_id}] Removed empty thinking message container.") - - async def get_thinking_start_time(self, chat_id: str, message_id: str) -> Optional[float]: - """获取已注册思考消息的开始时间。""" - async with self._thinking_lock: - thinking_message = self.thinking_messages.get(chat_id, {}).get(message_id) - return thinking_message.thinking_start_time if thinking_message else None async def send_message(self, message: MessageSending, typing=False, set_reply=False, storage_message=True): """ @@ -121,5 +85,4 @@ class HeartFCSender: except Exception as e: logger.error(f"[{chat_id}] 处理或存储消息 {message_id} 时出错: {e}") raise e - finally: - await self.complete_thinking(chat_id, message_id) + diff --git a/src/chat/normal_chat/normal_chat.py b/src/chat/normal_chat/normal_chat.py index 32fb24966..5a9293dd8 100644 --- a/src/chat/normal_chat/normal_chat.py +++ b/src/chat/normal_chat/normal_chat.py @@ -1,29 +1,21 @@ import asyncio import time -from random import random -from typing import List, Optional +from typing import Optional from src.config.config import global_config from src.common.logger import get_logger -from src.person_info.person_info import get_person_info_manager -from src.plugin_system.apis import generator_api -from maim_message import UserInfo, Seg from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager -from src.chat.utils.timer_calculator import Timer -from src.common.message_repository import count_messages -from src.chat.utils.prompt_builder import global_prompt_manager -from ..message_receive.message import MessageSending, MessageThinking, MessageSet, MessageRecv,message_from_db_dict +from ..message_receive.message import MessageThinking from src.chat.message_receive.normal_message_sender import message_manager from src.chat.normal_chat.willing.willing_manager import get_willing_manager from src.chat.planner_actions.action_manager import ActionManager from src.person_info.relationship_builder_manager import relationship_builder_manager -from .priority_manager import PriorityManager +from ..focus_chat.priority_manager import PriorityManager import traceback from src.chat.planner_actions.planner import ActionPlanner from src.chat.planner_actions.action_modifier import ActionModifier from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat_inclusive from src.chat.utils.utils import get_chat_type_and_target_info -from src.mood.mood_manager import mood_manager willing_manager = get_willing_manager() @@ -62,7 +54,7 @@ class NormalChat: self.willing_amplifier = 1 self.start_time = time.time() - self.mood_manager = mood_manager + # self.mood_manager = mood_manager self.start_time = time.time() self.running = False @@ -115,40 +107,40 @@ class NormalChat: self._priority_chat_task.cancel() logger.info(f"[{self.stream_name}] NormalChat 已停用。") - async def _interest_mode_loopbody(self): - try: - await asyncio.sleep(LOOP_INTERVAL) + # async def _interest_mode_loopbody(self): + # try: + # await asyncio.sleep(LOOP_INTERVAL) - if self._disabled: - return False + # if self._disabled: + # return False - now = time.time() - new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( - chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" - ) + # now = time.time() + # new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( + # chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" + # ) - if new_messages_data: - self.last_read_time = now + # if new_messages_data: + # self.last_read_time = now - for msg_data in new_messages_data: - try: - self.adjust_reply_frequency() - await self.normal_response( - message_data=msg_data, - is_mentioned=msg_data.get("is_mentioned", False), - interested_rate=msg_data.get("interest_rate", 0.0) * self.willing_amplifier, - ) - return True - except Exception as e: - logger.error(f"[{self.stream_name}] 处理消息时出错: {e} {traceback.format_exc()}") + # for msg_data in new_messages_data: + # try: + # self.adjust_reply_frequency() + # await self.normal_response( + # message_data=msg_data, + # is_mentioned=msg_data.get("is_mentioned", False), + # interested_rate=msg_data.get("interest_rate", 0.0) * self.willing_amplifier, + # ) + # return True + # except Exception as e: + # logger.error(f"[{self.stream_name}] 处理消息时出错: {e} {traceback.format_exc()}") - except asyncio.CancelledError: - logger.info(f"[{self.stream_name}] 兴趣模式轮询任务被取消") - return False - except Exception: - logger.error(f"[{self.stream_name}] 兴趣模式轮询循环出现错误: {traceback.format_exc()}", exc_info=True) - await asyncio.sleep(10) + # except asyncio.CancelledError: + # logger.info(f"[{self.stream_name}] 兴趣模式轮询任务被取消") + # return False + # except Exception: + # logger.error(f"[{self.stream_name}] 兴趣模式轮询循环出现错误: {traceback.format_exc()}", exc_info=True) + # await asyncio.sleep(10) async def _priority_mode_loopbody(self): try: @@ -181,20 +173,20 @@ class NormalChat: logger.error(f"[{self.stream_name}] 优先级消息生产者循环出现错误: {traceback.format_exc()}", exc_info=True) await asyncio.sleep(10) - async def _interest_message_polling_loop(self): - """ - [Interest Mode] 通过轮询数据库获取新消息并直接处理。 - """ - logger.info(f"[{self.stream_name}] 兴趣模式消息轮询任务开始") - try: - while not self._disabled: - success = await self._interest_mode_loopbody() + # async def _interest_message_polling_loop(self): + # """ + # [Interest Mode] 通过轮询数据库获取新消息并直接处理。 + # """ + # logger.info(f"[{self.stream_name}] 兴趣模式消息轮询任务开始") + # try: + # while not self._disabled: + # success = await self._interest_mode_loopbody() - if not success: - break + # if not success: + # break - except asyncio.CancelledError: - logger.info(f"[{self.stream_name}] 兴趣模式消息轮询任务被优雅地取消了") + # except asyncio.CancelledError: + # logger.info(f"[{self.stream_name}] 兴趣模式消息轮询任务被优雅地取消了") @@ -232,404 +224,403 @@ class NormalChat: await asyncio.sleep(10) # 改为实例方法 - async def _create_thinking_message(self, message_data: dict, timestamp: Optional[float] = None) -> str: - """创建思考消息""" - bot_user_info = UserInfo( - user_id=global_config.bot.qq_account, - user_nickname=global_config.bot.nickname, - platform=message_data.get("chat_info_platform"), - ) + # async def _create_thinking_message(self, message_data: dict, timestamp: Optional[float] = None) -> str: + # """创建思考消息""" + # bot_user_info = UserInfo( + # user_id=global_config.bot.qq_account, + # user_nickname=global_config.bot.nickname, + # platform=message_data.get("chat_info_platform"), + # ) - thinking_time_point = round(time.time(), 2) - thinking_id = "tid" + str(thinking_time_point) - thinking_message = MessageThinking( - message_id=thinking_id, - chat_stream=self.chat_stream, - bot_user_info=bot_user_info, - reply=None, - thinking_start_time=thinking_time_point, - timestamp=timestamp if timestamp is not None else None, - ) + # thinking_time_point = round(time.time(), 2) + # thinking_id = "tid" + str(thinking_time_point) + # thinking_message = MessageThinking( + # message_id=thinking_id, + # chat_stream=self.chat_stream, + # bot_user_info=bot_user_info, + # reply=None, + # thinking_start_time=thinking_time_point, + # timestamp=timestamp if timestamp is not None else None, + # ) - await message_manager.add_message(thinking_message) - return thinking_id + # await message_manager.add_message(thinking_message) + # return thinking_id # 改为实例方法 - async def _add_messages_to_manager( - self, message_data: dict, response_set: List[str], thinking_id - ) -> Optional[MessageSending]: - """发送回复消息""" - container = await message_manager.get_container(self.stream_id) # 使用 self.stream_id - thinking_message = None + # async def _add_messages_to_manager( + # self, message_data: dict, response_set: List[str], thinking_id + # ) -> Optional[MessageSending]: + # """发送回复消息""" + # container = await message_manager.get_container(self.stream_id) # 使用 self.stream_id + # thinking_message = None - for msg in container.messages[:]: - if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: - thinking_message = msg - container.messages.remove(msg) - break + # for msg in container.messages[:]: + # if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: + # thinking_message = msg + # container.messages.remove(msg) + # break - if not thinking_message: - logger.warning(f"[{self.stream_name}] 未找到对应的思考消息 {thinking_id},可能已超时被移除") - return None + # if not thinking_message: + # logger.warning(f"[{self.stream_name}] 未找到对应的思考消息 {thinking_id},可能已超时被移除") + # return None - thinking_start_time = thinking_message.thinking_start_time - message_set = MessageSet(self.chat_stream, thinking_id) # 使用 self.chat_stream + # thinking_start_time = thinking_message.thinking_start_time + # message_set = MessageSet(self.chat_stream, thinking_id) # 使用 self.chat_stream - sender_info = UserInfo( - user_id=message_data.get("user_id"), - user_nickname=message_data.get("user_nickname"), - platform=message_data.get("chat_info_platform"), - ) + # sender_info = UserInfo( + # user_id=message_data.get("user_id"), + # user_nickname=message_data.get("user_nickname"), + # platform=message_data.get("chat_info_platform"), + # ) - reply = message_from_db_dict(message_data) + # reply = message_from_db_dict(message_data) - mark_head = False - first_bot_msg = None - for msg in response_set: - if global_config.debug.debug_show_chat_mode: - msg += "ⁿ" - message_segment = Seg(type="text", data=msg) - bot_message = MessageSending( - message_id=thinking_id, - chat_stream=self.chat_stream, # 使用 self.chat_stream - bot_user_info=UserInfo( - user_id=global_config.bot.qq_account, - user_nickname=global_config.bot.nickname, - platform=message_data.get("chat_info_platform"), - ), - sender_info=sender_info, - message_segment=message_segment, - reply=reply, - is_head=not mark_head, - is_emoji=False, - thinking_start_time=thinking_start_time, - apply_set_reply_logic=True, - ) - if not mark_head: - mark_head = True - first_bot_msg = bot_message - message_set.add_message(bot_message) + # mark_head = False + # first_bot_msg = None + # for msg in response_set: + # if global_config.debug.debug_show_chat_mode: + # msg += "ⁿ" + # message_segment = Seg(type="text", data=msg) + # bot_message = MessageSending( + # message_id=thinking_id, + # chat_stream=self.chat_stream, # 使用 self.chat_stream + # bot_user_info=UserInfo( + # user_id=global_config.bot.qq_account, + # user_nickname=global_config.bot.nickname, + # platform=message_data.get("chat_info_platform"), + # ), + # sender_info=sender_info, + # message_segment=message_segment, + # reply=reply, + # is_head=not mark_head, + # is_emoji=False, + # thinking_start_time=thinking_start_time, + # apply_set_reply_logic=True, + # ) + # if not mark_head: + # mark_head = True + # first_bot_msg = bot_message + # message_set.add_message(bot_message) - await message_manager.add_message(message_set) + # await message_manager.add_message(message_set) - return first_bot_msg + # return first_bot_msg # 改为实例方法, 移除 chat 参数 - async def normal_response(self, message_data: dict, is_mentioned: bool, interested_rate: float) -> None: - """ - 处理接收到的消息。 - 在"兴趣"模式下,判断是否回复并生成内容。 - """ - if self._disabled: - return + # async def normal_response(self, message_data: dict, is_mentioned: bool, interested_rate: float) -> None: + # """ + # 处理接收到的消息。 + # 在"兴趣"模式下,判断是否回复并生成内容。 + # """ + # if self._disabled: + # return - # 新增:在auto模式下检查是否需要直接切换到focus模式 - if global_config.chat.chat_mode == "auto": - if await self._check_should_switch_to_focus(): - logger.info(f"[{self.stream_name}] 检测到切换到focus聊天模式的条件,尝试执行切换") - if self.on_switch_to_focus_callback: - switched_successfully = await self.on_switch_to_focus_callback() - if switched_successfully: - logger.info(f"[{self.stream_name}] 成功切换到focus模式,中止NormalChat处理") - return - else: - logger.info(f"[{self.stream_name}] 切换到focus模式失败(可能在冷却中),继续NormalChat处理") - else: - logger.warning(f"[{self.stream_name}] 没有设置切换到focus聊天模式的回调函数,无法执行切换") + # # 新增:在auto模式下检查是否需要直接切换到focus模式 + # if global_config.chat.chat_mode == "auto": + # if await self._check_should_switch_to_focus(): + # logger.info(f"[{self.stream_name}] 检测到切换到focus聊天模式的条件,尝试执行切换") + # if self.on_switch_to_focus_callback: + # switched_successfully = await self.on_switch_to_focus_callback() + # if switched_successfully: + # logger.info(f"[{self.stream_name}] 成功切换到focus模式,中止NormalChat处理") + # return + # else: + # logger.info(f"[{self.stream_name}] 切换到focus模式失败(可能在冷却中),继续NormalChat处理") + # else: + # logger.warning(f"[{self.stream_name}] 没有设置切换到focus聊天模式的回调函数,无法执行切换") - # --- 以下为 "兴趣" 模式逻辑 (从 _process_message 合并而来) --- - timing_results = {} - reply_probability = ( - 1.0 if is_mentioned and global_config.normal_chat.mentioned_bot_inevitable_reply else 0.0 - ) # 如果被提及,且开启了提及必回复,则基础概率为1,否则需要意愿判断 + # # --- 以下为 "兴趣" 模式逻辑 (从 _process_message 合并而来) --- + # timing_results = {} + # reply_probability = ( + # 1.0 if is_mentioned and global_config.normal_chat.mentioned_bot_inevitable_reply else 0.0 + # ) # 如果被提及,且开启了提及必回复,则基础概率为1,否则需要意愿判断 - # 意愿管理器:设置当前message信息 - willing_manager.setup(message_data, self.chat_stream) - # TODO: willing_manager 也需要修改以接收字典 + # # 意愿管理器:设置当前message信息 + # willing_manager.setup(message_data, self.chat_stream) - # 获取回复概率 - # is_willing = False - # 仅在未被提及或基础概率不为1时查询意愿概率 - if reply_probability < 1: # 简化逻辑,如果未提及 (reply_probability 为 0),则获取意愿概率 - # is_willing = True - reply_probability = await willing_manager.get_reply_probability(message_data.get("message_id")) + # # 获取回复概率 + # # is_willing = False + # # 仅在未被提及或基础概率不为1时查询意愿概率 + # if reply_probability < 1: # 简化逻辑,如果未提及 (reply_probability 为 0),则获取意愿概率 + # # is_willing = True + # reply_probability = await willing_manager.get_reply_probability(message_data.get("message_id")) - additional_config = message_data.get("additional_config", {}) - if additional_config and "maimcore_reply_probability_gain" in additional_config: - reply_probability += additional_config["maimcore_reply_probability_gain"] - reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间 + # additional_config = message_data.get("additional_config", {}) + # if additional_config and "maimcore_reply_probability_gain" in additional_config: + # reply_probability += additional_config["maimcore_reply_probability_gain"] + # reply_probability = min(max(reply_probability, 0), 1) # 确保概率在 0-1 之间 - # 处理表情包 - if message_data.get("is_emoji") or message_data.get("is_picid"): - reply_probability = 0 + # # 处理表情包 + # if message_data.get("is_emoji") or message_data.get("is_picid"): + # reply_probability = 0 - # 应用疲劳期回复频率调整 - fatigue_multiplier = self._get_fatigue_reply_multiplier() - original_probability = reply_probability - reply_probability *= fatigue_multiplier + # # 应用疲劳期回复频率调整 + # fatigue_multiplier = self._get_fatigue_reply_multiplier() + # original_probability = reply_probability + # reply_probability *= fatigue_multiplier - # 如果应用了疲劳调整,记录日志 - if fatigue_multiplier < 1.0: - logger.info( - f"[{self.stream_name}] 疲劳期回复频率调整: {original_probability * 100:.1f}% -> {reply_probability * 100:.1f}% (系数: {fatigue_multiplier:.2f})" - ) + # # 如果应用了疲劳调整,记录日志 + # if fatigue_multiplier < 1.0: + # logger.info( + # f"[{self.stream_name}] 疲劳期回复频率调整: {original_probability * 100:.1f}% -> {reply_probability * 100:.1f}% (系数: {fatigue_multiplier:.2f})" + # ) - # 打印消息信息 - mes_name = self.chat_stream.group_info.group_name if self.chat_stream.group_info else "私聊" - if reply_probability > 0.1: - logger.info( - f"[{mes_name}]" - f"{message_data.get('user_nickname')}:" - f"{message_data.get('processed_plain_text')}[兴趣:{interested_rate:.2f}][回复概率:{reply_probability * 100:.1f}%]" - ) - do_reply = False - response_set = None # 初始化 response_set - if random() < reply_probability: - with Timer("获取回复", timing_results): - await willing_manager.before_generate_reply_handle(message_data.get("message_id")) - do_reply = await self.reply_one_message(message_data) - response_set = do_reply if do_reply else None + # # 打印消息信息 + # mes_name = self.chat_stream.group_info.group_name if self.chat_stream.group_info else "私聊" + # if reply_probability > 0.1: + # logger.info( + # f"[{mes_name}]" + # f"{message_data.get('user_nickname')}:" + # f"{message_data.get('processed_plain_text')}[兴趣:{interested_rate:.2f}][回复概率:{reply_probability * 100:.1f}%]" + # ) + # do_reply = False + # response_set = None # 初始化 response_set + # if random() < reply_probability: + # with Timer("获取回复", timing_results): + # await willing_manager.before_generate_reply_handle(message_data.get("message_id")) + # do_reply = await self.reply_one_message(message_data) + # response_set = do_reply if do_reply else None - # 输出性能计时结果 - if do_reply and response_set: # 确保 response_set 不是 None - timing_str = " | ".join([f"{step}: {duration:.2f}秒" for step, duration in timing_results.items()]) - trigger_msg = message_data.get("processed_plain_text") - response_msg = " ".join([item[1] for item in response_set if item[0] == "text"]) - logger.info( - f"[{self.stream_name}]回复消息: {trigger_msg[:30]}... | 回复内容: {response_msg[:30]}... | 计时: {timing_str}" - ) - await willing_manager.after_generate_reply_handle(message_data.get("message_id")) - elif not do_reply: - # 不回复处理 - await willing_manager.not_reply_handle(message_data.get("message_id")) + # # 输出性能计时结果 + # if do_reply and response_set: # 确保 response_set 不是 None + # timing_str = " | ".join([f"{step}: {duration:.2f}秒" for step, duration in timing_results.items()]) + # trigger_msg = message_data.get("processed_plain_text") + # response_msg = " ".join([item[1] for item in response_set if item[0] == "text"]) + # logger.info( + # f"[{self.stream_name}]回复消息: {trigger_msg[:30]}... | 回复内容: {response_msg[:30]}... | 计时: {timing_str}" + # ) + # await willing_manager.after_generate_reply_handle(message_data.get("message_id")) + # elif not do_reply: + # # 不回复处理 + # await willing_manager.not_reply_handle(message_data.get("message_id")) - # 意愿管理器:注销当前message信息 (无论是否回复,只要处理过就删除) - willing_manager.delete(message_data.get("message_id")) + # # 意愿管理器:注销当前message信息 (无论是否回复,只要处理过就删除) + # willing_manager.delete(message_data.get("message_id")) - async def _generate_normal_response( - self, message_data: dict, available_actions: Optional[list] - ) -> Optional[list]: - """生成普通回复""" - try: - person_info_manager = get_person_info_manager() - person_id = person_info_manager.get_person_id( - message_data.get("chat_info_platform"), message_data.get("user_id") - ) - person_name = await person_info_manager.get_value(person_id, "person_name") - reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}" + # async def _generate_normal_response( + # self, message_data: dict, available_actions: Optional[list] + # ) -> Optional[list]: + # """生成普通回复""" + # try: + # person_info_manager = get_person_info_manager() + # person_id = person_info_manager.get_person_id( + # message_data.get("chat_info_platform"), message_data.get("user_id") + # ) + # person_name = await person_info_manager.get_value(person_id, "person_name") + # reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}" - success, reply_set = await generator_api.generate_reply( - chat_stream=self.chat_stream, - reply_to=reply_to_str, - available_actions=available_actions, - enable_tool=global_config.tool.enable_in_normal_chat, - request_type="normal.replyer", - ) + # success, reply_set = await generator_api.generate_reply( + # chat_stream=self.chat_stream, + # reply_to=reply_to_str, + # available_actions=available_actions, + # enable_tool=global_config.tool.enable_in_normal_chat, + # request_type="normal.replyer", + # ) - if not success or not reply_set: - logger.info(f"对 {message_data.get('processed_plain_text')} 的回复生成失败") - return None + # if not success or not reply_set: + # logger.info(f"对 {message_data.get('processed_plain_text')} 的回复生成失败") + # return None - return reply_set + # return reply_set - except Exception as e: - logger.error(f"[{self.stream_name}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") - return None + # except Exception as e: + # logger.error(f"[{self.stream_name}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") + # return None - async def _plan_and_execute_actions(self, message_data: dict, thinking_id: str) -> Optional[dict]: - """规划和执行额外动作""" - no_action = { - "action_result": { - "action_type": "no_action", - "action_data": {}, - "reasoning": "规划器初始化默认", - "is_parallel": True, - }, - "chat_context": "", - "action_prompt": "", - } + # async def _plan_and_execute_actions(self, message_data: dict, thinking_id: str) -> Optional[dict]: + # """规划和执行额外动作""" + # no_action = { + # "action_result": { + # "action_type": "no_action", + # "action_data": {}, + # "reasoning": "规划器初始化默认", + # "is_parallel": True, + # }, + # "chat_context": "", + # "action_prompt": "", + # } - if not self.enable_planner: - logger.debug(f"[{self.stream_name}] Planner未启用,跳过动作规划") - return no_action + # if not self.enable_planner: + # logger.debug(f"[{self.stream_name}] Planner未启用,跳过动作规划") + # return no_action - try: - # 检查是否应该跳过规划 - if self.action_modifier.should_skip_planning(): - logger.debug(f"[{self.stream_name}] 没有可用动作,跳过规划") - self.action_type = "no_action" - return no_action + # try: + # # 检查是否应该跳过规划 + # if self.action_modifier.should_skip_planning(): + # logger.debug(f"[{self.stream_name}] 没有可用动作,跳过规划") + # self.action_type = "no_action" + # return no_action - # 执行规划 - plan_result = await self.planner.plan() - action_type = plan_result["action_result"]["action_type"] - action_data = plan_result["action_result"]["action_data"] - reasoning = plan_result["action_result"]["reasoning"] - is_parallel = plan_result["action_result"].get("is_parallel", False) + # # 执行规划 + # plan_result = await self.planner.plan() + # action_type = plan_result["action_result"]["action_type"] + # action_data = plan_result["action_result"]["action_data"] + # reasoning = plan_result["action_result"]["reasoning"] + # is_parallel = plan_result["action_result"].get("is_parallel", False) - if action_type == "no_action": - logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定进行回复") - elif is_parallel: - logger.info( - f"[{self.stream_name}] {global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作" - ) - else: - logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定执行{action_type}动作") + # if action_type == "no_action": + # logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定进行回复") + # elif is_parallel: + # logger.info( + # f"[{self.stream_name}] {global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作" + # ) + # else: + # logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定执行{action_type}动作") - self.action_type = action_type # 更新实例属性 - self.is_parallel_action = is_parallel # 新增:保存并行执行标志 + # self.action_type = action_type # 更新实例属性 + # self.is_parallel_action = is_parallel # 新增:保存并行执行标志 - # 如果规划器决定不执行任何动作 - if action_type == "no_action": - logger.debug(f"[{self.stream_name}] Planner决定不执行任何额外动作") - return no_action + # # 如果规划器决定不执行任何动作 + # if action_type == "no_action": + # logger.debug(f"[{self.stream_name}] Planner决定不执行任何额外动作") + # return no_action - # 执行额外的动作(不影响回复生成) - action_result = await self._execute_action(action_type, action_data, message_data, thinking_id) - if action_result is not None: - logger.info(f"[{self.stream_name}] 额外动作 {action_type} 执行完成") - else: - logger.warning(f"[{self.stream_name}] 额外动作 {action_type} 执行失败") + # # 执行额外的动作(不影响回复生成) + # action_result = await self._handle_action(action_type, action_data, message_data, thinking_id) + # if action_result is not None: + # logger.info(f"[{self.stream_name}] 额外动作 {action_type} 执行完成") + # else: + # logger.warning(f"[{self.stream_name}] 额外动作 {action_type} 执行失败") - return { - "action_type": action_type, - "action_data": action_data, - "reasoning": reasoning, - "is_parallel": is_parallel, - } + # return { + # "action_type": action_type, + # "action_data": action_data, + # "reasoning": reasoning, + # "is_parallel": is_parallel, + # } - except Exception as e: - logger.error(f"[{self.stream_name}] Planner执行失败: {e}") - return no_action + # except Exception as e: + # logger.error(f"[{self.stream_name}] Planner执行失败: {e}") + # return no_action - async def reply_one_message(self, message_data: dict) -> None: - # 回复前处理 - await self.relationship_builder.build_relation() + # async def reply_one_message(self, message_data: dict) -> None: + # # 回复前处理 + # await self.relationship_builder.build_relation() - thinking_id = await self._create_thinking_message(message_data) + # thinking_id = await self._create_thinking_message(message_data) - # 如果启用planner,预先修改可用actions(避免在并行任务中重复调用) - available_actions = None - if self.enable_planner: - try: - await self.action_modifier.modify_actions(mode="normal", message_content=message_data.get("processed_plain_text")) - available_actions = self.action_manager.get_using_actions_for_mode("normal") - except Exception as e: - logger.warning(f"[{self.stream_name}] 获取available_actions失败: {e}") - available_actions = None + # # 如果启用planner,预先修改可用actions(避免在并行任务中重复调用) + # available_actions = None + # if self.enable_planner: + # try: + # await self.action_modifier.modify_actions(mode="normal", message_content=message_data.get("processed_plain_text")) + # available_actions = self.action_manager.get_using_actions_for_mode("normal") + # except Exception as e: + # logger.warning(f"[{self.stream_name}] 获取available_actions失败: {e}") + # available_actions = None - # 并行执行回复生成和动作规划 - self.action_type = None # 初始化动作类型 - self.is_parallel_action = False # 初始化并行动作标志 + # # 并行执行回复生成和动作规划 + # self.action_type = None # 初始化动作类型 + # self.is_parallel_action = False # 初始化并行动作标志 - gen_task = asyncio.create_task(self._generate_normal_response(message_data, available_actions)) - plan_task = asyncio.create_task(self._plan_and_execute_actions(message_data, thinking_id)) + # gen_task = asyncio.create_task(self._generate_normal_response(message_data, available_actions)) + # plan_task = asyncio.create_task(self._plan_and_execute_actions(message_data, thinking_id)) - try: - gather_timeout = global_config.chat.thinking_timeout - results = await asyncio.wait_for( - asyncio.gather(gen_task, plan_task, return_exceptions=True), - timeout=gather_timeout, - ) - response_set, plan_result = results - except asyncio.TimeoutError: - gen_timed_out = not gen_task.done() - plan_timed_out = not plan_task.done() + # try: + # gather_timeout = global_config.chat.thinking_timeout + # results = await asyncio.wait_for( + # asyncio.gather(gen_task, plan_task, return_exceptions=True), + # timeout=gather_timeout, + # ) + # response_set, plan_result = results + # except asyncio.TimeoutError: + # gen_timed_out = not gen_task.done() + # plan_timed_out = not plan_task.done() - timeout_details = [] - if gen_timed_out: - timeout_details.append("回复生成(gen)") - if plan_timed_out: - timeout_details.append("动作规划(plan)") + # timeout_details = [] + # if gen_timed_out: + # timeout_details.append("回复生成(gen)") + # if plan_timed_out: + # timeout_details.append("动作规划(plan)") - timeout_source = " 和 ".join(timeout_details) + # timeout_source = " 和 ".join(timeout_details) - logger.warning( - f"[{self.stream_name}] {timeout_source} 任务超时 ({global_config.chat.thinking_timeout}秒),正在取消相关任务..." - ) - # print(f"111{self.timeout_count}") - self.timeout_count += 1 - if self.timeout_count > 5: - logger.warning( - f"[{self.stream_name}] 连续回复超时次数过多,{global_config.chat.thinking_timeout}秒 内大模型没有返回有效内容,请检查你的api是否速度过慢或配置错误。建议不要使用推理模型,推理模型生成速度过慢。或者尝试拉高thinking_timeout参数,这可能导致回复时间过长。" - ) + # logger.warning( + # f"[{self.stream_name}] {timeout_source} 任务超时 ({global_config.chat.thinking_timeout}秒),正在取消相关任务..." + # ) + # # print(f"111{self.timeout_count}") + # self.timeout_count += 1 + # if self.timeout_count > 5: + # logger.warning( + # f"[{self.stream_name}] 连续回复超时次数过多,{global_config.chat.thinking_timeout}秒 内大模型没有返回有效内容,请检查你的api是否速度过慢或配置错误。建议不要使用推理模型,推理模型生成速度过慢。或者尝试拉高thinking_timeout参数,这可能导致回复时间过长。" + # ) - # 取消未完成的任务 - if not gen_task.done(): - gen_task.cancel() - if not plan_task.done(): - plan_task.cancel() + # # 取消未完成的任务 + # if not gen_task.done(): + # gen_task.cancel() + # if not plan_task.done(): + # plan_task.cancel() - # 清理思考消息 - await self._cleanup_thinking_message_by_id(thinking_id) + # # 清理思考消息 + # await self._cleanup_thinking_message_by_id(thinking_id) - response_set = None - plan_result = None + # response_set = None + # plan_result = None - # 处理生成回复的结果 - if isinstance(response_set, Exception): - logger.error(f"[{self.stream_name}] 回复生成异常: {response_set}") - response_set = None + # # 处理生成回复的结果 + # if isinstance(response_set, Exception): + # logger.error(f"[{self.stream_name}] 回复生成异常: {response_set}") + # response_set = None - # 处理规划结果(可选,不影响回复) - if isinstance(plan_result, Exception): - logger.error(f"[{self.stream_name}] 动作规划异常: {plan_result}") - elif plan_result: - logger.debug(f"[{self.stream_name}] 额外动作处理完成: {self.action_type}") + # # 处理规划结果(可选,不影响回复) + # if isinstance(plan_result, Exception): + # logger.error(f"[{self.stream_name}] 动作规划异常: {plan_result}") + # elif plan_result: + # logger.debug(f"[{self.stream_name}] 额外动作处理完成: {self.action_type}") - if response_set: - content = " ".join([item[1] for item in response_set if item[0] == "text"]) + # if response_set: + # content = " ".join([item[1] for item in response_set if item[0] == "text"]) - if not response_set or ( - self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action - ): - if not response_set: - logger.warning(f"[{self.stream_name}] 模型未生成回复内容") - elif self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action: - logger.info( - f"[{self.stream_name}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{self.action_type},不发表回复" - ) - # 如果模型未生成回复,移除思考消息 - await self._cleanup_thinking_message_by_id(thinking_id) - return False + # if not response_set or ( + # self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action + # ): + # if not response_set: + # logger.warning(f"[{self.stream_name}] 模型未生成回复内容") + # elif self.enable_planner and self.action_type not in ["no_action"] and not self.is_parallel_action: + # logger.info( + # f"[{self.stream_name}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{self.action_type},不发表回复" + # ) + # # 如果模型未生成回复,移除思考消息 + # await self._cleanup_thinking_message_by_id(thinking_id) + # return False - logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定的回复内容: {content}") + # logger.info(f"[{self.stream_name}] {global_config.bot.nickname} 决定的回复内容: {content}") - if self._disabled: - logger.info(f"[{self.stream_name}] 已停用,忽略 normal_response。") - return False + # if self._disabled: + # logger.info(f"[{self.stream_name}] 已停用,忽略 normal_response。") + # return False - # 提取回复文本 - reply_texts = [item[1] for item in response_set if item[0] == "text"] - if not reply_texts: - logger.info(f"[{self.stream_name}] 回复内容中没有文本,不发送消息") - await self._cleanup_thinking_message_by_id(thinking_id) - return False + # # 提取回复文本 + # reply_texts = [item[1] for item in response_set if item[0] == "text"] + # if not reply_texts: + # logger.info(f"[{self.stream_name}] 回复内容中没有文本,不发送消息") + # await self._cleanup_thinking_message_by_id(thinking_id) + # return False - # 发送回复 (不再需要传入 chat) - first_bot_msg = await self._add_messages_to_manager(message_data, reply_texts, thinking_id) + # # 发送回复 (不再需要传入 chat) + # first_bot_msg = await add_messages_to_manager(message_data, reply_texts, thinking_id) - # 检查 first_bot_msg 是否为 None (例如思考消息已被移除的情况) - if first_bot_msg: - # 消息段已在接收消息时更新,这里不需要额外处理 + # # 检查 first_bot_msg 是否为 None (例如思考消息已被移除的情况) + # if first_bot_msg: + # # 消息段已在接收消息时更新,这里不需要额外处理 - # 记录回复信息到最近回复列表中 - reply_info = { - "time": time.time(), - "user_message": message_data.get("processed_plain_text"), - "user_info": { - "user_id": message_data.get("user_id"), - "user_nickname": message_data.get("user_nickname"), - }, - "response": response_set, - "is_reference_reply": message_data.get("reply") is not None, # 判断是否为引用回复 - } - self.recent_replies.append(reply_info) - # 保持最近回复历史在限定数量内 - if len(self.recent_replies) > self.max_replies_history: - self.recent_replies = self.recent_replies[-self.max_replies_history :] - return response_set if response_set else False + # # 记录回复信息到最近回复列表中 + # reply_info = { + # "time": time.time(), + # "user_message": message_data.get("processed_plain_text"), + # "user_info": { + # "user_id": message_data.get("user_id"), + # "user_nickname": message_data.get("user_nickname"), + # }, + # "response": response_set, + # "is_reference_reply": message_data.get("reply") is not None, # 判断是否为引用回复 + # } + # self.recent_replies.append(reply_info) + # # 保持最近回复历史在限定数量内 + # if len(self.recent_replies) > self.max_replies_history: + # self.recent_replies = self.recent_replies[-self.max_replies_history :] + # return response_set if response_set else False # 改为实例方法, 移除 chat 参数 @@ -677,34 +668,34 @@ class NormalChat: self._priority_chat_task = None raise - def _handle_task_completion(self, task: asyncio.Task, task_name: str = "unknown"): - """任务完成回调处理""" - try: - logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 完成回调被调用") + # def _handle_task_completion(self, task: asyncio.Task, task_name: str = "unknown"): + # """任务完成回调处理""" + # try: + # logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 完成回调被调用") - if task is self._chat_task: - self._chat_task = None - elif task is self._priority_chat_task: - self._priority_chat_task = None - else: - logger.debug(f"[{self.stream_name}] 回调的任务 '{task_name}' 不是当前管理的任务") - return + # if task is self._chat_task: + # self._chat_task = None + # elif task is self._priority_chat_task: + # self._priority_chat_task = None + # else: + # logger.debug(f"[{self.stream_name}] 回调的任务 '{task_name}' 不是当前管理的任务") + # return - logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 引用已清理") + # logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 引用已清理") - if task.cancelled(): - logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 已取消") - elif task.done(): - exc = task.exception() - if exc: - logger.error(f"[{self.stream_name}] 任务 '{task_name}' 异常: {type(exc).__name__}: {exc}", exc_info=exc) - else: - logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 正常完成") + # if task.cancelled(): + # logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 已取消") + # elif task.done(): + # exc = task.exception() + # if exc: + # logger.error(f"[{self.stream_name}] 任务 '{task_name}' 异常: {type(exc).__name__}: {exc}", exc_info=exc) + # else: + # logger.debug(f"[{self.stream_name}] 任务 '{task_name}' 正常完成") - except Exception as e: - logger.error(f"[{self.stream_name}] 任务完成回调处理出错: {e}") - self._chat_task = None - self._priority_chat_task = None + # except Exception as e: + # logger.error(f"[{self.stream_name}] 任务完成回调处理出错: {e}") + # self._chat_task = None + # self._priority_chat_task = None # 改为实例方法, 移除 stream_id 参数 async def stop_chat(self): @@ -721,156 +712,140 @@ class NormalChat: self._chat_task = None self._priority_chat_task = None - asyncio.create_task(self._cleanup_thinking_messages_async()) - async def _cleanup_thinking_messages_async(self): - """异步清理思考消息,避免阻塞主流程""" - try: - await asyncio.sleep(0.1) + # def adjust_reply_frequency(self): + # """ + # 根据预设规则动态调整回复意愿(willing_amplifier)。 + # - 评估周期:10分钟 + # - 目标频率:由 global_config.chat.talk_frequency 定义(例如 1条/分钟) + # - 调整逻辑: + # - 0条回复 -> 5.0x 意愿 + # - 达到目标回复数 -> 1.0x 意愿(基准) + # - 达到目标2倍回复数 -> 0.2x 意愿 + # - 中间值线性变化 + # - 增益抑制:如果最近5分钟回复过快,则不增加意愿。 + # """ + # # --- 1. 定义参数 --- + # evaluation_minutes = 10.0 + # target_replies_per_min = global_config.chat.get_current_talk_frequency( + # self.stream_id + # ) # 目标频率:e.g. 1条/分钟 + # target_replies_in_window = target_replies_per_min * evaluation_minutes # 10分钟内的目标回复数 - container = await message_manager.get_container(self.stream_id) - if container: - thinking_messages = [msg for msg in container.messages[:] if isinstance(msg, MessageThinking)] - if thinking_messages: - for msg in thinking_messages: - container.messages.remove(msg) - logger.info(f"[{self.stream_name}] 清理了 {len(thinking_messages)} 条未处理的思考消息。") - except Exception as e: - logger.error(f"[{self.stream_name}] 异步清理思考消息时出错: {e}") + # if target_replies_in_window <= 0: + # logger.debug(f"[{self.stream_name}] 目标回复频率为0或负数,不调整意愿放大器。") + # return - def adjust_reply_frequency(self): - """ - 根据预设规则动态调整回复意愿(willing_amplifier)。 - - 评估周期:10分钟 - - 目标频率:由 global_config.chat.talk_frequency 定义(例如 1条/分钟) - - 调整逻辑: - - 0条回复 -> 5.0x 意愿 - - 达到目标回复数 -> 1.0x 意愿(基准) - - 达到目标2倍回复数 -> 0.2x 意愿 - - 中间值线性变化 - - 增益抑制:如果最近5分钟回复过快,则不增加意愿。 - """ - # --- 1. 定义参数 --- - evaluation_minutes = 10.0 - target_replies_per_min = global_config.chat.get_current_talk_frequency( - self.stream_id - ) # 目标频率:e.g. 1条/分钟 - target_replies_in_window = target_replies_per_min * evaluation_minutes # 10分钟内的目标回复数 + # # --- 2. 获取近期统计数据 --- + # stats_10_min = get_recent_message_stats(minutes=evaluation_minutes, chat_id=self.stream_id) + # bot_reply_count_10_min = stats_10_min["bot_reply_count"] - if target_replies_in_window <= 0: - logger.debug(f"[{self.stream_name}] 目标回复频率为0或负数,不调整意愿放大器。") - return + # # --- 3. 计算新的意愿放大器 (willing_amplifier) --- + # # 基于回复数在 [0, target*2] 区间内进行分段线性映射 + # if bot_reply_count_10_min <= target_replies_in_window: + # # 在 [0, 目标数] 区间,意愿从 5.0 线性下降到 1.0 + # new_amplifier = 5.0 + (bot_reply_count_10_min - 0) * (1.0 - 5.0) / (target_replies_in_window - 0) + # elif bot_reply_count_10_min <= target_replies_in_window * 2: + # # 在 [目标数, 目标数*2] 区间,意愿从 1.0 线性下降到 0.2 + # over_target_cap = target_replies_in_window * 2 + # new_amplifier = 1.0 + (bot_reply_count_10_min - target_replies_in_window) * (0.2 - 1.0) / ( + # over_target_cap - target_replies_in_window + # ) + # else: + # # 超过目标数2倍,直接设为最小值 + # new_amplifier = 0.2 - # --- 2. 获取近期统计数据 --- - stats_10_min = get_recent_message_stats(minutes=evaluation_minutes, chat_id=self.stream_id) - bot_reply_count_10_min = stats_10_min["bot_reply_count"] + # # --- 4. 检查是否需要抑制增益 --- + # # "如果邻近5分钟内,回复数量 > 频率/2,就不再进行增益" + # suppress_gain = False + # if new_amplifier > self.willing_amplifier: # 仅在计算结果为增益时检查 + # suppression_minutes = 5.0 + # # 5分钟内目标回复数的一半 + # suppression_threshold = (target_replies_per_min / 2) * suppression_minutes # e.g., (1/2)*5 = 2.5 + # stats_5_min = get_recent_message_stats(minutes=suppression_minutes, chat_id=self.stream_id) + # bot_reply_count_5_min = stats_5_min["bot_reply_count"] - # --- 3. 计算新的意愿放大器 (willing_amplifier) --- - # 基于回复数在 [0, target*2] 区间内进行分段线性映射 - if bot_reply_count_10_min <= target_replies_in_window: - # 在 [0, 目标数] 区间,意愿从 5.0 线性下降到 1.0 - new_amplifier = 5.0 + (bot_reply_count_10_min - 0) * (1.0 - 5.0) / (target_replies_in_window - 0) - elif bot_reply_count_10_min <= target_replies_in_window * 2: - # 在 [目标数, 目标数*2] 区间,意愿从 1.0 线性下降到 0.2 - over_target_cap = target_replies_in_window * 2 - new_amplifier = 1.0 + (bot_reply_count_10_min - target_replies_in_window) * (0.2 - 1.0) / ( - over_target_cap - target_replies_in_window - ) - else: - # 超过目标数2倍,直接设为最小值 - new_amplifier = 0.2 + # if bot_reply_count_5_min > suppression_threshold: + # suppress_gain = True - # --- 4. 检查是否需要抑制增益 --- - # "如果邻近5分钟内,回复数量 > 频率/2,就不再进行增益" - suppress_gain = False - if new_amplifier > self.willing_amplifier: # 仅在计算结果为增益时检查 - suppression_minutes = 5.0 - # 5分钟内目标回复数的一半 - suppression_threshold = (target_replies_per_min / 2) * suppression_minutes # e.g., (1/2)*5 = 2.5 - stats_5_min = get_recent_message_stats(minutes=suppression_minutes, chat_id=self.stream_id) - bot_reply_count_5_min = stats_5_min["bot_reply_count"] + # # --- 5. 更新意愿放大器 --- + # if suppress_gain: + # logger.debug( + # f"[{self.stream_name}] 回复增益被抑制。最近5分钟内回复数 ({bot_reply_count_5_min}) " + # f"> 阈值 ({suppression_threshold:.1f})。意愿放大器保持在 {self.willing_amplifier:.2f}" + # ) + # # 不做任何改动 + # else: + # # 限制最终值在 [0.2, 5.0] 范围内 + # self.willing_amplifier = max(0.2, min(5.0, new_amplifier)) + # logger.debug( + # f"[{self.stream_name}] 调整回复意愿。10分钟内回复: {bot_reply_count_10_min} (目标: {target_replies_in_window:.0f}) -> " + # f"意愿放大器更新为: {self.willing_amplifier:.2f}" + # ) - if bot_reply_count_5_min > suppression_threshold: - suppress_gain = True + # async def _execute_action( + # self, action_type: str, action_data: dict, message_data: dict, thinking_id: str + # ) -> Optional[bool]: + # """执行具体的动作,只返回执行成功与否""" + # try: + # # 创建动作处理器实例 + # action_handler = self.action_manager.create_action( + # action_name=action_type, + # action_data=action_data, + # reasoning=action_data.get("reasoning", ""), + # cycle_timers={}, # normal_chat使用空的cycle_timers + # thinking_id=thinking_id, + # chat_stream=self.chat_stream, + # log_prefix=self.stream_name, + # shutting_down=self._disabled, + # ) - # --- 5. 更新意愿放大器 --- - if suppress_gain: - logger.debug( - f"[{self.stream_name}] 回复增益被抑制。最近5分钟内回复数 ({bot_reply_count_5_min}) " - f"> 阈值 ({suppression_threshold:.1f})。意愿放大器保持在 {self.willing_amplifier:.2f}" - ) - # 不做任何改动 - else: - # 限制最终值在 [0.2, 5.0] 范围内 - self.willing_amplifier = max(0.2, min(5.0, new_amplifier)) - logger.debug( - f"[{self.stream_name}] 调整回复意愿。10分钟内回复: {bot_reply_count_10_min} (目标: {target_replies_in_window:.0f}) -> " - f"意愿放大器更新为: {self.willing_amplifier:.2f}" - ) + # if action_handler: + # # 执行动作 + # result = await action_handler.handle_action() + # success = False - async def _execute_action( - self, action_type: str, action_data: dict, message_data: dict, thinking_id: str - ) -> Optional[bool]: - """执行具体的动作,只返回执行成功与否""" - try: - # 创建动作处理器实例 - action_handler = self.action_manager.create_action( - action_name=action_type, - action_data=action_data, - reasoning=action_data.get("reasoning", ""), - cycle_timers={}, # normal_chat使用空的cycle_timers - thinking_id=thinking_id, - chat_stream=self.chat_stream, - log_prefix=self.stream_name, - shutting_down=self._disabled, - ) + # if result and isinstance(result, tuple) and len(result) >= 2: + # # handle_action返回 (success: bool, message: str) + # success = result[0] + # elif result: + # # 如果返回了其他结果,假设成功 + # success = True - if action_handler: - # 执行动作 - result = await action_handler.handle_action() - success = False + # return success - if result and isinstance(result, tuple) and len(result) >= 2: - # handle_action返回 (success: bool, message: str) - success = result[0] - elif result: - # 如果返回了其他结果,假设成功 - success = True + # except Exception as e: + # logger.error(f"[{self.stream_name}] 执行动作 {action_type} 失败: {e}") - return success + # return False - except Exception as e: - logger.error(f"[{self.stream_name}] 执行动作 {action_type} 失败: {e}") + # def get_action_manager(self) -> ActionManager: + # """获取动作管理器实例""" + # return self.action_manager - return False + # def _get_fatigue_reply_multiplier(self) -> float: + # """获取疲劳期回复频率调整系数 - def get_action_manager(self) -> ActionManager: - """获取动作管理器实例""" - return self.action_manager + # Returns: + # float: 回复频率调整系数,范围0.5-1.0 + # """ + # if not self.get_cooldown_progress_callback: + # return 1.0 # 没有冷却进度回调,返回正常系数 - def _get_fatigue_reply_multiplier(self) -> float: - """获取疲劳期回复频率调整系数 + # try: + # cooldown_progress = self.get_cooldown_progress_callback() - Returns: - float: 回复频率调整系数,范围0.5-1.0 - """ - if not self.get_cooldown_progress_callback: - return 1.0 # 没有冷却进度回调,返回正常系数 + # if cooldown_progress >= 1.0: + # return 1.0 # 冷却完成,正常回复频率 - try: - cooldown_progress = self.get_cooldown_progress_callback() + # # 疲劳期间:从0.5逐渐恢复到1.0 + # # progress=0时系数为0.5,progress=1时系数为1.0 + # multiplier = 0.2 + (0.8 * cooldown_progress) - if cooldown_progress >= 1.0: - return 1.0 # 冷却完成,正常回复频率 - - # 疲劳期间:从0.5逐渐恢复到1.0 - # progress=0时系数为0.5,progress=1时系数为1.0 - multiplier = 0.2 + (0.8 * cooldown_progress) - - return multiplier - except Exception as e: - logger.warning(f"[{self.stream_name}] 获取疲劳调整系数时出错: {e}") - return 1.0 # 出错时返回正常系数 + # return multiplier + # except Exception as e: + # logger.warning(f"[{self.stream_name}] 获取疲劳调整系数时出错: {e}") + # return 1.0 # 出错时返回正常系数 async def _check_should_switch_to_focus(self) -> bool: """ @@ -907,42 +882,42 @@ class NormalChat: return should_switch - async def _cleanup_thinking_message_by_id(self, thinking_id: str): - """根据ID清理思考消息""" - try: - container = await message_manager.get_container(self.stream_id) - if container: - for msg in container.messages[:]: - if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: - container.messages.remove(msg) - logger.info(f"[{self.stream_name}] 已清理思考消息 {thinking_id}") - break - except Exception as e: - logger.error(f"[{self.stream_name}] 清理思考消息 {thinking_id} 时出错: {e}") + # async def _cleanup_thinking_message_by_id(self, thinking_id: str): + # """根据ID清理思考消息""" + # try: + # container = await message_manager.get_container(self.stream_id) + # if container: + # for msg in container.messages[:]: + # if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: + # container.messages.remove(msg) + # logger.info(f"[{self.stream_name}] 已清理思考消息 {thinking_id}") + # break + # except Exception as e: + # logger.error(f"[{self.stream_name}] 清理思考消息 {thinking_id} 时出错: {e}") -def get_recent_message_stats(minutes: int = 30, chat_id: str = None) -> dict: - """ - Args: - minutes (int): 检索的分钟数,默认30分钟 - chat_id (str, optional): 指定的chat_id,仅统计该chat下的消息。为None时统计全部。 - Returns: - dict: {"bot_reply_count": int, "total_message_count": int} - """ +# def get_recent_message_stats(minutes: int = 30, chat_id: str = None) -> dict: +# """ +# Args: +# minutes (int): 检索的分钟数,默认30分钟 +# chat_id (str, optional): 指定的chat_id,仅统计该chat下的消息。为None时统计全部。 +# Returns: +# dict: {"bot_reply_count": int, "total_message_count": int} +# """ - now = time.time() - start_time = now - minutes * 60 - bot_id = global_config.bot.qq_account +# now = time.time() +# start_time = now - minutes * 60 +# bot_id = global_config.bot.qq_account - filter_base = {"time": {"$gte": start_time}} - if chat_id is not None: - filter_base["chat_id"] = chat_id +# filter_base = {"time": {"$gte": start_time}} +# if chat_id is not None: +# filter_base["chat_id"] = chat_id - # 总消息数 - total_message_count = count_messages(filter_base) - # bot自身回复数 - bot_filter = filter_base.copy() - bot_filter["user_id"] = bot_id - bot_reply_count = count_messages(bot_filter) +# # 总消息数 +# total_message_count = count_messages(filter_base) +# # bot自身回复数 +# bot_filter = filter_base.copy() +# bot_filter["user_id"] = bot_id +# bot_reply_count = count_messages(bot_filter) - return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count} +# return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count} diff --git a/src/chat/planner_actions/action_modifier.py b/src/chat/planner_actions/action_modifier.py index a2e0066cf..58be641e1 100644 --- a/src/chat/planner_actions/action_modifier.py +++ b/src/chat/planner_actions/action_modifier.py @@ -526,16 +526,24 @@ class ActionModifier: return removals - def get_available_actions_count(self) -> int: + def get_available_actions_count(self,mode:str = "focus") -> int: """获取当前可用动作数量(排除默认的no_action)""" - current_actions = self.action_manager.get_using_actions_for_mode("normal") + current_actions = self.action_manager.get_using_actions_for_mode(mode) # 排除no_action(如果存在) filtered_actions = {k: v for k, v in current_actions.items() if k != "no_action"} return len(filtered_actions) - - def should_skip_planning(self) -> bool: + + def should_skip_planning_for_no_reply(self) -> bool: """判断是否应该跳过规划过程""" - available_count = self.get_available_actions_count() + current_actions = self.action_manager.get_using_actions_for_mode("focus") + # 排除no_action(如果存在) + if len(current_actions) == 1 and "no_reply" in current_actions: + return True + return False + + def should_skip_planning_for_no_action(self) -> bool: + """判断是否应该跳过规划过程""" + available_count = self.action_manager.get_using_actions_for_mode("normal") if available_count == 0: logger.debug(f"{self.log_prefix} 没有可用动作,跳过规划") return True diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 760148d05..c088fd78c 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -64,20 +64,19 @@ def init_prompt(): class ActionPlanner: - def __init__(self, chat_id: str, action_manager: ActionManager, mode: str = "focus"): + def __init__(self, chat_id: str, action_manager: ActionManager): self.chat_id = chat_id self.log_prefix = f"[{get_chat_manager().get_stream_name(chat_id) or chat_id}]" - self.mode = mode self.action_manager = action_manager # LLM规划器配置 self.planner_llm = LLMRequest( model=global_config.model.planner, - request_type=f"{self.mode}.planner", # 用于动作规划 + request_type="planner", # 用于动作规划 ) self.last_obs_time_mark = 0.0 - async def plan(self) -> Dict[str, Any]: + async def plan(self,mode: str = "focus") -> Dict[str, Any]: """ 规划器 (Planner): 使用LLM根据上下文决定做出什么动作。 """ @@ -92,7 +91,7 @@ class ActionPlanner: is_group_chat, chat_target_info = get_chat_type_and_target_info(self.chat_id) logger.debug(f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}") - current_available_actions_dict = self.action_manager.get_using_actions_for_mode(self.mode) + current_available_actions_dict = self.action_manager.get_using_actions_for_mode(mode) # 获取完整的动作信息 all_registered_actions = self.action_manager.get_registered_actions() @@ -122,6 +121,7 @@ class ActionPlanner: is_group_chat=is_group_chat, # <-- Pass HFC state chat_target_info=chat_target_info, # <-- 传递获取到的聊天目标信息 current_available_actions=current_available_actions, # <-- Pass determined actions + mode=mode, ) # --- 调用 LLM (普通文本生成) --- @@ -215,6 +215,7 @@ class ActionPlanner: is_group_chat: bool, # Now passed as argument chat_target_info: Optional[dict], # Now passed as argument current_available_actions, + mode: str = "focus", ) -> str: """构建 Planner LLM 的提示词 (获取模板并填充数据)""" try: @@ -244,7 +245,7 @@ class ActionPlanner: self.last_obs_time_mark = time.time() - if self.mode == "focus": + if mode == "focus": by_what = "聊天内容" no_action_block = "" else: diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 846112305..627bcc69f 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -136,33 +136,6 @@ class DefaultReplyer: selected_config = random.choices(population=configs, weights=weights, k=1)[0] return selected_config - async def _create_thinking_message(self, anchor_message: Optional[MessageRecv], thinking_id: str): - """创建思考消息 (尝试锚定到 anchor_message)""" - if not anchor_message or not anchor_message.chat_stream: - logger.error(f"{self.log_prefix} 无法创建思考消息,缺少有效的锚点消息或聊天流。") - return None - - chat = anchor_message.chat_stream - messageinfo = anchor_message.message_info - thinking_time_point = parse_thinking_id_to_timestamp(thinking_id) - bot_user_info = UserInfo( - user_id=global_config.bot.qq_account, - user_nickname=global_config.bot.nickname, - platform=messageinfo.platform, - ) - - thinking_message = MessageThinking( - message_id=thinking_id, - chat_stream=chat, - bot_user_info=bot_user_info, - reply=anchor_message, # 回复的是锚点消息 - thinking_start_time=thinking_time_point, - ) - # logger.debug(f"创建思考消息thinking_message:{thinking_message}") - - await self.heart_fc_sender.register_thinking(thinking_message) - return None - async def generate_reply_with_context( self, reply_data: Dict[str, Any] = None, @@ -812,108 +785,6 @@ class DefaultReplyer: return prompt - async def send_response_messages( - self, - anchor_message: Optional[MessageRecv], - response_set: List[Tuple[str, str]], - thinking_id: str = "", - display_message: str = "", - ) -> Optional[MessageSending]: - """发送回复消息 (尝试锚定到 anchor_message),使用 HeartFCSender""" - chat = self.chat_stream - chat_id = self.chat_stream.stream_id - if chat is None: - logger.error(f"{self.log_prefix} 无法发送回复,chat_stream 为空。") - return None - if not anchor_message: - logger.error(f"{self.log_prefix} 无法发送回复,anchor_message 为空。") - return None - - stream_name = get_chat_manager().get_stream_name(chat_id) or chat_id # 获取流名称用于日志 - - # 检查思考过程是否仍在进行,并获取开始时间 - if thinking_id: - # print(f"thinking_id: {thinking_id}") - thinking_start_time = await self.heart_fc_sender.get_thinking_start_time(chat_id, thinking_id) - else: - print("thinking_id is None") - # thinking_id = "ds" + str(round(time.time(), 2)) - thinking_start_time = time.time() - - if thinking_start_time is None: - logger.error(f"[{stream_name}]replyer思考过程未找到或已结束,无法发送回复。") - return None - - mark_head = False - # first_bot_msg: Optional[MessageSending] = None - reply_message_ids = [] # 记录实际发送的消息ID - - sent_msg_list = [] - - for i, msg_text in enumerate(response_set): - # 为每个消息片段生成唯一ID - type = msg_text[0] - data = msg_text[1] - - if global_config.debug.debug_show_chat_mode and type == "text": - data += "ᶠ" - - part_message_id = f"{thinking_id}_{i}" - message_segment = Seg(type=type, data=data) - - if type == "emoji": - is_emoji = True - else: - is_emoji = False - reply_to = not mark_head - - bot_message: MessageSending = await self._build_single_sending_message( - anchor_message=anchor_message, - message_id=part_message_id, - message_segment=message_segment, - display_message=display_message, - reply_to=reply_to, - is_emoji=is_emoji, - thinking_id=thinking_id, - thinking_start_time=thinking_start_time, - ) - - try: - if ( - bot_message.is_private_message() - or bot_message.reply.processed_plain_text != "[System Trigger Context]" - or mark_head - ): - set_reply = False - else: - set_reply = True - - if not mark_head: - mark_head = True - typing = False - else: - typing = True - - sent_msg = await self.heart_fc_sender.send_message(bot_message, typing=typing, set_reply=set_reply) - - reply_message_ids.append(part_message_id) # 记录我们生成的ID - - sent_msg_list.append((type, sent_msg)) - - except Exception as e: - logger.error(f"{self.log_prefix}发送回复片段 {i} ({part_message_id}) 时失败: {e}") - traceback.print_exc() - # 这里可以选择是继续发送下一个片段还是中止 - - # 在尝试发送完所有片段后,完成原始的 thinking_id 状态 - try: - await self.heart_fc_sender.complete_thinking(chat_id, thinking_id) - - except Exception as e: - logger.error(f"{self.log_prefix}完成思考状态 {thinking_id} 时出错: {e}") - - return sent_msg_list - async def _build_single_sending_message( self, message_id: str, diff --git a/src/chat/normal_chat/willing/mode_classical.py b/src/chat/willing/mode_classical.py similarity index 100% rename from src/chat/normal_chat/willing/mode_classical.py rename to src/chat/willing/mode_classical.py diff --git a/src/chat/normal_chat/willing/mode_custom.py b/src/chat/willing/mode_custom.py similarity index 100% rename from src/chat/normal_chat/willing/mode_custom.py rename to src/chat/willing/mode_custom.py diff --git a/src/chat/normal_chat/willing/mode_mxp.py b/src/chat/willing/mode_mxp.py similarity index 100% rename from src/chat/normal_chat/willing/mode_mxp.py rename to src/chat/willing/mode_mxp.py diff --git a/src/chat/normal_chat/willing/willing_manager.py b/src/chat/willing/willing_manager.py similarity index 100% rename from src/chat/normal_chat/willing/willing_manager.py rename to src/chat/willing/willing_manager.py diff --git a/src/main.py b/src/main.py index bd9005394..ec15f76de 100644 --- a/src/main.py +++ b/src/main.py @@ -7,7 +7,7 @@ from src.common.remote import TelemetryHeartBeatTask from src.manager.async_task_manager import async_task_manager from src.chat.utils.statistic import OnlineTimeRecordTask, StatisticOutputTask from src.chat.emoji_system.emoji_manager import get_emoji_manager -from src.chat.normal_chat.willing.willing_manager import get_willing_manager +from src.chat.willing.willing_manager import get_willing_manager from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.normal_message_sender import message_manager from src.chat.message_receive.storage import MessageStorage diff --git a/src/plugins/built_in/core_actions/no_reply.py b/src/plugins/built_in/core_actions/no_reply.py index fa5a2fafe..063175490 100644 --- a/src/plugins/built_in/core_actions/no_reply.py +++ b/src/plugins/built_in/core_actions/no_reply.py @@ -11,8 +11,6 @@ from src.common.logger import get_logger # 导入API模块 - 标准Python包方式 from src.plugin_system.apis import message_api from src.config.config import global_config -from src.chat.memory_system.Hippocampus import hippocampus_manager -import math logger = get_logger("core_actions") From 1dc0bd0d8177d26ea437548165f6fc057557b831 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sat, 12 Jul 2025 01:26:49 +0800 Subject: [PATCH 11/21] fix:ruff --- src/chat/focus_chat/heartFC_chat.py | 5 +---- src/chat/heart_flow/sub_heartflow.py | 3 +-- src/chat/message_receive/uni_message_sender.py | 1 - src/chat/replyer/default_generator.py | 4 +--- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index 13361700d..968ce8dc0 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -2,9 +2,8 @@ import asyncio import time import traceback from collections import deque -from typing import Optional, Deque, Callable, Awaitable +from typing import Optional, Deque -from sqlalchemy import False_ from src.chat.message_receive.chat_stream import get_chat_manager from rich.traceback import install from src.chat.utils.prompt_builder import global_prompt_manager @@ -21,8 +20,6 @@ from random import random from src.chat.focus_chat.hfc_utils import create_thinking_message_from_dict, add_messages_to_manager,get_recent_message_stats,cleanup_thinking_message_by_id from src.person_info.person_info import get_person_info_manager from src.plugin_system.apis import generator_api -from ..message_receive.message import MessageThinking -from src.chat.message_receive.normal_message_sender import message_manager from src.chat.willing.willing_manager import get_willing_manager from .priority_manager import PriorityManager from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat_inclusive diff --git a/src/chat/heart_flow/sub_heartflow.py b/src/chat/heart_flow/sub_heartflow.py index 2b55f9fe6..631b0aaec 100644 --- a/src/chat/heart_flow/sub_heartflow.py +++ b/src/chat/heart_flow/sub_heartflow.py @@ -1,9 +1,8 @@ import asyncio import time -from typing import Optional, List, Tuple +from typing import Optional import traceback from src.common.logger import get_logger -from src.chat.message_receive.message import MessageRecv from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.focus_chat.heartFC_chat import HeartFChatting from src.chat.utils.utils import get_chat_type_and_target_info diff --git a/src/chat/message_receive/uni_message_sender.py b/src/chat/message_receive/uni_message_sender.py index 1102ab655..07eaaad97 100644 --- a/src/chat/message_receive/uni_message_sender.py +++ b/src/chat/message_receive/uni_message_sender.py @@ -1,5 +1,4 @@ import asyncio -from typing import Dict # 重新导入类型 from src.chat.message_receive.message import MessageSending from src.common.message.api import get_global_api from src.chat.message_receive.storage import MessageStorage diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 6fb54cfd7..974ed9721 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -1,10 +1,9 @@ import traceback from typing import List, Optional, Dict, Any, Tuple -from src.chat.message_receive.message import MessageRecv, MessageThinking, MessageSending +from src.chat.message_receive.message import MessageRecv, MessageSending from src.chat.message_receive.message import Seg # Local import needed after move from src.chat.message_receive.message import UserInfo -from src.chat.message_receive.chat_stream import get_chat_manager from src.common.logger import get_logger from src.llm_models.utils_model import LLMRequest from src.config.config import global_config @@ -12,7 +11,6 @@ from src.chat.utils.timer_calculator import Timer # <--- Import Timer from src.chat.message_receive.uni_message_sender import HeartFCSender from src.chat.utils.utils import get_chat_type_and_target_info from src.chat.message_receive.chat_stream import ChatStream -from src.chat.focus_chat.hfc_utils import parse_thinking_id_to_timestamp from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat import time From 3d17df89a29b12281c131688b807e3e0931cbb80 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 12 Jul 2025 10:16:50 +0800 Subject: [PATCH 12/21] fix typo --- .../express/{exprssion_learner.py => expression_learner.py} | 0 src/chat/express/expression_selector.py | 2 +- src/main.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/chat/express/{exprssion_learner.py => expression_learner.py} (100%) diff --git a/src/chat/express/exprssion_learner.py b/src/chat/express/expression_learner.py similarity index 100% rename from src/chat/express/exprssion_learner.py rename to src/chat/express/expression_learner.py diff --git a/src/chat/express/expression_selector.py b/src/chat/express/expression_selector.py index b85f53b79..cd1948f6c 100644 --- a/src/chat/express/expression_selector.py +++ b/src/chat/express/expression_selector.py @@ -1,4 +1,4 @@ -from .exprssion_learner import get_expression_learner +from .expression_learner import get_expression_learner import random from typing import List, Dict, Tuple from json_repair import repair_json diff --git a/src/main.py b/src/main.py index 64129814e..d481c7d03 100644 --- a/src/main.py +++ b/src/main.py @@ -2,7 +2,7 @@ import asyncio import time from maim_message import MessageServer -from src.chat.express.exprssion_learner import get_expression_learner +from src.chat.express.expression_learner import get_expression_learner from src.common.remote import TelemetryHeartBeatTask from src.manager.async_task_manager import async_task_manager from src.chat.utils.statistic import OnlineTimeRecordTask, StatisticOutputTask From 8fae6272bcd9ca98c1ecf942c90dffb24b072511 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sat, 12 Jul 2025 16:12:30 +0800 Subject: [PATCH 13/21] =?UTF-8?q?feat=EF=BC=9Anormal=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E4=B8=80=E7=A7=8D=E7=AE=80=E6=B4=81=E7=9A=84=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E5=88=B0focus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/focus_chat/focus_loop_info.py | 91 ---------- src/chat/focus_chat/heartFC_chat.py | 192 ++++---------------- src/chat/focus_chat/hfc_utils.py | 32 ---- src/chat/heart_flow/heartflow.py | 17 +- src/chat/planner_actions/action_modifier.py | 12 +- src/chat/utils/chat_message_builder.py | 13 +- src/common/message_repository.py | 7 +- 7 files changed, 62 insertions(+), 302 deletions(-) delete mode 100644 src/chat/focus_chat/focus_loop_info.py diff --git a/src/chat/focus_chat/focus_loop_info.py b/src/chat/focus_chat/focus_loop_info.py deleted file mode 100644 index 342368df7..000000000 --- a/src/chat/focus_chat/focus_loop_info.py +++ /dev/null @@ -1,91 +0,0 @@ -# 定义了来自外部世界的信息 -# 外部世界可以是某个聊天 不同平台的聊天 也可以是任意媒体 -from datetime import datetime -from src.common.logger import get_logger -from src.chat.focus_chat.hfc_utils import CycleDetail -from typing import List -# Import the new utility function - -logger = get_logger("loop_info") - - -# 所有观察的基类 -class FocusLoopInfo: - def __init__(self, observe_id): - self.observe_id = observe_id - self.last_observe_time = datetime.now().timestamp() # 初始化为当前时间 - self.history_loop: List[CycleDetail] = [] - - def add_loop_info(self, loop_info: CycleDetail): - self.history_loop.append(loop_info) - - async def observe(self): - recent_active_cycles: List[CycleDetail] = [] - for cycle in reversed(self.history_loop): - # 只关心实际执行了动作的循环 - # action_taken = cycle.loop_action_info["action_taken"] - # if action_taken: - recent_active_cycles.append(cycle) - if len(recent_active_cycles) == 5: - break - - cycle_info_block = "" - action_detailed_str = "" - consecutive_text_replies = 0 - responses_for_prompt = [] - - cycle_last_reason = "" - - # 检查这最近的活动循环中有多少是连续的文本回复 (从最近的开始看) - for cycle in recent_active_cycles: - action_result = cycle.loop_plan_info.get("action_result", {}) - action_type = action_result.get("action_type", "unknown") - action_reasoning = action_result.get("reasoning", "未提供理由") - is_taken = cycle.loop_action_info.get("action_taken", False) - action_taken_time = cycle.loop_action_info.get("taken_time", 0) - action_taken_time_str = ( - datetime.fromtimestamp(action_taken_time).strftime("%H:%M:%S") if action_taken_time > 0 else "未知时间" - ) - if action_reasoning != cycle_last_reason: - cycle_last_reason = action_reasoning - action_reasoning_str = f"你选择这个action的原因是:{action_reasoning}" - else: - action_reasoning_str = "" - - if action_type == "reply": - consecutive_text_replies += 1 - response_text = cycle.loop_action_info.get("reply_text", "") - responses_for_prompt.append(response_text) - - if is_taken: - action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}')。{action_reasoning_str}\n" - else: - action_detailed_str += f"{action_taken_time_str}时,你选择回复(action:{action_type},内容是:'{response_text}'),但是动作失败了。{action_reasoning_str}\n" - elif action_type == "no_reply": - pass - else: - if is_taken: - action_detailed_str += ( - f"{action_taken_time_str}时,你选择执行了(action:{action_type}),{action_reasoning_str}\n" - ) - else: - action_detailed_str += f"{action_taken_time_str}时,你选择执行了(action:{action_type}),但是动作失败了。{action_reasoning_str}\n" - - if action_detailed_str: - cycle_info_block = f"\n你最近做的事:\n{action_detailed_str}\n" - else: - cycle_info_block = "\n" - - # 获取history_loop中最新添加的 - if self.history_loop: - last_loop = self.history_loop[0] - start_time = last_loop.start_time - end_time = last_loop.end_time - if start_time is not None and end_time is not None: - time_diff = int(end_time - start_time) - if time_diff > 60: - cycle_info_block += f"距离你上一次阅读消息并思考和规划,已经过去了{int(time_diff / 60)}分钟\n" - else: - cycle_info_block += f"距离你上一次阅读消息并思考和规划,已经过去了{time_diff}秒\n" - else: - cycle_info_block += "你还没看过消息\n" diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index 968ce8dc0..fc5418ed3 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -2,14 +2,13 @@ import asyncio import time import traceback from collections import deque -from typing import Optional, Deque +from typing import Optional, Deque, List from src.chat.message_receive.chat_stream import get_chat_manager from rich.traceback import install from src.chat.utils.prompt_builder import global_prompt_manager from src.common.logger import get_logger from src.chat.utils.timer_calculator import Timer -from src.chat.focus_chat.focus_loop_info import FocusLoopInfo from src.chat.planner_actions.planner import ActionPlanner from src.chat.planner_actions.action_modifier import ActionModifier from src.chat.planner_actions.action_manager import ActionManager @@ -22,7 +21,7 @@ from src.person_info.person_info import get_person_info_manager from src.plugin_system.apis import generator_api from src.chat.willing.willing_manager import get_willing_manager from .priority_manager import PriorityManager -from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat_inclusive +from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat @@ -88,8 +87,6 @@ class HeartFChatting: self.loop_mode = "normal" - self.recent_replies = [] - # 新增:消息计数器和疲惫阈值 self._message_count = 0 # 发送的消息计数 # 基于exit_focus_threshold动态计算疲惫阈值 @@ -97,7 +94,6 @@ class HeartFChatting: self._message_threshold = max(10, int(30 * global_config.chat.exit_focus_threshold)) self._fatigue_triggered = False # 是否已触发疲惫退出 - self.loop_info: FocusLoopInfo = FocusLoopInfo(observe_id=self.stream_id) self.action_manager = ActionManager() self.action_planner = ActionPlanner(chat_id=self.stream_id, action_manager=self.action_manager) @@ -108,8 +104,8 @@ class HeartFChatting: self._loop_task: Optional[asyncio.Task] = None # 主循环任务 # 添加循环信息管理相关的属性 + self.history_loop: List[CycleDetail] = [] self._cycle_counter = 0 - self._cycle_history: Deque[CycleDetail] = deque(maxlen=10) # 保留最近10个循环的信息 self._current_cycle_detail: Optional[CycleDetail] = None self.reply_timeout_count = 0 @@ -118,30 +114,26 @@ class HeartFChatting: self.last_read_time = time.time()-1 - self.willing_amplifier = 1 - - self.action_type: Optional[str] = None # 当前动作类型 - self.is_parallel_action: bool = False # 是否是可并行动作 + self.willing_manager = get_willing_manager() - self._chat_task: Optional[asyncio.Task] = None - self._priority_chat_task: Optional[asyncio.Task] = None # for priority mode consumer self.reply_mode = self.chat_stream.context.get_priority_mode() if self.reply_mode == "priority": self.priority_manager = PriorityManager( normal_queue_max_size=5, ) + self.loop_mode = "priority" else: self.priority_manager = None - self.willing_manager = get_willing_manager() - - logger.info( f"{self.log_prefix} HeartFChatting 初始化完成,消息疲惫阈值: {self._message_threshold}条(基于exit_focus_threshold={global_config.chat.exit_focus_threshold}计算,仅在auto模式下生效)" ) + + + self.energy_value = 100 async def start(self): """检查是否需要启动主循环,如果未激活则启动。""" @@ -152,8 +144,6 @@ class HeartFChatting: return try: - # 重置消息计数器,开始新的focus会话 - self.reset_message_count() # 标记为活动状态,防止重复启动 self.running = True @@ -178,26 +168,20 @@ class HeartFChatting: else: logger.info(f"{self.log_prefix} HeartFChatting: 脱离了聊天 (外部停止)") except asyncio.CancelledError: - logger.info(f"{self.log_prefix} HeartFChatting: 脱离了聊天(任务取消)") - finally: - self.running = False - self._loop_task = None + logger.info(f"{self.log_prefix} HeartFChatting: 结束了聊天") def start_cycle(self): self._cycle_counter += 1 self._current_cycle_detail = CycleDetail(self._cycle_counter) - self._current_cycle_detail.prefix = self.log_prefix - thinking_id = "tid" + str(round(time.time(), 2)) - self._current_cycle_detail.set_thinking_id(thinking_id) + self._current_cycle_detail.thinking_id = "tid" + str(round(time.time(), 2)) cycle_timers = {} - return cycle_timers, thinking_id + return cycle_timers, self._current_cycle_detail.thinking_id def end_cycle(self,loop_info,cycle_timers): self._current_cycle_detail.set_loop_info(loop_info) - self.loop_info.add_loop_info(self._current_cycle_detail) + self.history_loop.append(self._current_cycle_detail) self._current_cycle_detail.timers = cycle_timers - self._current_cycle_detail.complete_cycle() - self._cycle_history.append(self._current_cycle_detail) + self._current_cycle_detail.end_time = time.time() def print_cycle_info(self,cycle_timers): # 记录循环信息和计时器结果 @@ -217,28 +201,24 @@ class HeartFChatting: async def _loopbody(self): if self.loop_mode == "focus": - logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次观察") return await self._observe() elif self.loop_mode == "normal": - now = time.time() - new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( - chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" + new_messages_data = get_raw_msg_by_timestamp_with_chat( + chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=time.time(),limit=10,limit_mode="earliest",fliter_bot=True ) - if new_messages_data: - self.last_read_time = now + if len(new_messages_data) > 5: + self.loop_mode = "focus" + return True - for msg_data in new_messages_data: - try: - self.adjust_reply_frequency() - logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次循环") - await self.normal_response(msg_data) - # TODO: 这个地方可能导致阻塞,需要优化 - return True - except Exception as e: - logger.error(f"[{self.log_prefix}] 处理消息时出错: {e} {traceback.format_exc()}") - else: - await asyncio.sleep(0.1) + if new_messages_data: + earliest_messages_data = new_messages_data[0] + self.last_read_time = earliest_messages_data.get("time") + + await self.normal_response(earliest_messages_data) + return True + + await asyncio.sleep(1) return True @@ -248,14 +228,17 @@ class HeartFChatting: # 创建新的循环信息 cycle_timers, thinking_id = self.start_cycle() - await create_thinking_message_from_dict(message_data,self.chat_stream,thinking_id) + logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考") + + if message_data: + await create_thinking_message_from_dict(message_data,self.chat_stream,thinking_id) async with global_prompt_manager.async_message_scope( self.chat_stream.context.get_template_name() ): loop_start_time = time.time() - await self.loop_info.observe() + # await self.loop_info.observe() await self.relationship_builder.build_relation() # 第一步:动作修改 @@ -263,7 +246,7 @@ class HeartFChatting: try: if self.loop_mode == "focus": await self.action_modifier.modify_actions( - loop_info=self.loop_info, + history_loop=self.history_loop, mode="focus", ) elif self.loop_mode == "normal": @@ -317,12 +300,11 @@ class HeartFChatting: if action_type == "no_action": gather_timeout = global_config.chat.thinking_timeout - results = await asyncio.wait_for( - asyncio.gather(gen_task, return_exceptions=True), - timeout=gather_timeout, - ) - response_set = results[0] - + try: + response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout) + except asyncio.TimeoutError: + response_set = None + if response_set: content = " ".join([item[1] for item in response_set if item[0] == "text"]) @@ -334,7 +316,7 @@ class HeartFChatting: logger.warning(f"[{self.log_prefix}] 模型未生成回复内容") elif action_type not in ["no_action"] and not is_parallel: logger.info( - f"[{self.log_prefix}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{self.action_type},不发表回复" + f"[{self.log_prefix}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复" ) # 如果模型未生成回复,移除思考消息 await cleanup_thinking_message_by_id(self.chat_stream.stream_id,thinking_id,self.log_prefix) @@ -350,27 +332,8 @@ class HeartFChatting: return False # 发送回复 (不再需要传入 chat) - first_bot_msg = await add_messages_to_manager(message_data, reply_texts, thinking_id,self.chat_stream.stream_id) + await add_messages_to_manager(message_data, reply_texts, thinking_id,self.chat_stream.stream_id) - # 检查 first_bot_msg 是否为 None (例如思考消息已被移除的情况) - if first_bot_msg: - # 消息段已在接收消息时更新,这里不需要额外处理 - - # 记录回复信息到最近回复列表中 - reply_info = { - "time": time.time(), - "user_message": message_data.get("processed_plain_text"), - "user_info": { - "user_id": message_data.get("user_id"), - "user_nickname": message_data.get("user_nickname"), - }, - "response": response_set, - "is_reference_reply": message_data.get("reply") is not None, # 判断是否为引用回复 - } - self.recent_replies.append(reply_info) - # 保持最近回复历史在限定数量内 - if len(self.recent_replies) > 10: - self.recent_replies = self.recent_replies[-10 :] return response_set if response_set else False @@ -416,6 +379,7 @@ class HeartFChatting: try: while self.running: # 主循环 success = await self._loopbody() + await asyncio.sleep(0.1) if not success: break @@ -531,12 +495,6 @@ class HeartFChatting: return max(10, int(30 / global_config.chat.exit_focus_threshold)) - def reset_message_count(self): - """重置消息计数器(用于重新启动focus模式时)""" - self._message_count = 0 - self._fatigue_triggered = False - logger.info(f"{self.log_prefix} 消息计数器已重置") - async def shutdown(self): """优雅关闭HeartFChatting实例,取消活动循环任务""" logger.info(f"{self.log_prefix} 正在关闭HeartFChatting...") @@ -675,16 +633,6 @@ class HeartFChatting: if message_data.get("is_emoji") or message_data.get("is_picid"): reply_probability = 0 - # 应用疲劳期回复频率调整 - fatigue_multiplier = self._get_fatigue_reply_multiplier() - original_probability = reply_probability - reply_probability *= fatigue_multiplier - - # 如果应用了疲劳调整,记录日志 - if fatigue_multiplier < 1.0: - logger.info( - f"[{self.log_prefix}] 疲劳期回复频率调整: {original_probability * 100:.1f}% -> {reply_probability * 100:.1f}% (系数: {fatigue_multiplier:.2f})" - ) # 打印消息信息 mes_name = self.chat_stream.group_info.group_name if self.chat_stream.group_info else "私聊" @@ -734,63 +682,3 @@ class HeartFChatting: except Exception as e: logger.error(f"[{self.log_prefix}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") return None - - - def _get_fatigue_reply_multiplier(self) -> float: - """获取疲劳期回复频率调整系数 - - Returns: - float: 回复频率调整系数,范围0.5-1.0 - """ - if not self.get_cooldown_progress_callback: - return 1.0 # 没有冷却进度回调,返回正常系数 - - try: - cooldown_progress = self.get_cooldown_progress_callback() - - if cooldown_progress >= 1.0: - return 1.0 # 冷却完成,正常回复频率 - - # 疲劳期间:从0.5逐渐恢复到1.0 - # progress=0时系数为0.5,progress=1时系数为1.0 - multiplier = 0.2 + (0.8 * cooldown_progress) - - return multiplier - except Exception as e: - logger.warning(f"[{self.log_prefix}] 获取疲劳调整系数时出错: {e}") - return 1.0 # 出错时返回正常系数 - - # async def _check_should_switch_to_focus(self) -> bool: - # """ - # 检查是否满足切换到focus模式的条件 - - # Returns: - # bool: 是否应该切换到focus模式 - # """ - # # 检查思考消息堆积情况 - # container = await message_manager.get_container(self.stream_id) - # if container: - # thinking_count = sum(1 for msg in container.messages if isinstance(msg, MessageThinking)) - # if thinking_count >= 4 * global_config.chat.auto_focus_threshold: # 如果堆积超过阈值条思考消息 - # logger.debug(f"[{self.stream_name}] 检测到思考消息堆积({thinking_count}条),切换到focus模式") - # return True - - # if not self.recent_replies: - # return False - - # current_time = time.time() - # time_threshold = 120 / global_config.chat.auto_focus_threshold - # reply_threshold = 6 * global_config.chat.auto_focus_threshold - - # one_minute_ago = current_time - time_threshold - - # # 统计指定时间内的回复数量 - # recent_reply_count = sum(1 for reply in self.recent_replies if reply["time"] > one_minute_ago) - - # should_switch = recent_reply_count > reply_threshold - # if should_switch: - # logger.debug( - # f"[{self.stream_name}] 检测到{time_threshold:.0f}秒内回复数量({recent_reply_count})大于{reply_threshold},满足切换到focus模式条件" - # ) - - # return should_switch \ No newline at end of file diff --git a/src/chat/focus_chat/hfc_utils.py b/src/chat/focus_chat/hfc_utils.py index c36f06a77..4921170d7 100644 --- a/src/chat/focus_chat/hfc_utils.py +++ b/src/chat/focus_chat/hfc_utils.py @@ -23,7 +23,6 @@ class CycleDetail: def __init__(self, cycle_id: int): self.cycle_id = cycle_id - self.prefix = "" self.thinking_id = "" self.start_time = time.time() self.end_time: Optional[float] = None @@ -85,43 +84,12 @@ class CycleDetail: "loop_action_info": convert_to_serializable(self.loop_action_info), } - def complete_cycle(self): - """完成循环,记录结束时间""" - self.end_time = time.time() - - # 处理 prefix,只保留中英文字符和基本标点 - if not self.prefix: - self.prefix = "group" - else: - # 只保留中文、英文字母、数字和基本标点 - allowed_chars = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_") - self.prefix = ( - "".join(char for char in self.prefix if "\u4e00" <= char <= "\u9fff" or char in allowed_chars) - or "group" - ) - - def set_thinking_id(self, thinking_id: str): - """设置思考消息ID""" - self.thinking_id = thinking_id - def set_loop_info(self, loop_info: Dict[str, Any]): """设置循环信息""" self.loop_plan_info = loop_info["loop_plan_info"] self.loop_action_info = loop_info["loop_action_info"] - -def parse_thinking_id_to_timestamp(thinking_id: str) -> float: - """ - 将形如 'tid' 的 thinking_id 解析回 float 时间戳 - 例如: 'tid1718251234.56' -> 1718251234.56 - """ - if not thinking_id.startswith("tid"): - raise ValueError("thinking_id 格式不正确") - ts_str = thinking_id[3:] - return float(ts_str) - - async def create_thinking_message_from_dict(message_data: dict, chat_stream: ChatStream, thinking_id: str) -> str: """创建思考消息""" bot_user_info = UserInfo( diff --git a/src/chat/heart_flow/heartflow.py b/src/chat/heart_flow/heartflow.py index cac19f780..e3c36d2e7 100644 --- a/src/chat/heart_flow/heartflow.py +++ b/src/chat/heart_flow/heartflow.py @@ -1,5 +1,5 @@ import traceback -from src.chat.heart_flow.sub_heartflow import SubHeartflow, ChatState +from src.chat.heart_flow.sub_heartflow import SubHeartflow from src.common.logger import get_logger from typing import Any, Optional from typing import Dict @@ -39,20 +39,5 @@ class Heartflow: traceback.print_exc() return None - async def force_change_subheartflow_status(self, subheartflow_id: str, status: ChatState) -> None: - """强制改变子心流的状态""" - # 这里的 message 是可选的,可能是一个消息对象,也可能是其他类型的数据 - return await self.force_change_state(subheartflow_id, status) - - async def force_change_state(self, subflow_id: Any, target_state: ChatState) -> bool: - """强制改变指定子心流的状态""" - subflow = self.subheartflows.get(subflow_id) - if not subflow: - logger.warning(f"[强制状态转换]尝试转换不存在的子心流{subflow_id} 到 {target_state.value}") - return False - await subflow.change_chat_state(target_state) - logger.info(f"[强制状态转换]子心流 {subflow_id} 已转换到 {target_state.value}") - return True - heartflow = Heartflow() diff --git a/src/chat/planner_actions/action_modifier.py b/src/chat/planner_actions/action_modifier.py index 08ea43b35..d89ce4686 100644 --- a/src/chat/planner_actions/action_modifier.py +++ b/src/chat/planner_actions/action_modifier.py @@ -1,6 +1,6 @@ from typing import List, Optional, Any, Dict from src.common.logger import get_logger -from src.chat.focus_chat.focus_loop_info import FocusLoopInfo +from src.chat.focus_chat.hfc_utils import CycleDetail from src.chat.message_receive.chat_stream import get_chat_manager from src.config.config import global_config from src.llm_models.utils_model import LLMRequest @@ -43,7 +43,7 @@ class ActionModifier: async def modify_actions( self, - loop_info=None, + history_loop=None, mode: str = "focus", message_content: str = "", ): @@ -82,8 +82,8 @@ class ActionModifier: chat_content = chat_content + "\n" + f"现在,最新的消息是:{message_content}" # === 第一阶段:传统观察处理 === - if loop_info: - removals_from_loop = await self.analyze_loop_actions(loop_info) + if history_loop: + removals_from_loop = await self.analyze_loop_actions(history_loop) if removals_from_loop: removals_s1.extend(removals_from_loop) @@ -459,7 +459,7 @@ class ActionModifier: logger.debug(f"{self.log_prefix}动作 {action_name} 未匹配到任何关键词: {activation_keywords}") return False - async def analyze_loop_actions(self, obs: FocusLoopInfo) -> List[tuple[str, str]]: + async def analyze_loop_actions(self, history_loop: List[CycleDetail]) -> List[tuple[str, str]]: """分析最近的循环内容并决定动作的移除 Returns: @@ -469,7 +469,7 @@ class ActionModifier: removals = [] # 获取最近10次循环 - recent_cycles = obs.history_loop[-10:] if len(obs.history_loop) > 10 else obs.history_loop + recent_cycles = history_loop[-10:] if len(history_loop) > 10 else history_loop if not recent_cycles: return removals diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py index c63909b94..a858abd4d 100644 --- a/src/chat/utils/chat_message_builder.py +++ b/src/chat/utils/chat_message_builder.py @@ -28,7 +28,7 @@ def get_raw_msg_by_timestamp( def get_raw_msg_by_timestamp_with_chat( - chat_id: str, timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest" + chat_id: str, timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest", fliter_bot = False ) -> List[Dict[str, Any]]: """获取在特定聊天从指定时间戳到指定时间戳的消息,按时间升序排序,返回消息列表 limit: 限制返回的消息数量,0为不限制 @@ -38,11 +38,11 @@ def get_raw_msg_by_timestamp_with_chat( # 只有当 limit 为 0 时才应用外部 sort sort_order = [("time", 1)] if limit == 0 else None # 直接将 limit_mode 传递给 find_messages - return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode) + return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode, fliter_bot=fliter_bot) def get_raw_msg_by_timestamp_with_chat_inclusive( - chat_id: str, timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest" + chat_id: str, timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest", fliter_bot = False ) -> List[Dict[str, Any]]: """获取在特定聊天从指定时间戳到指定时间戳的消息(包含边界),按时间升序排序,返回消息列表 limit: 限制返回的消息数量,0为不限制 @@ -52,7 +52,8 @@ def get_raw_msg_by_timestamp_with_chat_inclusive( # 只有当 limit 为 0 时才应用外部 sort sort_order = [("time", 1)] if limit == 0 else None # 直接将 limit_mode 传递给 find_messages - return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode) + + return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode, fliter_bot=fliter_bot) def get_raw_msg_by_timestamp_with_chat_users( @@ -580,6 +581,10 @@ def build_readable_actions(actions: List[Dict[str, Any]]) -> str: for action in actions: action_time = action.get("time", current_time) action_name = action.get("action_name", "未知动作") + if action_name == "no_action" or action_name == "no_reply": + continue + + action_prompt_display = action.get("action_prompt_display", "无具体内容") time_diff_seconds = current_time - action_time diff --git a/src/common/message_repository.py b/src/common/message_repository.py index 107ee1c5e..716452784 100644 --- a/src/common/message_repository.py +++ b/src/common/message_repository.py @@ -3,6 +3,7 @@ from src.common.logger import get_logger import traceback from typing import List, Any, Optional from peewee import Model # 添加 Peewee Model 导入 +from src.config.config import global_config logger = get_logger(__name__) @@ -19,6 +20,7 @@ def find_messages( sort: Optional[List[tuple[str, int]]] = None, limit: int = 0, limit_mode: str = "latest", + fliter_bot = False ) -> List[dict[str, Any]]: """ 根据提供的过滤器、排序和限制条件查找消息。 @@ -67,7 +69,10 @@ def find_messages( logger.warning(f"过滤器键 '{key}' 在 Messages 模型中未找到。将跳过此条件。") if conditions: query = query.where(*conditions) - + + if fliter_bot: + query = query.where(Messages.user_id != global_config.bot.qq_account) + if limit > 0: if limit_mode == "earliest": # 获取时间最早的 limit 条记录,已经是正序 From f5cd81ad69660438a300780d356d13dc4e1795e5 Mon Sep 17 00:00:00 2001 From: king-81 Date: Sat, 12 Jul 2025 20:14:19 +0800 Subject: [PATCH 14/21] Update docker-compose.yml --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index bcc8a57a8..2240541c6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,8 +27,8 @@ services: # image: infinitycat/maibot:dev environment: - TZ=Asia/Shanghai -# - EULA_AGREE=bda99dca873f5d8044e9987eac417e01 # 同意EULA -# - PRIVACY_AGREE=42dddb3cbe2b784b45a2781407b298a1 # 同意EULA +# - EULA_AGREE=99f08e0cab0190de853cb6af7d64d4de # 同意EULA +# - PRIVACY_AGREE=9943b855e72199d0f5016ea39052f1b6 # 同意EULA # ports: # - "8000:8000" volumes: From a549034bbc24b899866e5250f08d65ab6e1a6cfa Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sat, 12 Jul 2025 22:36:08 +0800 Subject: [PATCH 15/21] =?UTF-8?q?feat=EF=BC=9A=E4=BF=AE=E5=A4=8Dno=5Freply?= =?UTF-8?q?=E8=B5=B7=E5=A7=8B=E6=97=B6=E9=97=B4=EF=BC=8C=E7=A7=BB=E9=99=A4?= =?UTF-8?q?normal=E6=B6=88=E6=81=AF=E7=AE=A1=E7=90=86=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E5=B9=B6=E8=A1=8C=E7=94=9F=E6=88=90=E5=9B=9E?= =?UTF-8?q?=E5=A4=8D=EF=BC=8C=E4=B8=BAfocus=E6=8F=90=E4=BE=9B=E9=80=80?= =?UTF-8?q?=E5=87=BA=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/focus_chat/heartFC_chat.py | 104 +++++++++++------- src/chat/focus_chat/hfc_utils.py | 69 ------------ src/config/official_configs.py | 5 - src/plugins/built_in/core_actions/no_reply.py | 4 +- src/plugins/built_in/core_actions/plugin.py | 2 +- template/bot_config_template.toml | 2 - 6 files changed, 66 insertions(+), 120 deletions(-) diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index fc5418ed3..8c0368756 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -1,8 +1,7 @@ import asyncio import time import traceback -from collections import deque -from typing import Optional, Deque, List +from typing import Optional, List from src.chat.message_receive.chat_stream import get_chat_manager from rich.traceback import install @@ -16,9 +15,9 @@ from src.config.config import global_config from src.person_info.relationship_builder_manager import relationship_builder_manager from src.chat.focus_chat.hfc_utils import CycleDetail from random import random -from src.chat.focus_chat.hfc_utils import create_thinking_message_from_dict, add_messages_to_manager,get_recent_message_stats,cleanup_thinking_message_by_id +from src.chat.focus_chat.hfc_utils import get_recent_message_stats from src.person_info.person_info import get_person_info_manager -from src.plugin_system.apis import generator_api +from src.plugin_system.apis import generator_api,send_api,message_api from src.chat.willing.willing_manager import get_willing_manager from .priority_manager import PriorityManager from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat @@ -201,14 +200,22 @@ class HeartFChatting: async def _loopbody(self): if self.loop_mode == "focus": + + self.energy_value -= 5 * (1/global_config.chat.exit_focus_threshold) + if self.energy_value <= 0: + self.loop_mode = "normal" + return True + + return await self._observe() elif self.loop_mode == "normal": new_messages_data = get_raw_msg_by_timestamp_with_chat( chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=time.time(),limit=10,limit_mode="earliest",fliter_bot=True ) - if len(new_messages_data) > 5: + if len(new_messages_data) > 4 * global_config.chat.auto_focus_threshold: self.loop_mode = "focus" + self.energy_value = 100 return True if new_messages_data: @@ -228,10 +235,8 @@ class HeartFChatting: # 创建新的循环信息 cycle_timers, thinking_id = self.start_cycle() - logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考") + logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]") - if message_data: - await create_thinking_message_from_dict(message_data,self.chat_stream,thinking_id) async with global_prompt_manager.async_message_scope( self.chat_stream.context.get_template_name() @@ -257,7 +262,14 @@ class HeartFChatting: #如果normal,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的) if self.loop_mode == "normal": - gen_task = asyncio.create_task(self._generate_normal_response(message_data, available_actions)) + person_info_manager = get_person_info_manager() + person_id = person_info_manager.get_person_id( + message_data.get("chat_info_platform"), message_data.get("user_id") + ) + person_name = await person_info_manager.get_value(person_id, "person_name") + reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}" + + gen_task = asyncio.create_task(self._generate_response(message_data, available_actions,reply_to_str)) with Timer("规划器", cycle_timers): @@ -299,6 +311,7 @@ class HeartFChatting: if action_type == "no_action": + # 等待回复生成完毕 gather_timeout = global_config.chat.thinking_timeout try: response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout) @@ -308,7 +321,7 @@ class HeartFChatting: if response_set: content = " ".join([item[1] for item in response_set if item[0] == "text"]) - + # 模型炸了,没有回复内容生成 if not response_set or ( action_type not in ["no_action"] and not is_parallel ): @@ -318,25 +331,15 @@ class HeartFChatting: logger.info( f"[{self.log_prefix}] {global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复" ) - # 如果模型未生成回复,移除思考消息 - await cleanup_thinking_message_by_id(self.chat_stream.stream_id,thinking_id,self.log_prefix) return False logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定的回复内容: {content}") - # 提取回复文本 - reply_texts = [item[1] for item in response_set if item[0] == "text"] - if not reply_texts: - logger.info(f"[{self.log_prefix}] 回复内容中没有文本,不发送消息") - await cleanup_thinking_message_by_id(self.chat_stream.stream_id,thinking_id,self.log_prefix) - return False # 发送回复 (不再需要传入 chat) - await add_messages_to_manager(message_data, reply_texts, thinking_id,self.chat_stream.stream_id) + await self._send_response(response_set, reply_to_str, loop_start_time) - return response_set if response_set else False - - + return True @@ -465,7 +468,7 @@ class HeartFChatting: # 新增:消息计数和疲惫检查 if action == "reply" and success: self._message_count += 1 - current_threshold = self._get_current_fatigue_threshold() + current_threshold = max(10, int(30 / global_config.chat.exit_focus_threshold)) logger.info( f"{self.log_prefix} 已发送第 {self._message_count} 条消息(动态阈值: {current_threshold}, exit_focus_threshold: {global_config.chat.exit_focus_threshold})" ) @@ -486,14 +489,6 @@ class HeartFChatting: return command return "" - def _get_current_fatigue_threshold(self) -> int: - """动态获取当前的疲惫阈值,基于exit_focus_threshold配置 - - Returns: - int: 当前的疲惫阈值 - """ - return max(10, int(30 / global_config.chat.exit_focus_threshold)) - async def shutdown(self): """优雅关闭HeartFChatting实例,取消活动循环任务""" @@ -653,21 +648,14 @@ class HeartFChatting: return True - async def _generate_normal_response( - self, message_data: dict, available_actions: Optional[list] + async def _generate_response( + self, message_data: dict, available_actions: Optional[list],reply_to:str ) -> Optional[list]: """生成普通回复""" try: - person_info_manager = get_person_info_manager() - person_id = person_info_manager.get_person_id( - message_data.get("chat_info_platform"), message_data.get("user_id") - ) - person_name = await person_info_manager.get_value(person_id, "person_name") - reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}" - success, reply_set = await generator_api.generate_reply( chat_stream=self.chat_stream, - reply_to=reply_to_str, + reply_to=reply_to, available_actions=available_actions, enable_tool=global_config.tool.enable_in_normal_chat, request_type="normal.replyer", @@ -682,3 +670,37 @@ class HeartFChatting: except Exception as e: logger.error(f"[{self.log_prefix}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") return None + + + async def _send_response( + self, reply_set, reply_to, thinking_start_time + ): + current_time = time.time() + new_message_count = message_api.count_new_messages( + chat_id=self.chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time + ) + + need_reply = new_message_count >= random.randint(2, 4) + + logger.info( + f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,{'使用' if need_reply else '不使用'}引用回复" + ) + + reply_text = "" + first_replyed = False + for reply_seg in reply_set: + data = reply_seg[1] + if not first_replyed: + if need_reply: + await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, reply_to=reply_to, typing=False) + first_replyed = True + else: + await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, typing=False) + first_replyed = True + else: + await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, typing=True) + reply_text += data + + return reply_text + + \ No newline at end of file diff --git a/src/chat/focus_chat/hfc_utils.py b/src/chat/focus_chat/hfc_utils.py index 4921170d7..a7a4fe122 100644 --- a/src/chat/focus_chat/hfc_utils.py +++ b/src/chat/focus_chat/hfc_utils.py @@ -7,11 +7,7 @@ from typing import Dict, Any from src.config.config import global_config from src.chat.message_receive.message import MessageThinking from src.chat.message_receive.normal_message_sender import message_manager -from typing import List -from maim_message import Seg from src.common.message_repository import count_messages -from ..message_receive.message import MessageSending, MessageSet, message_from_db_dict -from src.chat.message_receive.chat_stream import get_chat_manager @@ -123,71 +119,6 @@ async def cleanup_thinking_message_by_id(chat_id: str, thinking_id: str, log_pre except Exception as e: logger.error(f"{log_prefix} 清理思考消息 {thinking_id} 时出错: {e}") - - -async def add_messages_to_manager( - message_data: dict, response_set: List[str], thinking_id, chat_id - ) -> Optional[MessageSending]: - """发送回复消息""" - - chat_stream = get_chat_manager().get_stream(chat_id) - - container = await message_manager.get_container(chat_id) # 使用 self.stream_id - thinking_message = None - - for msg in container.messages[:]: - # print(msg) - if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: - thinking_message = msg - container.messages.remove(msg) - break - - if not thinking_message: - logger.warning(f"[{chat_id}] 未找到对应的思考消息 {thinking_id},可能已超时被移除") - return None - - thinking_start_time = thinking_message.thinking_start_time - message_set = MessageSet(chat_stream, thinking_id) # 使用 self.chat_stream - - sender_info = UserInfo( - user_id=message_data.get("user_id"), - user_nickname=message_data.get("user_nickname"), - platform=message_data.get("chat_info_platform"), - ) - - reply = message_from_db_dict(message_data) - - - mark_head = False - first_bot_msg = None - for msg in response_set: - if global_config.debug.debug_show_chat_mode: - msg += "ⁿ" - message_segment = Seg(type="text", data=msg) - bot_message = MessageSending( - message_id=thinking_id, - chat_stream=chat_stream, # 使用 self.chat_stream - bot_user_info=UserInfo( - user_id=global_config.bot.qq_account, - user_nickname=global_config.bot.nickname, - platform=message_data.get("chat_info_platform"), - ), - sender_info=sender_info, - message_segment=message_segment, - reply=reply, - is_head=not mark_head, - is_emoji=False, - thinking_start_time=thinking_start_time, - apply_set_reply_logic=True, - ) - if not mark_head: - mark_head = True - first_bot_msg = bot_message - message_set.add_message(bot_message) - - await message_manager.add_message(message_set) - - return first_bot_msg def get_recent_message_stats(minutes: int = 30, chat_id: str = None) -> dict: diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 7e2efbeba..49914139b 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -280,17 +280,12 @@ class NormalChatConfig(ConfigBase): at_bot_inevitable_reply: bool = False """@bot 必然回复""" - enable_planner: bool = False - """是否启用动作规划器""" @dataclass class FocusChatConfig(ConfigBase): """专注聊天配置类""" - think_interval: float = 1 - """思考间隔(秒)""" - consecutive_replies: float = 1 """连续回复能力,值越高,麦麦连续回复的概率越高""" diff --git a/src/plugins/built_in/core_actions/no_reply.py b/src/plugins/built_in/core_actions/no_reply.py index 99337e515..f68652d5c 100644 --- a/src/plugins/built_in/core_actions/no_reply.py +++ b/src/plugins/built_in/core_actions/no_reply.py @@ -61,8 +61,8 @@ class NoReplyAction(BaseAction): count = NoReplyAction._consecutive_count reason = self.action_data.get("reason", "") - start_time = time.time() - check_interval = 1.0 # 每秒检查一次 + start_time = self.action_data.get("loop_start_time", time.time()) + check_interval = 0.6 # 每秒检查一次 # 随机生成本次等待需要的新消息数量阈值 exit_message_count_threshold = random.randint(self._min_exit_message_count, self._max_exit_message_count) diff --git a/src/plugins/built_in/core_actions/plugin.py b/src/plugins/built_in/core_actions/plugin.py index 548902225..8ef6f75fe 100644 --- a/src/plugins/built_in/core_actions/plugin.py +++ b/src/plugins/built_in/core_actions/plugin.py @@ -98,7 +98,7 @@ class ReplyAction(BaseAction): ) # 根据新消息数量决定是否使用reply_to - need_reply = new_message_count >= random.randint(2, 5) + need_reply = new_message_count >= random.randint(2, 4) logger.info( f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,{'使用' if need_reply else '不使用'}引用回复" ) diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index d4c158f65..f4470060b 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -119,10 +119,8 @@ willing_mode = "classical" # 回复意愿模式 —— 经典模式:classical response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数 mentioned_bot_inevitable_reply = true # 提及 bot 必然回复 at_bot_inevitable_reply = true # @bot 必然回复(包含提及) -enable_planner = true # 是否启用动作规划器(与focus_chat共享actions) [focus_chat] #专注聊天 -think_interval = 3 # 思考间隔 单位秒,可以有效减少消耗 consecutive_replies = 1 # 连续回复能力,值越高,麦麦连续回复的概率越高 [tool] From b58637bccd9f2a63c43108e979cae72efe9716e3 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sat, 12 Jul 2025 22:38:21 +0800 Subject: [PATCH 16/21] =?UTF-8?q?remove=EF=BC=9A=E5=BD=BB=E5=BA=95?= =?UTF-8?q?=E7=A7=BB=E9=99=A4normal=E6=B6=88=E6=81=AF=E5=8F=91=E9=80=81?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/focus_chat/hfc_utils.py | 36 -- src/chat/message_receive/__init__.py | 2 - .../message_receive/normal_message_sender.py | 310 ------------------ src/main.py | 4 - 4 files changed, 352 deletions(-) delete mode 100644 src/chat/message_receive/normal_message_sender.py diff --git a/src/chat/focus_chat/hfc_utils.py b/src/chat/focus_chat/hfc_utils.py index a7a4fe122..f7b9fdc93 100644 --- a/src/chat/focus_chat/hfc_utils.py +++ b/src/chat/focus_chat/hfc_utils.py @@ -5,8 +5,6 @@ from src.chat.message_receive.message import UserInfo from src.common.logger import get_logger from typing import Dict, Any from src.config.config import global_config -from src.chat.message_receive.message import MessageThinking -from src.chat.message_receive.normal_message_sender import message_manager from src.common.message_repository import count_messages @@ -86,40 +84,6 @@ class CycleDetail: self.loop_action_info = loop_info["loop_action_info"] -async def create_thinking_message_from_dict(message_data: dict, chat_stream: ChatStream, thinking_id: str) -> str: - """创建思考消息""" - bot_user_info = UserInfo( - user_id=global_config.bot.qq_account, - user_nickname=global_config.bot.nickname, - platform=message_data.get("chat_info_platform"), - ) - - thinking_message = MessageThinking( - message_id=thinking_id, - chat_stream=chat_stream, - bot_user_info=bot_user_info, - reply=None, - thinking_start_time=time.time(), - timestamp=time.time(), - ) - - await message_manager.add_message(thinking_message) - return thinking_id - -async def cleanup_thinking_message_by_id(chat_id: str, thinking_id: str, log_prefix: str): - """根据ID清理思考消息""" - try: - container = await message_manager.get_container(chat_id) - if container: - for msg in container.messages[:]: - if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: - container.messages.remove(msg) - logger.info(f"{log_prefix}已清理思考消息 {thinking_id}") - break - except Exception as e: - logger.error(f"{log_prefix} 清理思考消息 {thinking_id} 时出错: {e}") - - def get_recent_message_stats(minutes: int = 30, chat_id: str = None) -> dict: """ diff --git a/src/chat/message_receive/__init__.py b/src/chat/message_receive/__init__.py index d01bea726..44b9eee36 100644 --- a/src/chat/message_receive/__init__.py +++ b/src/chat/message_receive/__init__.py @@ -1,12 +1,10 @@ from src.chat.emoji_system.emoji_manager import get_emoji_manager from src.chat.message_receive.chat_stream import get_chat_manager -from src.chat.message_receive.normal_message_sender import message_manager from src.chat.message_receive.storage import MessageStorage __all__ = [ "get_emoji_manager", "get_chat_manager", - "message_manager", "MessageStorage", ] diff --git a/src/chat/message_receive/normal_message_sender.py b/src/chat/message_receive/normal_message_sender.py deleted file mode 100644 index c8bf72107..000000000 --- a/src/chat/message_receive/normal_message_sender.py +++ /dev/null @@ -1,310 +0,0 @@ -# src/plugins/chat/message_sender.py -import asyncio -import time -from asyncio import Task -from typing import Union -from src.common.message.api import get_global_api - -# from ...common.database import db # 数据库依赖似乎不需要了,注释掉 -from .message import MessageSending, MessageThinking, MessageSet - -from src.chat.message_receive.storage import MessageStorage -from ..utils.utils import truncate_message, calculate_typing_time, count_messages_between - -from src.common.logger import get_logger -from rich.traceback import install -import traceback - -install(extra_lines=3) - - -logger = get_logger("sender") - - -async def send_via_ws(message: MessageSending) -> None: - """通过 WebSocket 发送消息""" - try: - await get_global_api().send_message(message) - except Exception as e: - logger.error(f"WS发送失败: {e}") - raise ValueError(f"未找到平台:{message.message_info.platform} 的url配置,请检查配置文件") from e - - -async def send_message( - message: MessageSending, -) -> None: - """发送消息(核心发送逻辑)""" - - # --- 添加计算打字和延迟的逻辑 (从 heartflow_message_sender 移动并调整) --- - typing_time = calculate_typing_time( - input_string=message.processed_plain_text, - thinking_start_time=message.thinking_start_time, - is_emoji=message.is_emoji, - ) - # logger.debug(f"{message.processed_plain_text},{typing_time},计算输入时间结束") # 减少日志 - await asyncio.sleep(typing_time) - # logger.debug(f"{message.processed_plain_text},{typing_time},等待输入时间结束") # 减少日志 - # --- 结束打字延迟 --- - - message_preview = truncate_message(message.processed_plain_text) - - try: - await send_via_ws(message) - logger.info(f"发送消息 '{message_preview}' 成功") # 调整日志格式 - except Exception as e: - logger.error(f"发送消息 '{message_preview}' 失败: {str(e)}") - - -class MessageSender: - """发送器 (不再是单例)""" - - def __init__(self): - self.message_interval = (0.5, 1) # 消息间隔时间范围(秒) - self.last_send_time = 0 - self._current_bot = None - - def set_bot(self, bot): - """设置当前bot实例""" - pass - - -class MessageContainer: - """单个聊天流的发送/思考消息容器""" - - def __init__(self, chat_id: str, max_size: int = 100): - self.chat_id = chat_id - self.max_size = max_size - self.messages: list[MessageThinking | MessageSending] = [] # 明确类型 - self.last_send_time = 0 - self.thinking_wait_timeout = 20 # 思考等待超时时间(秒) - 从旧 sender 合并 - - def count_thinking_messages(self) -> int: - """计算当前容器中思考消息的数量""" - return sum(1 for msg in self.messages if isinstance(msg, MessageThinking)) - - def get_timeout_sending_messages(self) -> list[MessageSending]: - """获取所有超时的MessageSending对象(思考时间超过20秒),按thinking_start_time排序 - 从旧 sender 合并""" - current_time = time.time() - timeout_messages = [] - - for msg in self.messages: - # 只检查 MessageSending 类型 - if isinstance(msg, MessageSending): - # 确保 thinking_start_time 有效 - if msg.thinking_start_time and current_time - msg.thinking_start_time > self.thinking_wait_timeout: - timeout_messages.append(msg) - - # 按thinking_start_time排序,时间早的在前面 - timeout_messages.sort(key=lambda x: x.thinking_start_time) - return timeout_messages - - def get_earliest_message(self): - """获取thinking_start_time最早的消息对象""" - if not self.messages: - return None - earliest_time = float("inf") - earliest_message = None - for msg in self.messages: - # 确保消息有 thinking_start_time 属性 - msg_time = getattr(msg, "thinking_start_time", float("inf")) - if msg_time < earliest_time: - earliest_time = msg_time - earliest_message = msg - return earliest_message - - def add_message(self, message: Union[MessageThinking, MessageSending, MessageSet]): - """添加消息到队列""" - if isinstance(message, MessageSet): - for single_message in message.messages: - self.messages.append(single_message) - else: - self.messages.append(message) - - def remove_message(self, message_to_remove: Union[MessageThinking, MessageSending]): - """移除指定的消息对象,如果消息存在则返回True,否则返回False""" - try: - _initial_len = len(self.messages) - # 使用列表推导式或 message_filter 创建新列表,排除要删除的元素 - # self.messages = [msg for msg in self.messages if msg is not message_to_remove] - # 或者直接 remove (如果确定对象唯一性) - if message_to_remove in self.messages: - self.messages.remove(message_to_remove) - return True - # logger.debug(f"Removed message {getattr(message_to_remove, 'message_info', {}).get('message_id', 'UNKNOWN')}. Old len: {initial_len}, New len: {len(self.messages)}") - # return len(self.messages) < initial_len - return False - - except Exception as e: - logger.exception(f"移除消息时发生错误: {e}") - return False - - def has_messages(self) -> bool: - """检查是否有待发送的消息""" - return bool(self.messages) - - def get_all_messages(self) -> list[MessageThinking | MessageSending]: - """获取所有消息""" - return list(self.messages) # 返回副本 - - -class MessageManager: - """管理所有聊天流的消息容器 (不再是单例)""" - - def __init__(self): - self._processor_task: Task | None = None - self.containers: dict[str, MessageContainer] = {} - self.storage = MessageStorage() # 添加 storage 实例 - self._running = True # 处理器运行状态 - self._container_lock = asyncio.Lock() # 保护 containers 字典的锁 - # self.message_sender = MessageSender() # 创建发送器实例 (改为全局实例) - - async def start(self): - """启动后台处理器任务。""" - # 检查是否已有任务在运行,避免重复启动 - if self._processor_task is not None and not self._processor_task.done(): - logger.warning("Processor task already running.") - return - self._processor_task = asyncio.create_task(self._start_processor_loop()) - logger.debug("MessageManager processor task started.") - - def stop(self): - """停止后台处理器任务。""" - self._running = False - if self._processor_task is not None and not self._processor_task.done(): - self._processor_task.cancel() - logger.debug("MessageManager processor task stopping.") - else: - logger.debug("MessageManager processor task not running or already stopped.") - - async def get_container(self, chat_id: str) -> MessageContainer: - """获取或创建聊天流的消息容器 (异步,使用锁)""" - async with self._container_lock: - if chat_id not in self.containers: - self.containers[chat_id] = MessageContainer(chat_id) - return self.containers[chat_id] - - async def add_message(self, message: Union[MessageThinking, MessageSending, MessageSet]) -> None: - """添加消息到对应容器""" - chat_stream = message.chat_stream - if not chat_stream: - logger.error("消息缺少 chat_stream,无法添加到容器") - return # 或者抛出异常 - container = await self.get_container(chat_stream.stream_id) - container.add_message(message) - - async def _handle_sending_message(self, container: MessageContainer, message: MessageSending): - """处理单个 MessageSending 消息 (包含 set_reply 逻辑)""" - try: - _ = message.update_thinking_time() # 更新思考时间 - thinking_start_time = message.thinking_start_time - now_time = time.time() - # logger.debug(f"thinking_start_time:{thinking_start_time},now_time:{now_time}") - thinking_messages_count, thinking_messages_length = count_messages_between( - start_time=thinking_start_time, end_time=now_time, stream_id=message.chat_stream.stream_id - ) - - if ( - message.is_head - and (thinking_messages_count > 3 or thinking_messages_length > 200) - and not message.is_private_message() - ): - logger.debug( - f"[{message.chat_stream.stream_id}] 应用 set_reply 逻辑: {message.processed_plain_text[:20]}..." - ) - message.build_reply() - # --- 结束条件 set_reply --- - - await message.process() # 预处理消息内容 - - # logger.debug(f"{message}") - - # 使用全局 message_sender 实例 - await send_message(message) - await self.storage.store_message(message, message.chat_stream) - - # 移除消息要在发送 *之后* - container.remove_message(message) - # logger.debug(f"[{message.chat_stream.stream_id}] Sent and removed message: {message.message_info.message_id}") - - except Exception as e: - logger.error( - f"[{message.chat_stream.stream_id}] 处理发送消息 {getattr(message.message_info, 'message_id', 'N/A')} 时出错: {e}" - ) - logger.exception("详细错误信息:") - # 考虑是否移除出错的消息,防止无限循环 - removed = container.remove_message(message) - if removed: - logger.warning(f"[{message.chat_stream.stream_id}] 已移除处理出错的消息。") - - async def _process_chat_messages(self, chat_id: str): - """处理单个聊天流消息 (合并后的逻辑)""" - container = await self.get_container(chat_id) # 获取容器是异步的了 - - if container.has_messages(): - message_earliest = container.get_earliest_message() - - if not message_earliest: # 如果最早消息为空,则退出 - return - - if isinstance(message_earliest, MessageThinking): - # --- 处理思考消息 (来自旧 sender) --- - message_earliest.update_thinking_time() - thinking_time = message_earliest.thinking_time - # 减少控制台刷新频率或只在时间显著变化时打印 - if int(thinking_time) % 5 == 0: # 每5秒打印一次 - print( - f"消息 {message_earliest.message_info.message_id} 正在思考中,已思考 {int(thinking_time)} 秒\r", - end="", - flush=True, - ) - - elif isinstance(message_earliest, MessageSending): - # --- 处理发送消息 --- - await self._handle_sending_message(container, message_earliest) - - # --- 处理超时发送消息 (来自旧 sender) --- - # 在处理完最早的消息后,检查是否有超时的发送消息 - timeout_sending_messages = container.get_timeout_sending_messages() - if timeout_sending_messages: - logger.debug(f"[{chat_id}] 发现 {len(timeout_sending_messages)} 条超时的发送消息") - for msg in timeout_sending_messages: - # 确保不是刚刚处理过的最早消息 (虽然理论上应该已被移除,但以防万一) - if msg is message_earliest: - continue - logger.info(f"[{chat_id}] 处理超时发送消息: {msg.message_info.message_id}") - await self._handle_sending_message(container, msg) # 复用处理逻辑 - - async def _start_processor_loop(self): - """消息处理器主循环""" - while self._running: - tasks = [] - # 使用异步锁保护迭代器创建过程 - async with self._container_lock: - # 创建 keys 的快照以安全迭代 - chat_ids = list(self.containers.keys()) - - for chat_id in chat_ids: - # 为每个 chat_id 创建一个处理任务 - tasks.append(asyncio.create_task(self._process_chat_messages(chat_id))) - - if tasks: - try: - # 等待当前批次的所有任务完成 - await asyncio.gather(*tasks) - except Exception as e: - logger.error(f"消息处理循环 gather 出错: {e}") - print(traceback.format_exc()) - - # 等待一小段时间,避免CPU空转 - try: - await asyncio.sleep(0.1) # 稍微降低轮询频率 - except asyncio.CancelledError: - logger.info("Processor loop sleep cancelled.") - break # 退出循环 - logger.info("MessageManager processor loop finished.") - - -# --- 创建全局实例 --- -message_manager = MessageManager() -message_sender = MessageSender() -# --- 结束全局实例 --- diff --git a/src/main.py b/src/main.py index d7e02dc8f..a457f42e4 100644 --- a/src/main.py +++ b/src/main.py @@ -9,7 +9,6 @@ from src.chat.utils.statistic import OnlineTimeRecordTask, StatisticOutputTask from src.chat.emoji_system.emoji_manager import get_emoji_manager from src.chat.willing.willing_manager import get_willing_manager from src.chat.message_receive.chat_stream import get_chat_manager -from src.chat.message_receive.normal_message_sender import message_manager from src.chat.message_receive.storage import MessageStorage from src.config.config import global_config from src.chat.message_receive.bot import chat_bot @@ -126,9 +125,6 @@ class MainSystem: logger.info("个体特征初始化成功") try: - # 启动全局消息管理器 (负责消息发送/排队) - await message_manager.start() - logger.info("全局消息管理器启动成功") init_time = int(1000 * (time.time() - init_start_time)) logger.info(f"初始化完成,神经元放电{init_time}次") From 6f1add930b034d47e9460e0a593c4044529993cd Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sat, 12 Jul 2025 23:24:02 +0800 Subject: [PATCH 17/21] =?UTF-8?q?feat=EF=BC=9A=E7=BB=9F=E4=B8=80=E5=8C=96?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E5=8A=A8=E4=BD=9C=EF=BC=8C=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/audio/mock_audio.py | 62 -------------- src/chat/focus_chat/heartFC_chat.py | 73 ++++------------- src/chat/message_receive/bot.py | 2 - src/chat/planner_actions/action_manager.py | 23 ------ src/chat/planner_actions/action_modifier.py | 53 +++--------- src/chat/planner_actions/planner.py | 22 ++++- src/chat/replyer/default_generator.py | 19 +++-- src/config/config.py | 2 +- src/config/official_configs.py | 6 -- src/experimental/PFC/message_sender.py | 81 ------------------- src/mood/mood_manager.py | 4 +- src/plugins/built_in/core_actions/no_reply.py | 2 +- src/plugins/built_in/core_actions/plugin.py | 2 +- template/bot_config_template.toml | 6 +- 14 files changed, 67 insertions(+), 290 deletions(-) delete mode 100644 src/audio/mock_audio.py delete mode 100644 src/experimental/PFC/message_sender.py diff --git a/src/audio/mock_audio.py b/src/audio/mock_audio.py deleted file mode 100644 index 9772fdad9..000000000 --- a/src/audio/mock_audio.py +++ /dev/null @@ -1,62 +0,0 @@ -import asyncio -from src.common.logger import get_logger - -logger = get_logger("MockAudio") - - -class MockAudioPlayer: - """ - 一个模拟的音频播放器,它会根据音频数据的"长度"来模拟播放时间。 - """ - - def __init__(self, audio_data: bytes): - self._audio_data = audio_data - # 模拟音频时长:假设每 1024 字节代表 0.5 秒的音频 - self._duration = (len(audio_data) / 1024.0) * 0.5 - - async def play(self): - """模拟播放音频。该过程可以被中断。""" - if self._duration <= 0: - return - logger.info(f"开始播放模拟音频,预计时长: {self._duration:.2f} 秒...") - try: - await asyncio.sleep(self._duration) - logger.info("模拟音频播放完毕。") - except asyncio.CancelledError: - logger.info("音频播放被中断。") - raise # 重新抛出异常,以便上层逻辑可以捕获它 - - -class MockAudioGenerator: - """ - 一个模拟的文本到语音(TTS)生成器。 - """ - - def __init__(self): - # 模拟生成速度:每秒生成的字符数 - self.chars_per_second = 25.0 - - async def generate(self, text: str) -> bytes: - """ - 模拟从文本生成音频数据。该过程可以被中断。 - - Args: - text: 需要转换为音频的文本。 - - Returns: - 模拟的音频数据(bytes)。 - """ - if not text: - return b"" - - generation_time = len(text) / self.chars_per_second - logger.info(f"模拟生成音频... 文本长度: {len(text)}, 预计耗时: {generation_time:.2f} 秒...") - try: - await asyncio.sleep(generation_time) - # 生成虚拟的音频数据,其长度与文本长度成正比 - mock_audio_data = b"\x01\x02\x03" * (len(text) * 40) - logger.info(f"模拟音频生成完毕,数据大小: {len(mock_audio_data) / 1024:.2f} KB。") - return mock_audio_data - except asyncio.CancelledError: - logger.info("音频生成被中断。") - raise # 重新抛出异常 diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index 8c0368756..a9654449e 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -14,7 +14,7 @@ from src.chat.planner_actions.action_manager import ActionManager from src.config.config import global_config from src.person_info.relationship_builder_manager import relationship_builder_manager from src.chat.focus_chat.hfc_utils import CycleDetail -from random import random +import random from src.chat.focus_chat.hfc_utils import get_recent_message_stats from src.person_info.person_info import get_person_info_manager from src.plugin_system.apis import generator_api,send_api,message_api @@ -228,7 +228,15 @@ class HeartFChatting: await asyncio.sleep(1) return True - + + async def build_reply_to_str(self,message_data:dict): + person_info_manager = get_person_info_manager() + person_id = person_info_manager.get_person_id( + message_data.get("chat_info_platform"), message_data.get("user_id") + ) + person_name = await person_info_manager.get_value(person_id, "person_name") + reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}" + return reply_to_str async def _observe(self,message_data:dict = None): @@ -249,42 +257,19 @@ class HeartFChatting: # 第一步:动作修改 with Timer("动作修改", cycle_timers): try: - if self.loop_mode == "focus": - await self.action_modifier.modify_actions( - history_loop=self.history_loop, - mode="focus", - ) - elif self.loop_mode == "normal": - await self.action_modifier.modify_actions(mode="normal") - available_actions = self.action_manager.get_using_actions_for_mode("normal") + await self.action_modifier.modify_actions() + available_actions = self.action_manager.get_using_actions() except Exception as e: logger.error(f"{self.log_prefix} 动作修改失败: {e}") #如果normal,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的) if self.loop_mode == "normal": - person_info_manager = get_person_info_manager() - person_id = person_info_manager.get_person_id( - message_data.get("chat_info_platform"), message_data.get("user_id") - ) - person_name = await person_info_manager.get_value(person_id, "person_name") - reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}" - + reply_to_str = await self.build_reply_to_str(message_data) gen_task = asyncio.create_task(self._generate_response(message_data, available_actions,reply_to_str)) with Timer("规划器", cycle_timers): - if self.loop_mode == "focus": - if self.action_modifier.should_skip_planning_for_no_reply(): - logger.info(f"[{self.log_prefix}] 没有可用动作,跳过规划") - action_type = "no_reply" - else: - plan_result = await self.action_planner.plan(mode="focus") - elif self.loop_mode == "normal": - if self.action_modifier.should_skip_planning_for_no_action(): - logger.info(f"[{self.log_prefix}] 没有可用动作,跳过规划") - action_type = "no_action" - else: - plan_result = await self.action_planner.plan(mode="normal") + plan_result = await self.action_planner.plan(mode=self.loop_mode) @@ -445,9 +430,7 @@ class HeartFChatting: else: success, reply_text = result command = "" - - command = self._count_reply_and_exit_focus_chat(action,success) - + if reply_text == "timeout": self.reply_timeout_count += 1 if self.reply_timeout_count > 5: @@ -464,30 +447,6 @@ class HeartFChatting: traceback.print_exc() return False, "", "" - def _count_reply_and_exit_focus_chat(self,action,success): - # 新增:消息计数和疲惫检查 - if action == "reply" and success: - self._message_count += 1 - current_threshold = max(10, int(30 / global_config.chat.exit_focus_threshold)) - logger.info( - f"{self.log_prefix} 已发送第 {self._message_count} 条消息(动态阈值: {current_threshold}, exit_focus_threshold: {global_config.chat.exit_focus_threshold})" - ) - - # 检查是否达到疲惫阈值(只有在auto模式下才会自动退出) - if ( - global_config.chat.chat_mode == "auto" - and self._message_count >= current_threshold - and not self._fatigue_triggered - ): - self._fatigue_triggered = True - logger.info( - f"{self.log_prefix} [auto模式] 已发送 {self._message_count} 条消息,达到疲惫阈值 {current_threshold},麦麦感到疲惫了,准备退出专注聊天模式" - ) - # 设置系统命令,在下次循环检查时触发退出 - command = "stop_focus_chat" - - return command - return "" async def shutdown(self): @@ -638,7 +597,7 @@ class HeartFChatting: f"{message_data.get('processed_plain_text')}[兴趣:{interested_rate:.2f}][回复概率:{reply_probability * 100:.1f}%]" ) - if random() < reply_probability: + if random.random() < reply_probability: await self.willing_manager.before_generate_reply_handle(message_data.get("message_id")) await self._observe(message_data = message_data) diff --git a/src/chat/message_receive/bot.py b/src/chat/message_receive/bot.py index b460ad99b..7dabd7375 100644 --- a/src/chat/message_receive/bot.py +++ b/src/chat/message_receive/bot.py @@ -8,7 +8,6 @@ from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.message_receive.message import MessageRecv from src.experimental.only_message_process import MessageProcessor from src.chat.message_receive.storage import MessageStorage -from src.experimental.PFC.pfc_manager import PFCManager from src.chat.heart_flow.heartflow_message_processor import HeartFCMessageReceiver from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.config.config import global_config @@ -82,7 +81,6 @@ class ChatBot: # 创建初始化PFC管理器的任务,会在_ensure_started时执行 self.only_process_chat = MessageProcessor() - self.pfc_manager = PFCManager.get_instance() self.s4u_message_processor = S4UMessageProcessor() async def _ensure_started(self): diff --git a/src/chat/planner_actions/action_manager.py b/src/chat/planner_actions/action_manager.py index 3918831ca..25b7fbe42 100644 --- a/src/chat/planner_actions/action_manager.py +++ b/src/chat/planner_actions/action_manager.py @@ -181,29 +181,6 @@ class ActionManager: """获取当前正在使用的动作集合""" return self._using_actions.copy() - def get_using_actions_for_mode(self, mode: str) -> Dict[str, ActionInfo]: - """ - 根据聊天模式获取可用的动作集合 - - Args: - mode: 聊天模式 ("focus", "normal", "all") - - Returns: - Dict[str, ActionInfo]: 在指定模式下可用的动作集合 - """ - filtered_actions = {} - - for action_name, action_info in self._using_actions.items(): - action_mode = action_info.get("mode_enable", "all") - - # 检查动作是否在当前模式下启用 - if action_mode == "all" or action_mode == mode: - filtered_actions[action_name] = action_info - logger.debug(f"动作 {action_name} 在模式 {mode} 下可用 (mode_enable: {action_mode})") - - logger.debug(f"模式 {mode} 下可用动作: {list(filtered_actions.keys())}") - return filtered_actions - def add_action_to_using(self, action_name: str) -> bool: """ 添加已注册的动作到当前使用的动作集 diff --git a/src/chat/planner_actions/action_modifier.py b/src/chat/planner_actions/action_modifier.py index d89ce4686..bd4964a20 100644 --- a/src/chat/planner_actions/action_modifier.py +++ b/src/chat/planner_actions/action_modifier.py @@ -44,7 +44,6 @@ class ActionModifier: async def modify_actions( self, history_loop=None, - mode: str = "focus", message_content: str = "", ): """ @@ -62,7 +61,7 @@ class ActionModifier: removals_s2 = [] self.action_manager.restore_actions() - all_actions = self.action_manager.get_using_actions_for_mode(mode) + all_actions = self.action_manager.get_using_actions() message_list_before_now_half = get_raw_msg_before_timestamp_with_chat( chat_id=self.chat_stream.stream_id, @@ -82,10 +81,10 @@ class ActionModifier: chat_content = chat_content + "\n" + f"现在,最新的消息是:{message_content}" # === 第一阶段:传统观察处理 === - if history_loop: - removals_from_loop = await self.analyze_loop_actions(history_loop) - if removals_from_loop: - removals_s1.extend(removals_from_loop) + # if history_loop: + # removals_from_loop = await self.analyze_loop_actions(history_loop) + # if removals_from_loop: + # removals_s1.extend(removals_from_loop) # 检查动作的关联类型 chat_context = self.chat_stream.context @@ -104,12 +103,11 @@ class ActionModifier: logger.debug(f"{self.log_prefix}开始激活类型判定阶段") # 获取当前使用的动作集(经过第一阶段处理) - current_using_actions = self.action_manager.get_using_actions_for_mode(mode) + current_using_actions = self.action_manager.get_using_actions() # 获取因激活类型判定而需要移除的动作 removals_s2 = await self._get_deactivated_actions_by_type( current_using_actions, - mode, chat_content, ) @@ -124,7 +122,7 @@ class ActionModifier: removals_summary = " | ".join([f"{name}({reason})" for name, reason in all_removals]) logger.info( - f"{self.log_prefix}{mode}模式动作修改流程结束,最终可用动作: {list(self.action_manager.get_using_actions_for_mode(mode).keys())}||移除记录: {removals_summary}" + f"{self.log_prefix} 动作修改流程结束,最终可用动作: {list(self.action_manager.get_using_actions().keys())}||移除记录: {removals_summary}" ) def _check_action_associated_types(self, all_actions, chat_context): @@ -141,7 +139,6 @@ class ActionModifier: async def _get_deactivated_actions_by_type( self, actions_with_info: Dict[str, Any], - mode: str = "focus", chat_content: str = "", ) -> List[tuple[str, str]]: """ @@ -163,7 +160,7 @@ class ActionModifier: random.shuffle(actions_to_check) for action_name, action_info in actions_to_check: - activation_type = f"{mode}_activation_type" + activation_type = "focus_activation_type" activation_type = action_info.get(activation_type, "always") if activation_type == "always": @@ -186,6 +183,11 @@ class ActionModifier: elif activation_type == "llm_judge": llm_judge_actions[action_name] = action_info + elif activation_type == "never": + reason = f"激活类型为never" + deactivated_actions.append((action_name, reason)) + logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: 激活类型为never") + else: logger.warning(f"{self.log_prefix}未知的激活类型: {activation_type},跳过处理") @@ -203,35 +205,6 @@ class ActionModifier: return deactivated_actions - async def process_actions_for_planner( - self, observed_messages_str: str = "", chat_context: Optional[str] = None, extra_context: Optional[str] = None - ) -> Dict[str, Any]: - """ - [已废弃] 此方法现在已被整合到 modify_actions() 中 - - 为了保持向后兼容性而保留,但建议直接使用 ActionManager.get_using_actions() - 规划器应该直接从 ActionManager 获取最终的可用动作集,而不是调用此方法 - - 新的架构: - 1. 主循环调用 modify_actions() 处理完整的动作管理流程 - 2. 规划器直接使用 ActionManager.get_using_actions() 获取最终动作集 - """ - logger.warning( - f"{self.log_prefix}process_actions_for_planner() 已废弃,建议规划器直接使用 ActionManager.get_using_actions()" - ) - - # 为了向后兼容,仍然返回当前使用的动作集 - current_using_actions = self.action_manager.get_using_actions() - all_registered_actions = self.action_manager.get_registered_actions() - - # 构建完整的动作信息 - result = {} - for action_name in current_using_actions.keys(): - if action_name in all_registered_actions: - result[action_name] = all_registered_actions[action_name] - - return result - def _generate_context_hash(self, chat_content: str) -> str: """生成上下文的哈希值用于缓存""" context_content = f"{chat_content}" diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index c088fd78c..47da0d3ba 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -76,7 +76,7 @@ class ActionPlanner: self.last_obs_time_mark = 0.0 - async def plan(self,mode: str = "focus") -> Dict[str, Any]: + async def plan(self,mode:str = "focus") -> Dict[str, Any]: """ 规划器 (Planner): 使用LLM根据上下文决定做出什么动作。 """ @@ -91,7 +91,7 @@ class ActionPlanner: is_group_chat, chat_target_info = get_chat_type_and_target_info(self.chat_id) logger.debug(f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}") - current_available_actions_dict = self.action_manager.get_using_actions_for_mode(mode) + current_available_actions_dict = self.action_manager.get_using_actions() # 获取完整的动作信息 all_registered_actions = self.action_manager.get_registered_actions() @@ -247,7 +247,23 @@ class ActionPlanner: if mode == "focus": by_what = "聊天内容" - no_action_block = "" + no_action_block = """重要说明1: +- 'no_reply' 表示只进行不进行回复,等待合适的回复时机 +- 当你刚刚发送了消息,没有人回复时,选择no_reply +- 当你一次发送了太多消息,为了避免打扰聊天节奏,选择no_reply + +动作:reply +动作描述:参与聊天回复,发送文本进行表达 +- 你想要闲聊或者随便附和 +- 有人提到你 +- 如果你刚刚进行了回复,不要对同一个话题重复回应 +{ + "action": "reply", + "reply_to":"你要回复的对方的发言内容,格式:(用户名:发言内容),可以为none" + "reason":"回复的原因" +} + +""" else: by_what = "聊天内容和用户的最新消息" no_action_block = """重要说明: diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 974ed9721..1d83d2c29 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -29,6 +29,8 @@ from src.tools.tool_executor import ToolExecutor logger = get_logger("replyer") +ENABLE_S2S_MODE = True + def init_prompt(): Prompt("你正在qq群里聊天,下面是群里在聊的内容:", "chat_target_group1") @@ -504,13 +506,14 @@ class DefaultReplyer: show_actions=True, ) - message_list_before_now_half = get_raw_msg_before_timestamp_with_chat( + + message_list_before_short = get_raw_msg_before_timestamp_with_chat( chat_id=chat_id, timestamp=time.time(), - limit=int(global_config.chat.max_context_size * 0.5), + limit=int(global_config.chat.max_context_size * 0.33), ) - chat_talking_prompt_half = build_readable_messages( - message_list_before_now_half, + chat_talking_prompt_short = build_readable_messages( + message_list_before_short, replace_bot_name=True, merge_messages=False, timestamp_mode="relative", @@ -521,14 +524,14 @@ class DefaultReplyer: # 并行执行四个构建任务 task_results = await asyncio.gather( self._time_and_run_task( - self.build_expression_habits(chat_talking_prompt_half, target), "build_expression_habits" + self.build_expression_habits(chat_talking_prompt_short, target), "build_expression_habits" ), self._time_and_run_task( - self.build_relation_info(reply_data, chat_talking_prompt_half), "build_relation_info" + self.build_relation_info(reply_data, chat_talking_prompt_short), "build_relation_info" ), - self._time_and_run_task(self.build_memory_block(chat_talking_prompt_half, target), "build_memory_block"), + self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "build_memory_block"), self._time_and_run_task( - self.build_tool_info(reply_data, chat_talking_prompt_half, enable_tool=enable_tool), "build_tool_info" + self.build_tool_info(reply_data, chat_talking_prompt_short, enable_tool=enable_tool), "build_tool_info" ), ) diff --git a/src/config/config.py b/src/config/config.py index de173a520..c596e9fae 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -51,7 +51,7 @@ TEMPLATE_DIR = os.path.join(PROJECT_ROOT, "template") # 考虑到,实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码 # 对该字段的更新,请严格参照语义化版本规范:https://semver.org/lang/zh-CN/ -MMC_VERSION = "0.8.2-snapshot.1" +MMC_VERSION = "0.9.0-snapshot.1" def update_config(): diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 49914139b..4d4bbd428 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -67,9 +67,6 @@ class RelationshipConfig(ConfigBase): class ChatConfig(ConfigBase): """聊天配置类""" - chat_mode: str = "normal" - """聊天模式""" - max_context_size: int = 18 """上下文长度""" @@ -524,9 +521,6 @@ class TelemetryConfig(ConfigBase): class DebugConfig(ConfigBase): """调试配置类""" - debug_show_chat_mode: bool = False - """是否在回复后显示当前聊天模式""" - show_prompt: bool = False """是否显示prompt""" diff --git a/src/experimental/PFC/message_sender.py b/src/experimental/PFC/message_sender.py deleted file mode 100644 index d0816d8b5..000000000 --- a/src/experimental/PFC/message_sender.py +++ /dev/null @@ -1,81 +0,0 @@ -import time -from typing import Optional -from src.common.logger import get_logger -from src.chat.message_receive.chat_stream import ChatStream -from src.chat.message_receive.message import Message -from maim_message import UserInfo, Seg -from src.chat.message_receive.message import MessageSending, MessageSet -from src.chat.message_receive.normal_message_sender import message_manager -from src.chat.message_receive.storage import MessageStorage -from src.config.config import global_config -from rich.traceback import install - -install(extra_lines=3) - - -logger = get_logger("message_sender") - - -class DirectMessageSender: - """直接消息发送器""" - - def __init__(self, private_name: str): - self.private_name = private_name - self.storage = MessageStorage() - - async def send_message( - self, - chat_stream: ChatStream, - content: str, - reply_to_message: Optional[Message] = None, - ) -> None: - """发送消息到聊天流 - - Args: - chat_stream: 聊天流 - content: 消息内容 - reply_to_message: 要回复的消息(可选) - """ - try: - # 创建消息内容 - segments = Seg(type="seglist", data=[Seg(type="text", data=content)]) - - # 获取麦麦的信息 - bot_user_info = UserInfo( - user_id=global_config.bot.qq_account, - user_nickname=global_config.bot.nickname, - platform=chat_stream.platform, - ) - - # 用当前时间作为message_id,和之前那套sender一样 - message_id = f"dm{round(time.time(), 2)}" - - # 构建消息对象 - message = MessageSending( - message_id=message_id, - chat_stream=chat_stream, - bot_user_info=bot_user_info, - sender_info=reply_to_message.message_info.user_info if reply_to_message else None, - message_segment=segments, - reply=reply_to_message, - is_head=True, - is_emoji=False, - thinking_start_time=time.time(), - ) - - # 处理消息 - await message.process() - - # 不知道有什么用,先留下来了,和之前那套sender一样 - _message_json = message.to_dict() - - # 发送消息 - message_set = MessageSet(chat_stream, message_id) - message_set.add_message(message) - await message_manager.add_message(message_set) - await self.storage.store_message(message, chat_stream) - logger.info(f"[私聊][{self.private_name}]PFC消息已发送: {content}") - - except Exception as e: - logger.error(f"[私聊][{self.private_name}]PFC消息发送失败: {str(e)}") - raise diff --git a/src/mood/mood_manager.py b/src/mood/mood_manager.py index dee8d7cc6..a7b8d7f49 100644 --- a/src/mood/mood_manager.py +++ b/src/mood/mood_manager.py @@ -70,13 +70,15 @@ class ChatMood: else: interest_multiplier = 3 * math.pow(interested_rate, 0.25) - logger.info( + logger.debug( f"base_probability: {base_probability}, time_multiplier: {time_multiplier}, interest_multiplier: {interest_multiplier}" ) update_probability = min(1.0, base_probability * time_multiplier * interest_multiplier) if random.random() > update_probability: return + + logger.info(f"更新情绪状态,感兴趣度: {interested_rate}, 更新概率: {update_probability}") message_time = message.message_info.time message_list_before_now = get_raw_msg_by_timestamp_with_chat_inclusive( diff --git a/src/plugins/built_in/core_actions/no_reply.py b/src/plugins/built_in/core_actions/no_reply.py index f68652d5c..080c717f2 100644 --- a/src/plugins/built_in/core_actions/no_reply.py +++ b/src/plugins/built_in/core_actions/no_reply.py @@ -24,7 +24,7 @@ class NoReplyAction(BaseAction): 2. 累计新消息数量达到随机阈值 (默认5-10条) 则结束等待 """ - focus_activation_type = ActionActivationType.ALWAYS + focus_activation_type = ActionActivationType.NEVER normal_activation_type = ActionActivationType.NEVER mode_enable = ChatMode.FOCUS parallel_action = False diff --git a/src/plugins/built_in/core_actions/plugin.py b/src/plugins/built_in/core_actions/plugin.py index 8ef6f75fe..4c36dca3c 100644 --- a/src/plugins/built_in/core_actions/plugin.py +++ b/src/plugins/built_in/core_actions/plugin.py @@ -34,7 +34,7 @@ class ReplyAction(BaseAction): """回复动作 - 参与聊天回复""" # 激活设置 - focus_activation_type = ActionActivationType.ALWAYS + focus_activation_type = ActionActivationType.NEVER normal_activation_type = ActionActivationType.NEVER mode_enable = ChatMode.FOCUS parallel_action = False diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index f4470060b..5236e8da1 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "3.7.0" +version = "4.0.0" #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #如果你想要修改配置文件,请在修改后将version的值进行变更 @@ -62,14 +62,13 @@ enable_relationship = true # 是否启用关系系统 relation_frequency = 1 # 关系频率,麦麦构建关系的频率 [chat] #麦麦的聊天通用设置 -chat_mode = "normal" # 聊天模式 —— 普通模式:normal,专注模式:focus,auto模式:在普通模式和专注模式之间自动切换 auto_focus_threshold = 1 # 自动切换到专注聊天的阈值,越低越容易进入专注聊天 exit_focus_threshold = 1 # 自动退出专注聊天的阈值,越低越容易退出专注聊天 # 普通模式下,麦麦会针对感兴趣的消息进行回复,token消耗量较低 # 专注模式下,麦麦会进行主动的观察,并给出回复,token消耗量略高,但是回复时机更准确 # 自动模式下,麦麦会根据消息内容自动切换到专注模式或普通模式 -max_context_size = 18 # 上下文长度 +max_context_size = 25 # 上下文长度 thinking_timeout = 20 # 麦麦一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢) replyer_random_probability = 0.5 # 首要replyer模型被选择的概率 @@ -232,7 +231,6 @@ library_log_levels = { "aiohttp" = "WARNING"} # 设置特定库的日志级别 [debug] show_prompt = false # 是否显示prompt -debug_show_chat_mode = false # 是否在回复后显示当前聊天模式 [model] From b74376387a4d620befd6a89f10fb03212ebbf714 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sat, 12 Jul 2025 23:34:52 +0800 Subject: [PATCH 18/21] =?UTF-8?q?feat=EF=BC=9B=E7=8E=B0=E5=9C=A8=E5=8F=AA?= =?UTF-8?q?=E9=9C=80=E8=A6=81activation=5Ftype=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/hello_world_plugin/_manifest.json | 3 +-- src/chat/planner_actions/action_modifier.py | 5 +++-- src/chat/planner_actions/planner.py | 2 +- src/plugins/built_in/tts_plugin/_manifest.json | 3 +-- src/plugins/built_in/vtb_plugin/_manifest.json | 3 +-- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/plugins/hello_world_plugin/_manifest.json b/plugins/hello_world_plugin/_manifest.json index 86f01afc3..b1a4c4eb8 100644 --- a/plugins/hello_world_plugin/_manifest.json +++ b/plugins/hello_world_plugin/_manifest.json @@ -10,8 +10,7 @@ "license": "GPL-v3.0-or-later", "host_application": { - "min_version": "0.8.0", - "max_version": "0.8.0" + "min_version": "0.8.0" }, "homepage_url": "https://github.com/MaiM-with-u/maibot", "repository_url": "https://github.com/MaiM-with-u/maibot", diff --git a/src/chat/planner_actions/action_modifier.py b/src/chat/planner_actions/action_modifier.py index bd4964a20..3c68ab700 100644 --- a/src/chat/planner_actions/action_modifier.py +++ b/src/chat/planner_actions/action_modifier.py @@ -160,8 +160,9 @@ class ActionModifier: random.shuffle(actions_to_check) for action_name, action_info in actions_to_check: - activation_type = "focus_activation_type" - activation_type = action_info.get(activation_type, "always") + activation_type = action_info.get("activation_type", "") + if not activation_type: + activation_type = action_info.get("focus_activation_type", "") if activation_type == "always": continue # 总是激活,无需处理 diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 47da0d3ba..8863c60fa 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -171,7 +171,7 @@ class ActionPlanner: if action == "no_action": reasoning = "normal决定不使用额外动作" - elif action not in current_available_actions: + elif action != "no_reply" and action != "reply" and action not in current_available_actions: logger.warning( f"{self.log_prefix}LLM 返回了当前不可用或无效的动作: '{action}' (可用: {list(current_available_actions.keys())}),将强制使用 'no_reply'" ) diff --git a/src/plugins/built_in/tts_plugin/_manifest.json b/src/plugins/built_in/tts_plugin/_manifest.json index be9f61b0a..05a233757 100644 --- a/src/plugins/built_in/tts_plugin/_manifest.json +++ b/src/plugins/built_in/tts_plugin/_manifest.json @@ -10,8 +10,7 @@ "license": "GPL-v3.0-or-later", "host_application": { - "min_version": "0.8.0", - "max_version": "0.8.10" + "min_version": "0.8.0" }, "homepage_url": "https://github.com/MaiM-with-u/maibot", "repository_url": "https://github.com/MaiM-with-u/maibot", diff --git a/src/plugins/built_in/vtb_plugin/_manifest.json b/src/plugins/built_in/vtb_plugin/_manifest.json index 1cff37136..96f985abd 100644 --- a/src/plugins/built_in/vtb_plugin/_manifest.json +++ b/src/plugins/built_in/vtb_plugin/_manifest.json @@ -9,8 +9,7 @@ }, "license": "GPL-v3.0-or-later", "host_application": { - "min_version": "0.8.0", - "max_version": "0.8.10" + "min_version": "0.8.0" }, "keywords": ["vtb", "vtuber", "emotion", "expression", "virtual", "streamer"], "categories": ["Entertainment", "Virtual Assistant", "Emotion"], From 58ef00865d6786d3aada08d2a29f83afd2cdc132 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sat, 12 Jul 2025 23:35:05 +0800 Subject: [PATCH 19/21] fix rudff --- src/chat/focus_chat/hfc_utils.py | 2 -- src/chat/planner_actions/action_modifier.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/chat/focus_chat/hfc_utils.py b/src/chat/focus_chat/hfc_utils.py index f7b9fdc93..5d8df651d 100644 --- a/src/chat/focus_chat/hfc_utils.py +++ b/src/chat/focus_chat/hfc_utils.py @@ -1,7 +1,5 @@ import time from typing import Optional -from src.chat.message_receive.chat_stream import ChatStream -from src.chat.message_receive.message import UserInfo from src.common.logger import get_logger from typing import Dict, Any from src.config.config import global_config diff --git a/src/chat/planner_actions/action_modifier.py b/src/chat/planner_actions/action_modifier.py index 3c68ab700..fe0941fd9 100644 --- a/src/chat/planner_actions/action_modifier.py +++ b/src/chat/planner_actions/action_modifier.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Any, Dict +from typing import List, Any, Dict from src.common.logger import get_logger from src.chat.focus_chat.hfc_utils import CycleDetail from src.chat.message_receive.chat_stream import get_chat_manager @@ -185,7 +185,7 @@ class ActionModifier: llm_judge_actions[action_name] = action_info elif activation_type == "never": - reason = f"激活类型为never" + reason = "激活类型为never" deactivated_actions.append((action_name, reason)) logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: 激活类型为never") From 80bf8759e09c1975427fd84c1b5b374830629564 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 12 Jul 2025 23:45:43 +0800 Subject: [PATCH 20/21] fix typo --- src/config/official_configs.py | 2 +- src/individuality/individuality.py | 4 ++-- template/bot_config_template.toml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 4d4bbd428..613e447e8 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -48,7 +48,7 @@ class IdentityConfig(ConfigBase): identity_detail: list[str] = field(default_factory=lambda: []) """身份特征""" - compress_indentity: bool = True + compress_identity: bool = True """是否压缩身份,压缩后会精简身份信息,节省token消耗并提高回复性能,但是会丢失一些信息,如果不长,可以关闭""" diff --git a/src/individuality/individuality.py b/src/individuality/individuality.py index 8365c0888..a829def8e 100644 --- a/src/individuality/individuality.py +++ b/src/individuality/individuality.py @@ -335,7 +335,7 @@ class Individuality: # 身份配置哈希 identity_config = { "identity_detail": sorted(identity_detail), - "compress_identity": global_config.identity.compress_indentity, + "compress_identity": global_config.identity.compress_identity, } identity_str = json.dumps(identity_config, sort_keys=True) identity_hash = hashlib.md5(identity_str.encode("utf-8")).hexdigest() @@ -504,7 +504,7 @@ class Individuality: """使用LLM创建压缩版本的impression""" logger.info("正在构建身份.........") - if global_config.identity.compress_indentity: + if global_config.identity.compress_identity: identity_to_compress = [] if identity_detail: identity_to_compress.append(f"身份背景: {'、'.join(identity_detail)}") diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 5236e8da1..7ab5195d1 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "4.0.0" +version = "4.0.1" #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #如果你想要修改配置文件,请在修改后将version的值进行变更 @@ -40,7 +40,7 @@ identity_detail = [ "有橙色的短发", ] -compress_indentity = true # 是否压缩身份,压缩后会精简身份信息,节省token消耗并提高回复性能,但是会丢失一些信息,如果不长,可以关闭 +compress_identity = true # 是否压缩身份,压缩后会精简身份信息,节省token消耗并提高回复性能,但是会丢失一些信息,如果不长,可以关闭 [expression] # 表达方式 From 2d39cefce0a0e96358daeb12f88a90b864f6b9ac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 12 Jul 2025 16:21:28 +0000 Subject: [PATCH 21/21] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/focus_chat/heartFC_chat.py | 168 +++++++----------- src/chat/focus_chat/hfc_utils.py | 2 - src/chat/focus_chat/priority_manager.py | 2 +- .../heart_flow/heartflow_message_processor.py | 4 +- src/chat/heart_flow/sub_heartflow.py | 11 +- src/chat/message_receive/message.py | 9 +- .../message_receive/uni_message_sender.py | 1 - src/chat/normal_chat/normal_chat.py | 84 +++++---- src/chat/planner_actions/action_modifier.py | 12 +- src/chat/planner_actions/planner.py | 2 +- src/chat/replyer/default_generator.py | 1 - src/chat/utils/chat_message_builder.py | 27 ++- src/common/message_repository.py | 6 +- src/config/official_configs.py | 1 - src/main.py | 1 - src/mood/mood_manager.py | 2 +- src/plugin_system/apis/message_api.py | 24 ++- 17 files changed, 168 insertions(+), 189 deletions(-) diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index a9654449e..9d22a593e 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -17,13 +17,12 @@ from src.chat.focus_chat.hfc_utils import CycleDetail import random from src.chat.focus_chat.hfc_utils import get_recent_message_stats from src.person_info.person_info import get_person_info_manager -from src.plugin_system.apis import generator_api,send_api,message_api +from src.plugin_system.apis import generator_api, send_api, message_api from src.chat.willing.willing_manager import get_willing_manager from .priority_manager import PriorityManager from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat - ERROR_LOOP_INFO = { "loop_plan_info": { "action_result": { @@ -85,7 +84,7 @@ class HeartFChatting: self.relationship_builder = relationship_builder_manager.get_or_create_builder(self.stream_id) self.loop_mode = "normal" - + # 新增:消息计数器和疲惫阈值 self._message_count = 0 # 发送的消息计数 # 基于exit_focus_threshold动态计算疲惫阈值 @@ -93,7 +92,6 @@ class HeartFChatting: self._message_threshold = max(10, int(30 * global_config.chat.exit_focus_threshold)) self._fatigue_triggered = False # 是否已触发疲惫退出 - self.action_manager = ActionManager() self.action_planner = ActionPlanner(chat_id=self.stream_id, action_manager=self.action_manager) self.action_modifier = ActionModifier(action_manager=self.action_manager, chat_id=self.stream_id) @@ -109,14 +107,12 @@ class HeartFChatting: self.reply_timeout_count = 0 self.plan_timeout_count = 0 - - self.last_read_time = time.time()-1 - - + + self.last_read_time = time.time() - 1 + self.willing_amplifier = 1 self.willing_manager = get_willing_manager() - - + self.reply_mode = self.chat_stream.context.get_priority_mode() if self.reply_mode == "priority": self.priority_manager = PriorityManager( @@ -125,13 +121,11 @@ class HeartFChatting: self.loop_mode = "priority" else: self.priority_manager = None - logger.info( f"{self.log_prefix} HeartFChatting 初始化完成,消息疲惫阈值: {self._message_threshold}条(基于exit_focus_threshold={global_config.chat.exit_focus_threshold}计算,仅在auto模式下生效)" ) - - + self.energy_value = 100 async def start(self): @@ -168,68 +162,69 @@ class HeartFChatting: logger.info(f"{self.log_prefix} HeartFChatting: 脱离了聊天 (外部停止)") except asyncio.CancelledError: logger.info(f"{self.log_prefix} HeartFChatting: 结束了聊天") - + def start_cycle(self): self._cycle_counter += 1 self._current_cycle_detail = CycleDetail(self._cycle_counter) self._current_cycle_detail.thinking_id = "tid" + str(round(time.time(), 2)) cycle_timers = {} return cycle_timers, self._current_cycle_detail.thinking_id - - def end_cycle(self,loop_info,cycle_timers): + + def end_cycle(self, loop_info, cycle_timers): self._current_cycle_detail.set_loop_info(loop_info) self.history_loop.append(self._current_cycle_detail) self._current_cycle_detail.timers = cycle_timers self._current_cycle_detail.end_time = time.time() - - def print_cycle_info(self,cycle_timers): - # 记录循环信息和计时器结果 - timer_strings = [] - for name, elapsed in cycle_timers.items(): - formatted_time = f"{elapsed * 1000:.2f}毫秒" if elapsed < 1 else f"{elapsed:.2f}秒" - timer_strings.append(f"{name}: {formatted_time}") - logger.info( - f"{self.log_prefix} 第{self._current_cycle_detail.cycle_id}次思考," - f"耗时: {self._current_cycle_detail.end_time - self._current_cycle_detail.start_time:.1f}秒, " - f"选择动作: {self._current_cycle_detail.loop_plan_info.get('action_result', {}).get('action_type', '未知动作')}" - + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") - ) - + def print_cycle_info(self, cycle_timers): + # 记录循环信息和计时器结果 + timer_strings = [] + for name, elapsed in cycle_timers.items(): + formatted_time = f"{elapsed * 1000:.2f}毫秒" if elapsed < 1 else f"{elapsed:.2f}秒" + timer_strings.append(f"{name}: {formatted_time}") + + logger.info( + f"{self.log_prefix} 第{self._current_cycle_detail.cycle_id}次思考," + f"耗时: {self._current_cycle_detail.end_time - self._current_cycle_detail.start_time:.1f}秒, " + f"选择动作: {self._current_cycle_detail.loop_plan_info.get('action_result', {}).get('action_type', '未知动作')}" + + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") + ) - async def _loopbody(self): if self.loop_mode == "focus": - - self.energy_value -= 5 * (1/global_config.chat.exit_focus_threshold) + self.energy_value -= 5 * (1 / global_config.chat.exit_focus_threshold) if self.energy_value <= 0: self.loop_mode = "normal" return True - - + return await self._observe() elif self.loop_mode == "normal": new_messages_data = get_raw_msg_by_timestamp_with_chat( - chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=time.time(),limit=10,limit_mode="earliest",fliter_bot=True + chat_id=self.stream_id, + timestamp_start=self.last_read_time, + timestamp_end=time.time(), + limit=10, + limit_mode="earliest", + fliter_bot=True, ) - + if len(new_messages_data) > 4 * global_config.chat.auto_focus_threshold: self.loop_mode = "focus" self.energy_value = 100 return True - + if new_messages_data: earliest_messages_data = new_messages_data[0] self.last_read_time = earliest_messages_data.get("time") - + await self.normal_response(earliest_messages_data) return True await asyncio.sleep(1) - + return True - - async def build_reply_to_str(self,message_data:dict): + + async def build_reply_to_str(self, message_data: dict): person_info_manager = get_person_info_manager() person_id = person_info_manager.get_person_id( message_data.get("chat_info_platform"), message_data.get("user_id") @@ -238,22 +233,17 @@ class HeartFChatting: reply_to_str = f"{person_name}:{message_data.get('processed_plain_text')}" return reply_to_str - - async def _observe(self,message_data:dict = None): + async def _observe(self, message_data: dict = None): # 创建新的循环信息 cycle_timers, thinking_id = self.start_cycle() - + logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]") - - - async with global_prompt_manager.async_message_scope( - self.chat_stream.context.get_template_name() - ): + async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()): loop_start_time = time.time() # await self.loop_info.observe() await self.relationship_builder.build_relation() - + # 第一步:动作修改 with Timer("动作修改", cycle_timers): try: @@ -261,18 +251,15 @@ class HeartFChatting: available_actions = self.action_manager.get_using_actions() except Exception as e: logger.error(f"{self.log_prefix} 动作修改失败: {e}") - - #如果normal,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的) + + # 如果normal,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的) if self.loop_mode == "normal": reply_to_str = await self.build_reply_to_str(message_data) - gen_task = asyncio.create_task(self._generate_response(message_data, available_actions,reply_to_str)) - + gen_task = asyncio.create_task(self._generate_response(message_data, available_actions, reply_to_str)) with Timer("规划器", cycle_timers): plan_result = await self.action_planner.plan(mode=self.loop_mode) - - action_result = plan_result.get("action_result", {}) action_type, action_data, reasoning, is_parallel = ( action_result.get("action_type", "error"), @@ -282,7 +269,7 @@ class HeartFChatting: ) action_data["loop_start_time"] = loop_start_time - + if self.loop_mode == "normal": if action_type == "no_action": logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定进行回复") @@ -293,8 +280,6 @@ class HeartFChatting: else: logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定执行{action_type}动作") - - if action_type == "no_action": # 等待回复生成完毕 gather_timeout = global_config.chat.thinking_timeout @@ -307,9 +292,7 @@ class HeartFChatting: content = " ".join([item[1] for item in response_set if item[0] == "text"]) # 模型炸了,没有回复内容生成 - if not response_set or ( - action_type not in ["no_action"] and not is_parallel - ): + if not response_set or (action_type not in ["no_action"] and not is_parallel): if not response_set: logger.warning(f"[{self.log_prefix}] 模型未生成回复内容") elif action_type not in ["no_action"] and not is_parallel: @@ -320,14 +303,11 @@ class HeartFChatting: logger.info(f"[{self.log_prefix}] {global_config.bot.nickname} 决定的回复内容: {content}") - # 发送回复 (不再需要传入 chat) await self._send_response(response_set, reply_to_str, loop_start_time) return True - - - + else: # 动作执行计时 with Timer("动作执行", cycle_timers): @@ -350,18 +330,16 @@ class HeartFChatting: if loop_info["loop_action_info"]["command"] == "stop_focus_chat": logger.info(f"{self.log_prefix} 麦麦决定停止专注聊天") return False - #停止该聊天模式的循环 + # 停止该聊天模式的循环 - self.end_cycle(loop_info,cycle_timers) + self.end_cycle(loop_info, cycle_timers) self.print_cycle_info(cycle_timers) if self.loop_mode == "normal": await self.willing_manager.after_generate_reply_handle(message_data.get("message_id")) return True - - - + async def _main_chat_loop(self): """主循环,持续进行计划并可能回复消息,直到被外部取消。""" try: @@ -370,7 +348,7 @@ class HeartFChatting: await asyncio.sleep(0.1) if not success: break - + logger.info(f"{self.log_prefix} 麦麦已强制离开聊天") except asyncio.CancelledError: # 设置了关闭标志位后被取消是正常流程 @@ -430,7 +408,7 @@ class HeartFChatting: else: success, reply_text = result command = "" - + if reply_text == "timeout": self.reply_timeout_count += 1 if self.reply_timeout_count > 5: @@ -446,8 +424,6 @@ class HeartFChatting: logger.error(f"{self.log_prefix} 处理{action}时出错: {e}") traceback.print_exc() return False, "", "" - - async def shutdown(self): """优雅关闭HeartFChatting实例,取消活动循环任务""" @@ -483,7 +459,6 @@ class HeartFChatting: logger.info(f"{self.log_prefix} HeartFChatting关闭完成") - def adjust_reply_frequency(self): """ 根据预设规则动态调整回复意愿(willing_amplifier)。 @@ -553,18 +528,16 @@ class HeartFChatting: f"[{self.log_prefix}] 调整回复意愿。10分钟内回复: {bot_reply_count_10_min} (目标: {target_replies_in_window:.0f}) -> " f"意愿放大器更新为: {self.willing_amplifier:.2f}" ) - - - + async def normal_response(self, message_data: dict) -> None: """ 处理接收到的消息。 在"兴趣"模式下,判断是否回复并生成内容。 """ - + is_mentioned = message_data.get("is_mentioned", False) interested_rate = message_data.get("interest_rate", 0.0) * self.willing_amplifier - + reply_probability = ( 1.0 if is_mentioned and global_config.normal_chat.mentioned_bot_inevitable_reply else 0.0 ) # 如果被提及,且开启了提及必回复,则基础概率为1,否则需要意愿判断 @@ -587,7 +560,6 @@ class HeartFChatting: if message_data.get("is_emoji") or message_data.get("is_picid"): reply_probability = 0 - # 打印消息信息 mes_name = self.chat_stream.group_info.group_name if self.chat_stream.group_info else "私聊" if reply_probability > 0.1: @@ -599,16 +571,15 @@ class HeartFChatting: if random.random() < reply_probability: await self.willing_manager.before_generate_reply_handle(message_data.get("message_id")) - await self._observe(message_data = message_data) + await self._observe(message_data=message_data) # 意愿管理器:注销当前message信息 (无论是否回复,只要处理过就删除) self.willing_manager.delete(message_data.get("message_id")) - + return True - - + async def _generate_response( - self, message_data: dict, available_actions: Optional[list],reply_to:str + self, message_data: dict, available_actions: Optional[list], reply_to: str ) -> Optional[list]: """生成普通回复""" try: @@ -629,29 +600,28 @@ class HeartFChatting: except Exception as e: logger.error(f"[{self.log_prefix}] 回复生成出现错误:{str(e)} {traceback.format_exc()}") return None - - - async def _send_response( - self, reply_set, reply_to, thinking_start_time - ): + + async def _send_response(self, reply_set, reply_to, thinking_start_time): current_time = time.time() new_message_count = message_api.count_new_messages( chat_id=self.chat_stream.stream_id, start_time=thinking_start_time, end_time=current_time ) - + need_reply = new_message_count >= random.randint(2, 4) - + logger.info( f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,{'使用' if need_reply else '不使用'}引用回复" ) - + reply_text = "" first_replyed = False for reply_seg in reply_set: data = reply_seg[1] if not first_replyed: if need_reply: - await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, reply_to=reply_to, typing=False) + await send_api.text_to_stream( + text=data, stream_id=self.chat_stream.stream_id, reply_to=reply_to, typing=False + ) first_replyed = True else: await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, typing=False) @@ -659,7 +629,5 @@ class HeartFChatting: else: await send_api.text_to_stream(text=data, stream_id=self.chat_stream.stream_id, typing=True) reply_text += data - + return reply_text - - \ No newline at end of file diff --git a/src/chat/focus_chat/hfc_utils.py b/src/chat/focus_chat/hfc_utils.py index 5d8df651d..db5bfea12 100644 --- a/src/chat/focus_chat/hfc_utils.py +++ b/src/chat/focus_chat/hfc_utils.py @@ -6,7 +6,6 @@ from src.config.config import global_config from src.common.message_repository import count_messages - logger = get_logger(__name__) @@ -82,7 +81,6 @@ class CycleDetail: self.loop_action_info = loop_info["loop_action_info"] - def get_recent_message_stats(minutes: int = 30, chat_id: str = None) -> dict: """ Args: diff --git a/src/chat/focus_chat/priority_manager.py b/src/chat/focus_chat/priority_manager.py index a3f379651..9db67bc63 100644 --- a/src/chat/focus_chat/priority_manager.py +++ b/src/chat/focus_chat/priority_manager.py @@ -49,7 +49,7 @@ class PriorityManager: 添加新消息到合适的队列中。 """ user_id = message_data.get("user_id") - + priority_info_raw = message_data.get("priority_info") priority_info = {} if isinstance(priority_info_raw, str): diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index d5d63483d..ec28fb813 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -109,12 +109,12 @@ class HeartFCMessageReceiver: interested_rate, is_mentioned = await _calculate_interest(message) message.interest_value = interested_rate message.is_mentioned = is_mentioned - + await self.storage.store_message(message, chat) subheartflow = await heartflow.get_or_create_subheartflow(chat.stream_id) message.update_chat_stream(chat) - + # subheartflow.add_message_to_normal_chat_cache(message, interested_rate, is_mentioned) chat_mood = mood_manager.get_mood_by_chat_id(subheartflow.chat_id) diff --git a/src/chat/heart_flow/sub_heartflow.py b/src/chat/heart_flow/sub_heartflow.py index 631b0aaec..8c2e6de20 100644 --- a/src/chat/heart_flow/sub_heartflow.py +++ b/src/chat/heart_flow/sub_heartflow.py @@ -28,26 +28,22 @@ class SubHeartflow: self.subheartflow_id = subheartflow_id self.chat_id = subheartflow_id - self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_id) self.log_prefix = get_chat_manager().get_stream_name(self.subheartflow_id) or self.subheartflow_id - + # focus模式退出冷却时间管理 self.last_focus_exit_time: float = 0 # 上次退出focus模式的时间 # 随便水群 normal_chat 和 认真水群 focus_chat 实例 # CHAT模式激活 随便水群 FOCUS模式激活 认真水群 self.heart_fc_instance: Optional[HeartFChatting] = HeartFChatting( - chat_id=self.subheartflow_id, - ) # 该sub_heartflow的HeartFChatting实例 + chat_id=self.subheartflow_id, + ) # 该sub_heartflow的HeartFChatting实例 async def initialize(self): """异步初始化方法,创建兴趣流并确定聊天类型""" await self.heart_fc_instance.start() - - - async def _stop_heart_fc_chat(self): """停止并清理 HeartFChatting 实例""" if self.heart_fc_instance.running: @@ -85,7 +81,6 @@ class SubHeartflow: logger.error(f"{self.log_prefix} _start_heart_fc_chat 执行时出错: {e}") logger.error(traceback.format_exc()) return False - def is_in_focus_cooldown(self) -> bool: """检查是否在focus模式的冷却期内 diff --git a/src/chat/message_receive/message.py b/src/chat/message_receive/message.py index 8cc06573c..bc55311c1 100644 --- a/src/chat/message_receive/message.py +++ b/src/chat/message_receive/message.py @@ -444,11 +444,8 @@ class MessageSet: def message_recv_from_dict(message_dict: dict) -> MessageRecv: - return MessageRecv( - - message_dict - - ) + return MessageRecv(message_dict) + def message_from_db_dict(db_dict: dict) -> MessageRecv: """从数据库字典创建MessageRecv实例""" @@ -492,4 +489,4 @@ def message_from_db_dict(db_dict: dict) -> MessageRecv: msg.is_emoji = db_dict.get("is_emoji", False) msg.is_picid = db_dict.get("is_picid", False) - return msg \ No newline at end of file + return msg diff --git a/src/chat/message_receive/uni_message_sender.py b/src/chat/message_receive/uni_message_sender.py index 07eaaad97..6bc14b026 100644 --- a/src/chat/message_receive/uni_message_sender.py +++ b/src/chat/message_receive/uni_message_sender.py @@ -84,4 +84,3 @@ class HeartFCSender: except Exception as e: logger.error(f"[{chat_id}] 处理或存储消息 {message_id} 时出错: {e}") raise e - diff --git a/src/chat/normal_chat/normal_chat.py b/src/chat/normal_chat/normal_chat.py index 5a9293dd8..e7b0434a9 100644 --- a/src/chat/normal_chat/normal_chat.py +++ b/src/chat/normal_chat/normal_chat.py @@ -23,6 +23,7 @@ logger = get_logger("normal_chat") LOOP_INTERVAL = 0.3 + class NormalChat: """ 普通聊天处理类,负责处理非核心对话的聊天逻辑。 @@ -43,7 +44,7 @@ class NormalChat: """ self.chat_stream = chat_stream self.stream_id = chat_stream.stream_id - self.last_read_time = time.time()-1 + self.last_read_time = time.time() - 1 self.stream_name = get_chat_manager().get_stream_name(self.stream_id) or self.stream_id @@ -56,7 +57,7 @@ class NormalChat: # self.mood_manager = mood_manager self.start_time = time.time() - + self.running = False self._initialized = False # Track initialization status @@ -86,7 +87,7 @@ class NormalChat: # 任务管理 self._chat_task: Optional[asyncio.Task] = None - self._priority_chat_task: Optional[asyncio.Task] = None # for priority mode consumer + self._priority_chat_task: Optional[asyncio.Task] = None # for priority mode consumer self._disabled = False # 停用标志 # 新增:回复模式和优先级管理器 @@ -106,11 +107,11 @@ class NormalChat: if self.reply_mode == "priority" and self._priority_chat_task and not self._priority_chat_task.done(): self._priority_chat_task.cancel() logger.info(f"[{self.stream_name}] NormalChat 已停用。") - + # async def _interest_mode_loopbody(self): # try: # await asyncio.sleep(LOOP_INTERVAL) - + # if self._disabled: # return False @@ -118,10 +119,10 @@ class NormalChat: # new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( # chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" # ) - + # if new_messages_data: # self.last_read_time = now - + # for msg_data in new_messages_data: # try: # self.adjust_reply_frequency() @@ -134,44 +135,42 @@ class NormalChat: # except Exception as e: # logger.error(f"[{self.stream_name}] 处理消息时出错: {e} {traceback.format_exc()}") - # except asyncio.CancelledError: # logger.info(f"[{self.stream_name}] 兴趣模式轮询任务被取消") # return False # except Exception: # logger.error(f"[{self.stream_name}] 兴趣模式轮询循环出现错误: {traceback.format_exc()}", exc_info=True) # await asyncio.sleep(10) - + async def _priority_mode_loopbody(self): - try: - await asyncio.sleep(LOOP_INTERVAL) + try: + await asyncio.sleep(LOOP_INTERVAL) - if self._disabled: - return False - - now = time.time() - new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( - chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" - ) - - if new_messages_data: - self.last_read_time = now - - for msg_data in new_messages_data: - try: - if self.priority_manager: - self.priority_manager.add_message(msg_data, msg_data.get("interest_rate", 0.0)) - return True - except Exception as e: - logger.error(f"[{self.stream_name}] 添加消息到优先级队列时出错: {e} {traceback.format_exc()}") - - - except asyncio.CancelledError: - logger.info(f"[{self.stream_name}] 优先级消息生产者任务被取消") + if self._disabled: return False - except Exception: - logger.error(f"[{self.stream_name}] 优先级消息生产者循环出现错误: {traceback.format_exc()}", exc_info=True) - await asyncio.sleep(10) + + now = time.time() + new_messages_data = get_raw_msg_by_timestamp_with_chat_inclusive( + chat_id=self.stream_id, timestamp_start=self.last_read_time, timestamp_end=now, limit_mode="earliest" + ) + + if new_messages_data: + self.last_read_time = now + + for msg_data in new_messages_data: + try: + if self.priority_manager: + self.priority_manager.add_message(msg_data, msg_data.get("interest_rate", 0.0)) + return True + except Exception as e: + logger.error(f"[{self.stream_name}] 添加消息到优先级队列时出错: {e} {traceback.format_exc()}") + + except asyncio.CancelledError: + logger.info(f"[{self.stream_name}] 优先级消息生产者任务被取消") + return False + except Exception: + logger.error(f"[{self.stream_name}] 优先级消息生产者循环出现错误: {traceback.format_exc()}", exc_info=True) + await asyncio.sleep(10) # async def _interest_message_polling_loop(self): # """ @@ -181,16 +180,13 @@ class NormalChat: # try: # while not self._disabled: # success = await self._interest_mode_loopbody() - + # if not success: # break # except asyncio.CancelledError: # logger.info(f"[{self.stream_name}] 兴趣模式消息轮询任务被优雅地取消了") - - - async def _priority_chat_loop(self): """ 使用优先级队列的消息处理循环。 @@ -272,9 +268,8 @@ class NormalChat: # user_nickname=message_data.get("user_nickname"), # platform=message_data.get("chat_info_platform"), # ) - + # reply = message_from_db_dict(message_data) - # mark_head = False # first_bot_msg = None @@ -652,7 +647,9 @@ class NormalChat: # Start consumer loop consumer_task = asyncio.create_task(self._priority_chat_loop()) self._priority_chat_task = consumer_task - self._priority_chat_task.add_done_callback(lambda t: self._handle_task_completion(t, "priority_consumer")) + self._priority_chat_task.add_done_callback( + lambda t: self._handle_task_completion(t, "priority_consumer") + ) else: # Interest mode polling_task = asyncio.create_task(self._interest_message_polling_loop()) self._chat_task = polling_task @@ -712,7 +709,6 @@ class NormalChat: self._chat_task = None self._priority_chat_task = None - # def adjust_reply_frequency(self): # """ # 根据预设规则动态调整回复意愿(willing_amplifier)。 diff --git a/src/chat/planner_actions/action_modifier.py b/src/chat/planner_actions/action_modifier.py index fe0941fd9..8f17f16f2 100644 --- a/src/chat/planner_actions/action_modifier.py +++ b/src/chat/planner_actions/action_modifier.py @@ -82,9 +82,9 @@ class ActionModifier: # === 第一阶段:传统观察处理 === # if history_loop: - # removals_from_loop = await self.analyze_loop_actions(history_loop) - # if removals_from_loop: - # removals_s1.extend(removals_from_loop) + # removals_from_loop = await self.analyze_loop_actions(history_loop) + # if removals_from_loop: + # removals_s1.extend(removals_from_loop) # 检查动作的关联类型 chat_context = self.chat_stream.context @@ -188,7 +188,7 @@ class ActionModifier: reason = "激活类型为never" deactivated_actions.append((action_name, reason)) logger.debug(f"{self.log_prefix}未激活动作: {action_name},原因: 激活类型为never") - + else: logger.warning(f"{self.log_prefix}未知的激活类型: {activation_type},跳过处理") @@ -500,13 +500,13 @@ class ActionModifier: return removals - def get_available_actions_count(self,mode:str = "focus") -> int: + def get_available_actions_count(self, mode: str = "focus") -> int: """获取当前可用动作数量(排除默认的no_action)""" current_actions = self.action_manager.get_using_actions_for_mode(mode) # 排除no_action(如果存在) filtered_actions = {k: v for k, v in current_actions.items() if k != "no_action"} return len(filtered_actions) - + def should_skip_planning_for_no_reply(self) -> bool: """判断是否应该跳过规划过程""" current_actions = self.action_manager.get_using_actions_for_mode("focus") diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 8863c60fa..7d08688a6 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -76,7 +76,7 @@ class ActionPlanner: self.last_obs_time_mark = 0.0 - async def plan(self,mode:str = "focus") -> Dict[str, Any]: + async def plan(self, mode: str = "focus") -> Dict[str, Any]: """ 规划器 (Planner): 使用LLM根据上下文决定做出什么动作。 """ diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 1d83d2c29..b2df0dff1 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -506,7 +506,6 @@ class DefaultReplyer: show_actions=True, ) - message_list_before_short = get_raw_msg_before_timestamp_with_chat( chat_id=chat_id, timestamp=time.time(), diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py index a858abd4d..cdc9ffe86 100644 --- a/src/chat/utils/chat_message_builder.py +++ b/src/chat/utils/chat_message_builder.py @@ -28,7 +28,12 @@ def get_raw_msg_by_timestamp( def get_raw_msg_by_timestamp_with_chat( - chat_id: str, timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest", fliter_bot = False + chat_id: str, + timestamp_start: float, + timestamp_end: float, + limit: int = 0, + limit_mode: str = "latest", + fliter_bot=False, ) -> List[Dict[str, Any]]: """获取在特定聊天从指定时间戳到指定时间戳的消息,按时间升序排序,返回消息列表 limit: 限制返回的消息数量,0为不限制 @@ -38,11 +43,18 @@ def get_raw_msg_by_timestamp_with_chat( # 只有当 limit 为 0 时才应用外部 sort sort_order = [("time", 1)] if limit == 0 else None # 直接将 limit_mode 传递给 find_messages - return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode, fliter_bot=fliter_bot) + return find_messages( + message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode, fliter_bot=fliter_bot + ) def get_raw_msg_by_timestamp_with_chat_inclusive( - chat_id: str, timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest", fliter_bot = False + chat_id: str, + timestamp_start: float, + timestamp_end: float, + limit: int = 0, + limit_mode: str = "latest", + fliter_bot=False, ) -> List[Dict[str, Any]]: """获取在特定聊天从指定时间戳到指定时间戳的消息(包含边界),按时间升序排序,返回消息列表 limit: 限制返回的消息数量,0为不限制 @@ -52,8 +64,10 @@ def get_raw_msg_by_timestamp_with_chat_inclusive( # 只有当 limit 为 0 时才应用外部 sort sort_order = [("time", 1)] if limit == 0 else None # 直接将 limit_mode 传递给 find_messages - - return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode, fliter_bot=fliter_bot) + + return find_messages( + message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode, fliter_bot=fliter_bot + ) def get_raw_msg_by_timestamp_with_chat_users( @@ -583,8 +597,7 @@ def build_readable_actions(actions: List[Dict[str, Any]]) -> str: action_name = action.get("action_name", "未知动作") if action_name == "no_action" or action_name == "no_reply": continue - - + action_prompt_display = action.get("action_prompt_display", "无具体内容") time_diff_seconds = current_time - action_time diff --git a/src/common/message_repository.py b/src/common/message_repository.py index 716452784..4eb9287a2 100644 --- a/src/common/message_repository.py +++ b/src/common/message_repository.py @@ -20,7 +20,7 @@ def find_messages( sort: Optional[List[tuple[str, int]]] = None, limit: int = 0, limit_mode: str = "latest", - fliter_bot = False + fliter_bot=False, ) -> List[dict[str, Any]]: """ 根据提供的过滤器、排序和限制条件查找消息。 @@ -69,10 +69,10 @@ def find_messages( logger.warning(f"过滤器键 '{key}' 在 Messages 模型中未找到。将跳过此条件。") if conditions: query = query.where(*conditions) - + if fliter_bot: query = query.where(Messages.user_id != global_config.bot.qq_account) - + if limit > 0: if limit_mode == "earliest": # 获取时间最早的 limit 条记录,已经是正序 diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 613e447e8..000d4e95f 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -278,7 +278,6 @@ class NormalChatConfig(ConfigBase): """@bot 必然回复""" - @dataclass class FocusChatConfig(ConfigBase): """专注聊天配置类""" diff --git a/src/main.py b/src/main.py index a457f42e4..0e85f6945 100644 --- a/src/main.py +++ b/src/main.py @@ -125,7 +125,6 @@ class MainSystem: logger.info("个体特征初始化成功") try: - init_time = int(1000 * (time.time() - init_start_time)) logger.info(f"初始化完成,神经元放电{init_time}次") except Exception as e: diff --git a/src/mood/mood_manager.py b/src/mood/mood_manager.py index a7b8d7f49..a8f343f3f 100644 --- a/src/mood/mood_manager.py +++ b/src/mood/mood_manager.py @@ -77,7 +77,7 @@ class ChatMood: if random.random() > update_probability: return - + logger.info(f"更新情绪状态,感兴趣度: {interested_rate}, 更新概率: {update_probability}") message_time = message.message_info.time diff --git a/src/plugin_system/apis/message_api.py b/src/plugin_system/apis/message_api.py index d3e319595..e3847c55f 100644 --- a/src/plugin_system/apis/message_api.py +++ b/src/plugin_system/apis/message_api.py @@ -56,7 +56,12 @@ def get_messages_by_time( def get_messages_by_time_in_chat( - chat_id: str, start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False + chat_id: str, + start_time: float, + end_time: float, + limit: int = 0, + limit_mode: str = "latest", + filter_mai: bool = False, ) -> List[Dict[str, Any]]: """ 获取指定聊天中指定时间范围内的消息 @@ -78,7 +83,12 @@ def get_messages_by_time_in_chat( def get_messages_by_time_in_chat_inclusive( - chat_id: str, start_time: float, end_time: float, limit: int = 0, limit_mode: str = "latest", filter_mai: bool = False + chat_id: str, + start_time: float, + end_time: float, + limit: int = 0, + limit_mode: str = "latest", + filter_mai: bool = False, ) -> List[Dict[str, Any]]: """ 获取指定聊天中指定时间范围内的消息(包含边界) @@ -95,7 +105,9 @@ def get_messages_by_time_in_chat_inclusive( 消息列表 """ if filter_mai: - return filter_mai_messages(get_raw_msg_by_timestamp_with_chat_inclusive(chat_id, start_time, end_time, limit, limit_mode)) + return filter_mai_messages( + get_raw_msg_by_timestamp_with_chat_inclusive(chat_id, start_time, end_time, limit, limit_mode) + ) return get_raw_msg_by_timestamp_with_chat_inclusive(chat_id, start_time, end_time, limit, limit_mode) @@ -181,7 +193,9 @@ def get_messages_before_time(timestamp: float, limit: int = 0, filter_mai: bool return get_raw_msg_before_timestamp(timestamp, limit) -def get_messages_before_time_in_chat(chat_id: str, timestamp: float, limit: int = 0, filter_mai: bool = False) -> List[Dict[str, Any]]: +def get_messages_before_time_in_chat( + chat_id: str, timestamp: float, limit: int = 0, filter_mai: bool = False +) -> List[Dict[str, Any]]: """ 获取指定聊天中指定时间戳之前的消息 @@ -342,10 +356,12 @@ async def get_person_ids_from_messages(messages: List[Dict[str, Any]]) -> List[s """ return await get_person_id_list(messages) + # ============================================================================= # 消息过滤函数 # ============================================================================= + def filter_mai_messages(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """ 从消息列表中移除麦麦的消息