diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index 6ad7027ba..005b94bff 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -51,13 +51,12 @@ async def _calculate_interest(message: MessageRecv) -> Tuple[float, bool]: is_mentioned, _ = is_mentioned_bot_in_message(message) interested_rate = 0.0 - if global_config.memory.enable_memory: - with Timer("记忆激活"): - interested_rate = await hippocampus_manager.get_activate_from_text( - message.processed_plain_text, - fast_retrieval=True, - ) - logger.debug(f"记忆激活率: {interested_rate:.2f}") + with Timer("记忆激活"): + interested_rate = await hippocampus_manager.get_activate_from_text( + message.processed_plain_text, + fast_retrieval=True, + ) + logger.debug(f"记忆激活率: {interested_rate:.2f}") text_len = len(message.processed_plain_text) # 根据文本长度调整兴趣度,长度越大兴趣度越高,但增长率递减,最低0.01,最高0.05 diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index edd5d010d..9963baa78 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -11,7 +11,7 @@ from json_repair import repair_json from src.chat.utils.utils import get_chat_type_and_target_info from datetime import datetime from src.chat.message_receive.chat_stream import get_chat_manager -from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat +from src.chat.utils.chat_message_builder import build_readable_actions, build_readable_messages, get_actions_by_timestamp_with_chat, get_raw_msg_before_timestamp_with_chat import time logger = get_logger("planner") @@ -27,6 +27,9 @@ def init_prompt(): 你现在需要根据聊天内容,选择的合适的action来参与聊天。 {chat_context_description},以下是具体的聊天内容: {chat_content_block} +你刚刚进行过的action是: +{actions_before_now_block} + {moderation_prompt} 现在请你根据{by_what}选择合适的action: @@ -221,6 +224,16 @@ class ActionPlanner: truncate=True, show_actions=True, ) + + actions_before_now = get_actions_by_timestamp_with_chat( + chat_id=self.chat_id, + timestamp_end=time.time(), + limit=5, + ) + + actions_before_now_block = build_readable_actions( + actions=actions_before_now, + ) self.last_obs_time_mark = time.time() @@ -285,6 +298,7 @@ class ActionPlanner: by_what=by_what, chat_context_description=chat_context_description, chat_content_block=chat_content_block, + actions_before_now_block=actions_before_now_block, no_action_block=no_action_block, action_options_text=action_options_block, moderation_prompt=moderation_prompt_block, diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py index ab97f395b..5598aac14 100644 --- a/src/chat/utils/chat_message_builder.py +++ b/src/chat/utils/chat_message_builder.py @@ -77,6 +77,56 @@ def get_raw_msg_by_timestamp_with_chat_users( return find_messages(message_filter=filter_query, sort=sort_order, limit=limit, limit_mode=limit_mode) +def get_actions_by_timestamp_with_chat( + chat_id: str, timestamp_start: float = 0, timestamp_end: float = time.time(), limit: int = 0, limit_mode: str = "latest" +) -> List[Dict[str, Any]]: + """获取在特定聊天从指定时间戳到指定时间戳的动作记录,按时间升序排序,返回动作记录列表""" + query = ActionRecords.select().where( + (ActionRecords.chat_id == chat_id) + & (ActionRecords.time > timestamp_start) + & (ActionRecords.time < timestamp_end) + ) + + if limit > 0: + if limit_mode == "latest": + query = query.order_by(ActionRecords.time.desc()).limit(limit) + # 获取后需要反转列表,以保持最终输出为时间升序 + actions = list(query) + return [action.__data__ for action in reversed(actions)] + else: # earliest + query = query.order_by(ActionRecords.time.asc()).limit(limit) + else: + query = query.order_by(ActionRecords.time.asc()) + + actions = list(query) + return [action.__data__ for action in actions] + + +def get_actions_by_timestamp_with_chat_inclusive( + chat_id: str, timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest" +) -> List[Dict[str, Any]]: + """获取在特定聊天从指定时间戳到指定时间戳的动作记录(包含边界),按时间升序排序,返回动作记录列表""" + query = ActionRecords.select().where( + (ActionRecords.chat_id == chat_id) + & (ActionRecords.time >= timestamp_start) + & (ActionRecords.time <= timestamp_end) + ) + + if limit > 0: + if limit_mode == "latest": + query = query.order_by(ActionRecords.time.desc()).limit(limit) + # 获取后需要反转列表,以保持最终输出为时间升序 + actions = list(query) + return [action.__data__ for action in reversed(actions)] + else: # earliest + query = query.order_by(ActionRecords.time.asc()).limit(limit) + else: + query = query.order_by(ActionRecords.time.asc()) + + actions = list(query) + return [action.__data__ for action in actions] + + def get_raw_msg_by_timestamp_random( timestamp_start: float, timestamp_end: float, limit: int = 0, limit_mode: str = "latest" ) -> List[Dict[str, Any]]: @@ -503,6 +553,45 @@ def build_pic_mapping_info(pic_id_mapping: Dict[str, str]) -> str: return "\n".join(mapping_lines) +def build_readable_actions(actions: List[Dict[str, Any]]) -> str: + """ + 将动作列表转换为可读的文本格式。 + 格式: 在()分钟前,你使用了(action_name),具体内容是:(action_prompt_display) + + Args: + actions: 动作记录字典列表。 + + Returns: + 格式化的动作字符串。 + """ + if not actions: + return "" + + output_lines = [] + current_time = time.time() + + # The get functions return actions sorted ascending by time. Let's reverse it to show newest first. + sorted_actions = sorted(actions, key=lambda x: x.get("time", 0), reverse=True) + + for action in sorted_actions: + action_time = action.get("time", current_time) + action_name = action.get("action_name", "未知动作") + action_prompt_display = action.get("action_prompt_display", "无具体内容") + + time_diff_seconds = current_time - action_time + + if time_diff_seconds < 60: + time_ago_str = f"在{time_diff_seconds}秒前" + else: + time_diff_minutes = round(time_diff_seconds / 60) + time_ago_str = f"在{time_diff_minutes}分钟前" + + line = f"{time_ago_str},你使用了“{action_name}”,具体内容是:“{action_prompt_display}”" + output_lines.append(line) + + return "\n".join(output_lines) + + async def build_readable_messages_with_list( messages: List[Dict[str, Any]], replace_bot_name: bool = True, diff --git a/src/plugins/built_in/core_actions/plugin.py b/src/plugins/built_in/core_actions/plugin.py index 2b7194063..a591e1abf 100644 --- a/src/plugins/built_in/core_actions/plugin.py +++ b/src/plugins/built_in/core_actions/plugin.py @@ -9,6 +9,7 @@ import random import time from typing import List, Tuple, Type import asyncio +import re # 导入新插件系统 from src.plugin_system import BasePlugin, register_plugin, BaseAction, ComponentInfo, ActionActivationType, ChatMode @@ -53,12 +54,28 @@ class ReplyAction(BaseAction): # 关联类型 associated_types = ["text"] + + def _parse_reply_target(self, target_message: str) -> tuple: + sender = "" + target = "" + if ":" in target_message or ":" in target_message: + # 使用正则表达式匹配中文或英文冒号 + parts = re.split(pattern=r"[::]", string=target_message, maxsplit=1) + if len(parts) == 2: + sender = parts[0].strip() + target = parts[1].strip() + return sender, target async def execute(self) -> Tuple[bool, str]: """执行回复动作""" logger.info(f"{self.log_prefix} 决定进行回复") start_time = self.action_data.get("loop_start_time", time.time()) + + reply_to = self.action_data.get("reply_to", "") + sender, target = self._parse_reply_target(reply_to) + + try: try: @@ -105,6 +122,11 @@ class ReplyAction(BaseAction): reply_text += data # 存储动作记录 + if sender and target: + reply_text = f"你对{sender}说的{target},进行了回复:{reply_text}" + else: + reply_text = f"你进行发言:{reply_text}" + await self.store_action_info( action_build_into_prompt=False, action_prompt_display=reply_text, @@ -148,31 +170,23 @@ class CoreActionsPlugin(BasePlugin): # 配置Schema定义 config_schema = { "plugin": { - "enabled": ConfigField(type=bool, default=True, description="是否启用插件"), - "config_version": ConfigField(type=str, default="0.3.1", description="配置文件版本"), + "enabled": ConfigField(type=bool, default=False, description="是否启用插件"), + "config_version": ConfigField(type=str, default="0.4.0", description="配置文件版本"), }, "components": { - "enable_reply": ConfigField(type=bool, default=True, description="是否启用'回复'动作"), - "enable_no_reply": ConfigField(type=bool, default=True, description="是否启用'不回复'动作"), - "enable_emoji": ConfigField(type=bool, default=True, description="是否启用'表情'动作"), + "enable_reply": ConfigField(type=bool, default=True, description="是否启用回复动作"), + "enable_no_reply": ConfigField(type=bool, default=True, description="是否启用不回复动作"), + "enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"), }, "no_reply": { - "max_timeout": ConfigField(type=int, default=1200, description="最大等待超时时间(秒)"), - "min_judge_interval": ConfigField( - type=float, default=1.0, description="LLM判断的最小间隔时间(秒),防止过于频繁" - ), - "auto_exit_message_count": ConfigField( - type=int, default=20, description="累计消息数量达到此阈值时自动结束等待" + "interest_exit_threshold": ConfigField( + type=float, default=3.0, description="累计兴趣值达到此阈值时自动结束等待" ), + "min_exit_message_count": ConfigField(type=int, default=6, description="自动结束等待的最小消息数"), + "max_exit_message_count": ConfigField(type=int, default=9, description="自动结束等待的最大消息数"), "random_probability": ConfigField( type=float, default=0.8, description="Focus模式下,随机选择不回复的概率(0.0到1.0)", example=0.8 ), - "skip_judge_when_tired": ConfigField( - type=bool, default=True, description="当发言过多时是否启用跳过LLM判断机制" - ), - "frequency_check_window": ConfigField( - type=int, default=600, description="回复频率检查窗口时间(秒)", example=600 - ), }, } @@ -193,21 +207,14 @@ class CoreActionsPlugin(BasePlugin): no_reply_probability = self.get_config("no_reply.random_probability", 0.8) NoReplyAction.random_activation_probability = no_reply_probability - min_judge_interval = self.get_config("no_reply.min_judge_interval", 1.0) - NoReplyAction._min_judge_interval = min_judge_interval + interest_exit_threshold = self.get_config("no_reply.interest_exit_threshold", 10.0) + NoReplyAction._interest_exit_threshold = interest_exit_threshold - auto_exit_message_count = self.get_config("no_reply.auto_exit_message_count", 20) - NoReplyAction._auto_exit_message_count = auto_exit_message_count + min_exit_count = self.get_config("no_reply.min_exit_message_count", 5) + NoReplyAction._min_exit_message_count = min_exit_count - max_timeout = self.get_config("no_reply.max_timeout", 600) - NoReplyAction._max_timeout = max_timeout - - skip_judge_when_tired = self.get_config("no_reply.skip_judge_when_tired", True) - NoReplyAction._skip_judge_when_tired = skip_judge_when_tired - - # 新增:频率检测相关配置 - frequency_check_window = self.get_config("no_reply.frequency_check_window", 600) - NoReplyAction._frequency_check_window = frequency_check_window + max_exit_count = self.get_config("no_reply.max_exit_message_count", 10) + NoReplyAction._max_exit_message_count = max_exit_count # --- 根据配置注册组件 --- components = []