From 3cb60b03b4cd730fe39101bb77a17598b3b8d350 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Mon, 23 Jun 2025 00:39:13 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E7=B2=BE=E7=AE=80=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=99=A8=E4=B8=8A=E4=B8=8B=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/focus_chat/heartFC_chat.py | 3 +- src/chat/focus_chat/hfc_version_manager.py | 2 +- src/chat/focus_chat/info/obs_info.py | 50 ++++++ .../info_processors/chattinginfo_processor.py | 15 +- .../expression_selector_processor.py | 3 +- .../info_processors/relationship_processor.py | 5 +- .../info_processors/tool_processor.py | 3 +- src/chat/focus_chat/memory_activator.py | 2 +- .../observation/chatting_observation.py | 22 +++ src/plugins/built_in/core_actions/plugin.py | 144 ++++++++---------- 10 files changed, 159 insertions(+), 90 deletions(-) diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index 7e5139c51..8d90232cf 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -660,6 +660,7 @@ class HeartFChatting: } with Timer("执行动作", cycle_timers): + action_type, action_data, reasoning = ( plan_result.get("action_result", {}).get("action_type", "error"), plan_result.get("action_result", {}).get("action_data", {}), @@ -674,7 +675,7 @@ class HeartFChatting: action_str = action_type logger.debug(f"{self.log_prefix} 麦麦想要:'{action_str}'") - + success, reply_text, command = await self._handle_action( action_type, reasoning, action_data, cycle_timers, thinking_id ) diff --git a/src/chat/focus_chat/hfc_version_manager.py b/src/chat/focus_chat/hfc_version_manager.py index 72954df06..570b4bf1d 100644 --- a/src/chat/focus_chat/hfc_version_manager.py +++ b/src/chat/focus_chat/hfc_version_manager.py @@ -20,7 +20,7 @@ class HFCVersionManager: """HFC版本号管理器""" # 默认版本号 - DEFAULT_VERSION = "v1.0.0" + DEFAULT_VERSION = "v2.0.2" # 当前运行时版本号 _current_version: Optional[str] = None diff --git a/src/chat/focus_chat/info/obs_info.py b/src/chat/focus_chat/info/obs_info.py index 05dcf98c8..2cb4d3cb7 100644 --- a/src/chat/focus_chat/info/obs_info.py +++ b/src/chat/focus_chat/info/obs_info.py @@ -16,6 +16,8 @@ class ObsInfo(InfoBase): Data Fields: talking_message (str): 说话消息内容 talking_message_str_truncate (str): 截断后的说话消息内容 + talking_message_str_short (str): 简短版本的说话消息内容(使用最新一半消息) + talking_message_str_truncate_short (str): 截断简短版本的说话消息内容(使用最新一半消息) chat_type (str): 聊天类型,可以是 "private"(私聊)、"group"(群聊)或 "other"(其他) """ @@ -37,6 +39,22 @@ class ObsInfo(InfoBase): """ self.data["talking_message_str_truncate"] = message + def set_talking_message_str_short(self, message: str) -> None: + """设置简短版本的说话消息 + + Args: + message (str): 简短版本的说话消息内容 + """ + self.data["talking_message_str_short"] = message + + def set_talking_message_str_truncate_short(self, message: str) -> None: + """设置截断简短版本的说话消息 + + Args: + message (str): 截断简短版本的说话消息内容 + """ + self.data["talking_message_str_truncate_short"] = message + def set_previous_chat_info(self, message: str) -> None: """设置之前聊天信息 @@ -62,6 +80,22 @@ class ObsInfo(InfoBase): chat_target (str): 聊天目标,可以是 "private"(私聊)、"group"(群聊)或 "other"(其他) """ self.data["chat_target"] = chat_target + + def set_chat_id(self, chat_id: str) -> None: + """设置聊天ID + + Args: + chat_id (str): 聊天ID + """ + self.data["chat_id"] = chat_id + + def get_chat_id(self) -> Optional[str]: + """获取聊天ID + + Returns: + Optional[str]: 聊天ID,如果未设置则返回 None + """ + return self.get_info("chat_id") def get_talking_message(self) -> Optional[str]: """获取说话消息 @@ -79,6 +113,22 @@ class ObsInfo(InfoBase): """ return self.get_info("talking_message_str_truncate") + def get_talking_message_str_short(self) -> Optional[str]: + """获取简短版本的说话消息 + + Returns: + Optional[str]: 简短版本的说话消息内容,如果未设置则返回 None + """ + return self.get_info("talking_message_str_short") + + def get_talking_message_str_truncate_short(self) -> Optional[str]: + """获取截断简短版本的说话消息 + + Returns: + Optional[str]: 截断简短版本的说话消息内容,如果未设置则返回 None + """ + return self.get_info("talking_message_str_truncate_short") + def get_chat_type(self) -> str: """获取聊天类型 diff --git a/src/chat/focus_chat/info_processors/chattinginfo_processor.py b/src/chat/focus_chat/info_processors/chattinginfo_processor.py index 17236b7ab..b7a65e9f8 100644 --- a/src/chat/focus_chat/info_processors/chattinginfo_processor.py +++ b/src/chat/focus_chat/info_processors/chattinginfo_processor.py @@ -62,6 +62,10 @@ class ChattingInfoProcessor(BaseProcessor): # 改为异步任务,不阻塞主流程 # asyncio.create_task(self.chat_compress(obs)) + # 设置聊天ID + if hasattr(obs, "chat_id"): + obs_info.set_chat_id(obs.chat_id) + # 设置说话消息 if hasattr(obs, "talking_message_str"): # print(f"设置说话消息:obs.talking_message_str: {obs.talking_message_str}") @@ -72,6 +76,14 @@ class ChattingInfoProcessor(BaseProcessor): # print(f"设置截断后的说话消息:obs.talking_message_str_truncate: {obs.talking_message_str_truncate}") obs_info.set_talking_message_str_truncate(obs.talking_message_str_truncate) + # 设置简短版本的说话消息 + if hasattr(obs, "talking_message_str_short"): + obs_info.set_talking_message_str_short(obs.talking_message_str_short) + + # 设置截断简短版本的说话消息 + if hasattr(obs, "talking_message_str_truncate_short"): + obs_info.set_talking_message_str_truncate_short(obs.talking_message_str_truncate_short) + if hasattr(obs, "mid_memory_info"): # print(f"设置之前聊天信息:obs.mid_memory_info: {obs.mid_memory_info}") obs_info.set_previous_chat_info(obs.mid_memory_info) @@ -82,7 +94,8 @@ class ChattingInfoProcessor(BaseProcessor): chat_type = "group" else: chat_type = "private" - obs_info.set_chat_target(obs.chat_target_info.get("person_name", "某人")) + if hasattr(obs, "chat_target_info") and obs.chat_target_info: + obs_info.set_chat_target(obs.chat_target_info.get("person_name", "某人")) obs_info.set_chat_type(chat_type) # logger.debug(f"聊天信息处理器处理后的信息: {obs_info}") diff --git a/src/chat/focus_chat/info_processors/expression_selector_processor.py b/src/chat/focus_chat/info_processors/expression_selector_processor.py index 3791bf3e4..1c630ed81 100644 --- a/src/chat/focus_chat/info_processors/expression_selector_processor.py +++ b/src/chat/focus_chat/info_processors/expression_selector_processor.py @@ -149,7 +149,8 @@ class ExpressionSelectorProcessor(BaseProcessor): if observations: for observation in observations: if isinstance(observation, ChattingObservation): - chat_info = observation.get_observe_info() + # chat_info = observation.get_observe_info() + chat_info = observation.talking_message_str_truncate_short break if not chat_info: diff --git a/src/chat/focus_chat/info_processors/relationship_processor.py b/src/chat/focus_chat/info_processors/relationship_processor.py index 7a148860a..9dd61c73f 100644 --- a/src/chat/focus_chat/info_processors/relationship_processor.py +++ b/src/chat/focus_chat/info_processors/relationship_processor.py @@ -144,7 +144,7 @@ def init_prompt(): fetch_info_prompt = """ {name_block} -以下是你对{person_name}的了解,请你从中提取用户的有关"{info_type}"的信息,如果用户没有相关信息,请输出none: +以下是你在之前与{person_name}的交流中,产生的对{person_name}的了解,请你从中提取用户的有关"{info_type}"的信息,如果用户没有相关信息,请输出none: {person_impression_block} {points_text_block} 请严格按照以下json输出格式,不要输出多余内容: @@ -547,8 +547,7 @@ class PersonImpressionpProcessor(BaseProcessor): for observation in observations: if isinstance(observation, ChattingObservation): chat_observe_info = observation.get_observe_info() - chat_observe_info = chat_observe_info[-300:] - + # latest_message_time = observation.last_observe_time # 从聊天观察中提取用户信息并更新消息段 # 获取最新的非bot消息来更新消息段 latest_messages = get_raw_msg_by_timestamp_with_chat( diff --git a/src/chat/focus_chat/info_processors/tool_processor.py b/src/chat/focus_chat/info_processors/tool_processor.py index 411e91159..fad6d8fad 100644 --- a/src/chat/focus_chat/info_processors/tool_processor.py +++ b/src/chat/focus_chat/info_processors/tool_processor.py @@ -112,7 +112,8 @@ class ToolProcessor(BaseProcessor): is_group_chat = observation.is_group_chat - chat_observe_info = observation.get_observe_info() + # chat_observe_info = observation.get_observe_info() + chat_observe_info = observation.talking_message_str_truncate_short # person_list = observation.person_list # 获取时间信息 diff --git a/src/chat/focus_chat/memory_activator.py b/src/chat/focus_chat/memory_activator.py index a557999d1..cd67c3d23 100644 --- a/src/chat/focus_chat/memory_activator.py +++ b/src/chat/focus_chat/memory_activator.py @@ -94,7 +94,7 @@ class MemoryActivator: obs_info_text = "" for observation in observations: if isinstance(observation, ChattingObservation): - obs_info_text += observation.get_observe_info() + obs_info_text += observation.talking_message_str_truncate_short elif isinstance(observation, StructureObservation): working_info = observation.get_observe_info() for working_info_item in working_info: diff --git a/src/chat/heart_flow/observation/chatting_observation.py b/src/chat/heart_flow/observation/chatting_observation.py index ba901711e..e33b5364c 100644 --- a/src/chat/heart_flow/observation/chatting_observation.py +++ b/src/chat/heart_flow/observation/chatting_observation.py @@ -63,6 +63,8 @@ class ChattingObservation(Observation): self.talking_message = [] self.talking_message_str = "" self.talking_message_str_truncate = "" + self.talking_message_str_short = "" + self.talking_message_str_truncate_short = "" self.name = global_config.bot.nickname self.nick_name = global_config.bot.alias_names self.max_now_obs_len = global_config.focus_chat.observation_context_size @@ -88,6 +90,8 @@ class ChattingObservation(Observation): "chat_target_info": self.chat_target_info, "talking_message_str": self.talking_message_str, "talking_message_str_truncate": self.talking_message_str_truncate, + "talking_message_str_short": self.talking_message_str_short, + "talking_message_str_truncate_short": self.talking_message_str_truncate_short, "name": self.name, "nick_name": self.nick_name, "last_observe_time": self.last_observe_time, @@ -228,6 +232,24 @@ class ChattingObservation(Observation): truncate=True, show_actions=True, ) + + # 构建简短版本 - 使用最新一半的消息 + half_count = len(self.talking_message) // 2 + recent_messages = self.talking_message[-half_count:] if half_count > 0 else self.talking_message + + self.talking_message_str_short = build_readable_messages( + messages=recent_messages, + timestamp_mode="lite", + read_mark=last_obs_time_mark, + show_actions=True, + ) + self.talking_message_str_truncate_short = build_readable_messages( + messages=recent_messages, + timestamp_mode="normal_no_YMD", + read_mark=last_obs_time_mark, + truncate=True, + show_actions=True, + ) self.person_list = await get_person_id_list(self.talking_message) diff --git a/src/plugins/built_in/core_actions/plugin.py b/src/plugins/built_in/core_actions/plugin.py index 1ada97f3e..8eb5d8949 100644 --- a/src/plugins/built_in/core_actions/plugin.py +++ b/src/plugins/built_in/core_actions/plugin.py @@ -54,6 +54,8 @@ class ReplyAction(BaseAction): logger.info(f"{self.log_prefix} 决定回复: {self.reasoning}") start_time = self.action_data.get("loop_start_time", time.time()) + + try: success, reply_set = await generator_api.generate_reply( @@ -64,6 +66,7 @@ class ReplyAction(BaseAction): is_group=self.is_group, ) + # 检查从start_time以来的新消息数量 # 获取动作触发时间或使用默认值 current_time = time.time() @@ -79,15 +82,20 @@ class ReplyAction(BaseAction): # 构建回复文本 reply_text = "" - first_reply = False + first_replyed = False for reply_seg in reply_set: data = reply_seg[1] - if not first_reply and need_reply: - await self.send_text(content=data, reply_to=self.action_data.get("reply_to", "")) + if not first_replyed: + if need_reply: + await self.send_text(content=data, reply_to=self.action_data.get("reply_to", ""),typing=False) + first_replyed = True + else: + await self.send_text(content=data,typing=False) + first_replyed = True else: - await self.send_text(content=data) - first_reply = True + await self.send_text(content=data,typing=True) reply_text += data + # 存储动作记录 await self.store_action_info( @@ -119,16 +127,17 @@ class NoReplyAction(BaseAction): action_name = "no_reply" action_description = "暂时不回复消息" - # 默认超时时间,将由插件在注册时设置 - waiting_timeout = 1200 - # 连续no_reply计数器 _consecutive_count = 0 - - # random_activation_probability = 0.2 - - # 分级等待时间 - _waiting_stages = [10, 60, 600] # 第1、2、3次的等待时间 + + # 概率判定时间点 + _probability_check_time = 15 # 15秒时进行概率判定 + + # 概率判定通过的概率(通过则结束动作) + _end_probability = 0.5 # 50%概率结束 + + # 最大等待超时时间 + _max_timeout = 1200 # 1200秒 # 动作参数定义 action_parameters = {"reason": "不回复的原因"} @@ -140,33 +149,42 @@ class NoReplyAction(BaseAction): associated_types = [] async def execute(self) -> Tuple[bool, str]: - """执行不回复动作,等待新消息或超时""" + """执行不回复动作,在15秒时进行概率判定,决定是否继续等待""" + import random + try: # 增加连续计数 NoReplyAction._consecutive_count += 1 count = NoReplyAction._consecutive_count reason = self.action_data.get("reason", "") + + logger.info(f"{self.log_prefix} 选择不回复(第{count}次),开始等待新消息,原因: {reason}") - # 计算本次等待时间 - if count <= len(self._waiting_stages): - # 前3次使用预设时间 - stage_time = self._waiting_stages[count - 1] - # 如果WAITING_TIME_THRESHOLD更小,则使用它 - timeout = min(stage_time, self.waiting_timeout) + # 先等待到概率判定时间点(15秒) + logger.info(f"{self.log_prefix} 等待{self._probability_check_time}秒后进行概率判定...") + + # 等待15秒或有新消息 + result = await self.wait_for_new_message(self._probability_check_time) + + # 如果在15秒内有新消息,直接返回 + if result[0]: # 有新消息 + logger.info(f"{self.log_prefix} 在{self._probability_check_time}秒内收到新消息,结束等待") + return result + + # 15秒后进行概率判定 + if random.random() < self._end_probability: + # 概率判定通过,结束动作 + logger.info(f"{self.log_prefix} 概率判定通过({self._end_probability * 100}%),结束不回复动作") + return True, "概率判定通过,结束等待" else: - # 第4次及以后使用WAITING_TIME_THRESHOLD - timeout = self.waiting_timeout - - logger.info( - f"{self.log_prefix} 选择不回复(第{count}次连续),等待新消息中... (超时: {timeout}秒),原因: {reason}" - ) - - # 等待新消息或达到时间上限 - result = await self.wait_for_new_message(timeout) - - # 如果有新消息或者超时,都不重置计数器,因为可能还会继续no_reply - return result + # 概率判定不通过,继续等待直到最大超时时间 + remaining_time = self._max_timeout - self._probability_check_time + logger.info(f"{self.log_prefix} 概率判定不通过,继续等待{remaining_time}秒直到超时或有新消息...") + + # 继续等待剩余时间 + result = await self.wait_for_new_message(remaining_time) + return result except Exception as e: logger.error(f"{self.log_prefix} 不回复动作执行失败: {e}") @@ -245,41 +263,6 @@ class EmojiAction(BaseAction): return False, f"表情发送失败: {str(e)}" -class ChangeToFocusChatAction(BaseAction): - """切换到专注聊天动作 - 从普通模式切换到专注模式""" - - focus_activation_type = ActionActivationType.NEVER - normal_activation_type = ActionActivationType.NEVER - mode_enable = ChatMode.NORMAL - parallel_action = False - - # 动作基本信息 - action_name = "change_to_focus_chat" - action_description = "切换到专注聊天,从普通模式切换到专注模式" - - # 动作参数定义 - action_parameters = {} - - apex = 111 - # 动作使用场景 - action_require = [ - "你想要进入专注聊天模式", - "聊天上下文中自己的回复条数较多(超过3-4条)", - "对话进行得非常热烈活跃", - "用户表现出深入交流的意图", - "话题需要更专注和深入的讨论", - ] - - async def execute(self) -> Tuple[bool, str]: - """执行切换到专注聊天动作""" - logger.info(f"{self.log_prefix} 决定切换到专注聊天: {self.reasoning}") - - # 重置NoReplyAction的连续计数器 - NoReplyAction.reset_consecutive_count() - - # 这里只做决策标记,具体切换逻辑由上层管理器处理 - return True, "决定切换到专注聊天模式" - class ExitFocusChatAction(BaseAction): """退出专注聊天动作 - 从专注模式切换到普通模式""" @@ -371,7 +354,7 @@ class CoreActionsPlugin(BasePlugin): config_schema = { "plugin": { "enabled": ConfigField(type=bool, default=True, description="是否启用插件"), - "config_version": ConfigField(type=str, default="0.0.2", description="配置文件版本"), + "config_version": ConfigField(type=str, default="0.0.3", description="配置文件版本"), }, "components": { "enable_reply": ConfigField(type=bool, default=True, description="是否启用'回复'动作"), @@ -381,12 +364,11 @@ class CoreActionsPlugin(BasePlugin): "enable_exit_focus": ConfigField(type=bool, default=True, description="是否启用'退出专注模式'动作"), }, "no_reply": { - "waiting_timeout": ConfigField( - type=int, default=1200, description="连续不回复时,最长的等待超时时间(秒)" + "probability_check_time": ConfigField(type=int, default=15, description="进行概率判定的时间点(秒)"), + "end_probability": ConfigField( + type=float, default=0.5, description="在判定时间点结束等待的概率(0.0到1.0)", example=0.5 ), - "stage_1_wait": ConfigField(type=int, default=10, description="第1次连续不回复的等待时间(秒)"), - "stage_2_wait": ConfigField(type=int, default=60, description="第2次连续不回复的等待时间(秒)"), - "stage_3_wait": ConfigField(type=int, default=600, description="第3次连续不回复的等待时间(秒)"), + "max_timeout": ConfigField(type=int, default=1200, description="最大等待超时时间(秒)"), "random_probability": ConfigField( type=float, default=0.8, description="Focus模式下,随机选择不回复的概率(0.0到1.0)", example=0.8 ), @@ -408,13 +390,14 @@ class CoreActionsPlugin(BasePlugin): no_reply_probability = self.get_config("no_reply.random_probability", 0.8) NoReplyAction.random_activation_probability = no_reply_probability - no_reply_timeout = self.get_config("no_reply.waiting_timeout", 1200) - NoReplyAction.waiting_timeout = no_reply_timeout + probability_check_time = self.get_config("no_reply.probability_check_time", 15) + NoReplyAction._probability_check_time = probability_check_time - stage1 = self.get_config("no_reply.stage_1_wait", 10) - stage2 = self.get_config("no_reply.stage_2_wait", 60) - stage3 = self.get_config("no_reply.stage_3_wait", 600) - NoReplyAction._waiting_stages = [stage1, stage2, stage3] + end_probability = self.get_config("no_reply.end_probability", 0.5) + NoReplyAction._end_probability = end_probability + + max_timeout = self.get_config("no_reply.max_timeout", 1200) + NoReplyAction._max_timeout = max_timeout # --- 根据配置注册组件 --- components = [] @@ -426,8 +409,7 @@ class CoreActionsPlugin(BasePlugin): components.append((EmojiAction.get_action_info(), EmojiAction)) if self.get_config("components.enable_exit_focus", True): components.append((ExitFocusChatAction.get_action_info(), ExitFocusChatAction)) - if self.get_config("components.enable_change_to_focus", True): - components.append((ChangeToFocusChatAction.get_action_info(), ChangeToFocusChatAction)) + # components.append((DeepReplyAction.get_action_info(), DeepReplyAction)) return components