From f6f67356919b2228faa36cb419a928afa5ff8160 Mon Sep 17 00:00:00 2001 From: tt-P607 <68868379+tt-P607@users.noreply.github.com> Date: Tue, 9 Sep 2025 13:53:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=80=BB=E4=B9=8B=E5=B0=B1=E6=98=AF=E6=88=90?= =?UTF-8?q?=E4=BA=86=EF=BC=81=F0=9F=98=8B=F0=9F=98=8B=F0=9F=98=8B=E4=B8=BB?= =?UTF-8?q?=E5=8A=A8=E6=80=9D=E8=80=83=E7=BB=88=E4=BA=8E=E6=88=90=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat_loop/proactive/proactive_thinker.py | 64 ++++---- .../proactive/smart_reminder_analyzer.py | 6 +- .../heart_flow/heartflow_message_processor.py | 16 +- src/chat/planner_actions/planner.py | 55 ++++--- src/plugins/built_in/at_user_plugin/plugin.py | 141 +++++++----------- 5 files changed, 133 insertions(+), 149 deletions(-) diff --git a/src/chat/chat_loop/proactive/proactive_thinker.py b/src/chat/chat_loop/proactive/proactive_thinker.py index 7c736b51a..e4086aec1 100644 --- a/src/chat/chat_loop/proactive/proactive_thinker.py +++ b/src/chat/chat_loop/proactive/proactive_thinker.py @@ -122,53 +122,67 @@ class ProactiveThinker: try: # 如果是提醒事件,跳过规划器,直接构建默认动作 if trigger_event.source == "reminder_system": - # 1. 获取原始消息上下文 - action_message = {} - if trigger_event.related_message_id: - # 直接将从数据库获取的完整消息记录作为 action_message - action_message = await db_get( - Messages, {"message_id": trigger_event.related_message_id}, single_result=True - ) or {} + # 1. 获取上下文信息 + metadata = trigger_event.metadata or {} + action_message = metadata + reminder_content = trigger_event.reason.replace("定时提醒:", "").strip() - # 2. 智能确定@对象 - reason_text = trigger_event.reason.replace("定时提醒:", "").strip() - user_name_match = re.search(r"艾特一下(\S+)", reason_text) - - if user_name_match: - user_name = user_name_match.group(1) - at_message = reason_text.replace(f"艾特一下{user_name}", "").strip() - elif action_message.get("user_nickname"): - user_name = action_message.get("user_nickname") - at_message = reason_text + # 2. 确定目标用户名 + target_user_name = None + match = re.search(r"艾特一下([^,,\s]+)", reminder_content) + if match: + target_user_name = match.group(1) else: - user_name = "我" - at_message = reason_text + from src.person_info.person_info import get_person_info_manager + user_id = metadata.get("user_id") + platform = metadata.get("platform") + if user_id and platform: + person_id = get_person_info_manager().get_person_id(platform, user_id) + target_user_name = await get_person_info_manager().get_value(person_id, "person_name") + + if not target_user_name: + logger.warning(f"无法从提醒 '{reminder_content}' 中确定目标用户,回退") + raise Exception("无法确定目标用户") # 3. 构建动作 action_result = { "action_type": "at_user", "reasoning": "执行定时提醒", "action_data": { - "user_name": user_name, - "at_message": at_message or "时间到啦!" + "user_name": target_user_name, + "at_message": reminder_content }, "action_message": action_message } # 4. 执行或回退 try: - success, _, _ = await self.cycle_processor._handle_action( + original_chat_id = metadata.get("chat_id") + if not original_chat_id: + if trigger_event.related_message_id: + db_message = await db_get(Messages, {"message_id": trigger_event.related_message_id}, single_result=True) or {} + original_chat_id = db_message.get("chat_id") + + if not original_chat_id: + raise Exception("提醒事件中缺少chat_id") + + from src.chat.heart_flow.heartflow import heartflow + subflow = await heartflow.get_or_create_subheartflow(original_chat_id) + if not subflow: + raise Exception(f"无法为chat_id {original_chat_id} 获取subflow") + + success, _, _ = await subflow.heart_fc_instance.cycle_processor._handle_action( action=action_result["action_type"], reasoning=action_result["reasoning"], action_data=action_result["action_data"], cycle_timers={}, thinking_id="", - action_message=action_result["action_message"] + action_message=action_result["action_message"], ) if not success: raise Exception("at_user action failed") - except Exception: - logger.warning(f"{self.context.log_prefix} at_user动作执行失败,回退到proactive_reply") + except Exception as e: + logger.warning(f"{self.context.log_prefix} at_user动作执行失败: {e},回退到proactive_reply") fallback_action = { "action_type": "proactive_reply", "action_data": {"topic": trigger_event.reason}, diff --git a/src/chat/chat_loop/proactive/smart_reminder_analyzer.py b/src/chat/chat_loop/proactive/smart_reminder_analyzer.py index 5678270be..3498cb2f5 100644 --- a/src/chat/chat_loop/proactive/smart_reminder_analyzer.py +++ b/src/chat/chat_loop/proactive/smart_reminder_analyzer.py @@ -100,7 +100,7 @@ class SmartReminderAnalyzer: 请判断用户是否想要设置提醒,如果是,请提取: 1. 是否包含提醒请求 (has_reminder: true/false) 2. 置信度 (confidence: 0.0-1.0) -3. 相对时间表达 (relative_time: 如"3分钟后", "2小时后") +3. 相对时间表达 (relative_time: 标准化的时间表达,例如将'半小时后'转换为'30分钟后', '明天下午三点'转换为'明天15点') 4. 提醒内容 (content: 提醒的具体内容) 5. 分析原因 (reasoning: 判断理由) @@ -108,7 +108,7 @@ class SmartReminderAnalyzer: {{ "has_reminder": true/false, "confidence": 0.0-1.0, - "relative_time": "时间表达", + "relative_time": "标准化的时间表达 (例如 '30分钟后', '2小时后')", "content": "提醒内容", "reasoning": "判断理由" }}""" @@ -152,7 +152,7 @@ class SmartReminderAnalyzer: logger.info(f"备用解析成功: {result}") return result except Exception as fallback_error: - logger.error(f"备用JSON解析也失败: {fallback_error}") + logger.error(f"备用JSON解析也失败了: {fallback_error}") except Exception as e: logger.error(f"LLM分析失败: {e}") diff --git a/src/chat/heart_flow/heartflow_message_processor.py b/src/chat/heart_flow/heartflow_message_processor.py index a0d18c44c..65c0fa61c 100644 --- a/src/chat/heart_flow/heartflow_message_processor.py +++ b/src/chat/heart_flow/heartflow_message_processor.py @@ -151,9 +151,9 @@ class HeartFCMessageReceiver: # 获取对应的subheartflow实例 from src.chat.heart_flow.heartflow import heartflow - subflow = await heartflow.get_or_create_subheartflow(chat.stream_id) + subflow = await heartflow.get_or_create_subheartflow(metadata.get("chat_id")) if not subflow: - logger.error(f"无法获取subheartflow实例: {chat.stream_id}") + logger.error(f"无法获取subheartflow实例: {metadata.get('chat_id')}") return # 创建主动思考事件,触发完整的思考流程 @@ -163,11 +163,9 @@ class HeartFCMessageReceiver: event = ProactiveTriggerEvent( source="reminder_system", reason=f"定时提醒:{reminder_content}", - metadata={ - "reminder_text": reminder_content, - "trigger_time": datetime.now().isoformat() - } - ) + metadata=metadata, + related_message_id=metadata.get("original_message_id") + ) # 通过subflow的HeartFChatting实例触发主动思考 await subflow.heart_fc_instance.proactive_thinker.think(event) @@ -181,10 +179,11 @@ class HeartFCMessageReceiver: # Fallback: 如果主动思考失败,直接发送提醒消息 try: + from src.plugin_system.apis.send_api import text_to_stream reminder_content = metadata.get('content', '提醒时间到了') await text_to_stream( text=f"⏰ 提醒:{reminder_content}", - stream_id=chat.stream_id, + stream_id=metadata.get("chat_id"), typing=False ) logger.info(f"Fallback提醒消息已发送: {reminder_content}") @@ -196,6 +195,7 @@ class HeartFCMessageReceiver: metadata = { "type": "reminder", "user_id": reminder_event.user_id, + "platform": chat.platform, "chat_id": chat.stream_id, "content": reminder_event.content, "confidence": reminder_event.confidence, diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 4bf80f2b9..f0f850eb7 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -50,6 +50,7 @@ def init_prompt(): {time_block} {identity_block} +{users_in_chat} {custom_prompt_block} {chat_context_description},以下是具体的聊天内容。 {chat_content_block} @@ -154,7 +155,6 @@ def init_prompt(): ) - class ActionPlanner: def __init__(self, chat_id: str, action_manager: ActionManager): self.chat_id = chat_id @@ -165,7 +165,6 @@ class ActionPlanner: self.planner_llm = LLMRequest( model_set=model_config.model_task_config.planner, request_type="planner" ) - self.last_obs_time_mark = 0.0 async def _get_long_term_memory_context(self) -> str: @@ -268,14 +267,14 @@ class ActionPlanner: # 假设消息列表是按时间顺序排列的,最后一个是最新的 return message_id_list[-1].get("message") - def _parse_single_action( + async def _parse_single_action( self, action_json: dict, message_id_list: list, # 使用 planner.py 的 list of dict current_available_actions: list, # 使用 planner.py 的 list of tuple ) -> List[Dict[str, Any]]: """ - [注释] 解析单个小脑LLM返回的action JSON,并将其转换为标准化的字典。 + [注释] 解析单个LLM返回的action JSON,并将其转换为标准化的字典。 """ parsed_actions = [] try: @@ -312,6 +311,16 @@ class ActionPlanner: "available_actions": available_actions_dict, } ) + # 如果是at_user动作且只有user_name,尝试转换为user_id + if action == "at_user" and "user_name" in action_data and "user_id" not in action_data: + user_name = action_data["user_name"] + from src.person_info.person_info import get_person_info_manager + user_info = await get_person_info_manager().get_person_info_by_name(user_name) + if user_info and user_info.get("user_id"): + action_data["user_id"] = user_info["user_id"] + logger.info(f"成功将用户名 '{user_name}' 解析为 user_id '{user_info['user_id']}'") + else: + logger.warning(f"无法将用户名 '{user_name}' 解析为 user_id") except Exception as e: logger.error(f"{self.log_prefix}解析单个action时出错: {e}") parsed_actions.append( @@ -349,22 +358,6 @@ class ActionPlanner: 统一决策是否进行聊天回复(reply)以及执行哪些actions。 """ # --- 1. 准备上下文信息 --- - message_list_before_now = get_raw_msg_before_timestamp_with_chat( - chat_id=self.chat_id, - timestamp=time.time(), - limit=int(global_config.chat.max_context_size * 0.6), - ) - chat_content_block, message_id_list = build_readable_messages_with_id( - messages=message_list_before_now, - timestamp_mode="normal", - read_mark=self.last_obs_time_mark, - truncate=True, - show_actions=True, - ) - if pseudo_message: - chat_content_block += f"\n[m99] 刚刚, 用户: {pseudo_message}" - self.last_obs_time_mark = time.time() - is_group_chat, chat_target_info, current_available_actions = self.get_necessary_info() if available_actions is None: available_actions = current_available_actions @@ -377,8 +370,6 @@ class ActionPlanner: chat_target_info=chat_target_info, current_available_actions=available_actions, mode=mode, - chat_content_block_override=chat_content_block, - message_id_list_override=message_id_list, ) llm_content, _ = await self.planner_llm.generate_response_async(prompt=prompt) @@ -392,7 +383,7 @@ class ActionPlanner: if isinstance(parsed_json, list): for item in parsed_json: if isinstance(item, dict): - final_actions.extend(self._parse_single_action(item, used_message_id_list, list(available_actions.items()))) + final_actions.extend(await self._parse_single_action(item, used_message_id_list, list(available_actions.items()))) # 如果是私聊且开启了强制回复,并且没有任何回复性action,则强制添加reply if not is_group_chat and global_config.chat.force_reply_private: @@ -402,7 +393,7 @@ class ActionPlanner: "action_type": "reply", "reasoning": "私聊强制回复", "action_data": {}, - "action_message": self.get_latest_message(message_id_list), + "action_message": self.get_latest_message(used_message_id_list), "available_actions": available_actions, }) logger.info(f"{self.log_prefix}私聊强制回复已触发,添加 'reply' 动作") @@ -444,8 +435,6 @@ class ActionPlanner: chat_target_info: Optional[dict], current_available_actions: Dict[str, ActionInfo], mode: ChatMode = ChatMode.FOCUS, - chat_content_block_override: Optional[str] = None, - message_id_list_override: Optional[List] = None, refresh_time: bool = False, # 添加缺失的参数 ) -> tuple[str, list]: """构建 Planner LLM 的提示词 (获取模板并填充数据)""" @@ -479,7 +468,7 @@ class ActionPlanner: timestamp=time.time(), limit=int(global_config.chat.max_context_size * 0.2), # 主动思考时只看少量最近消息 ) - chat_content_block, _ = build_readable_messages_with_id( + chat_content_block, message_id_list = build_readable_messages_with_id( messages=message_list_short, timestamp_mode="normal", truncate=False, @@ -505,7 +494,7 @@ class ActionPlanner: chat_content_block=chat_content_block or "最近没有聊天内容。", actions_before_now_block=actions_before_now_block, ) - return prompt, [] + return prompt, message_id_list # --- FOCUS 和 NORMAL 模式的逻辑 --- message_list_before_now = get_raw_msg_before_timestamp_with_chat( @@ -513,7 +502,6 @@ class ActionPlanner: timestamp=time.time(), limit=int(global_config.chat.max_context_size * 0.6), ) - chat_content_block, message_id_list = build_readable_messages_with_id( messages=message_list_before_now, timestamp_mode="normal", @@ -580,6 +568,14 @@ class ActionPlanner: custom_prompt_block = "" if global_config.custom_prompt.planner_custom_prompt_content: custom_prompt_block = global_config.custom_prompt.planner_custom_prompt_content + + from src.person_info.person_info import get_person_info_manager + users_in_chat_str = "" + if is_group_chat and chat_target_info and chat_target_info.get("group_id"): + user_list = await get_person_info_manager().get_specific_value_list("person_name", lambda x: x is not None) + if user_list: + users_in_chat_str = "当前聊天中的用户列表(用于@):\n" + "\n".join([f"- {name} (ID: {pid})" for pid, name in user_list.items()]) + "\n" + planner_prompt_template = await global_prompt_manager.get_prompt_async("planner_prompt") prompt = planner_prompt_template.format( @@ -596,6 +592,7 @@ class ActionPlanner: identity_block=identity_block, custom_prompt_block=custom_prompt_block, bot_name=bot_name, + users_in_chat=users_in_chat_str ) return prompt, message_id_list except Exception as e: diff --git a/src/plugins/built_in/at_user_plugin/plugin.py b/src/plugins/built_in/at_user_plugin/plugin.py index 7a80b8ab6..e01f9bf9c 100644 --- a/src/plugins/built_in/at_user_plugin/plugin.py +++ b/src/plugins/built_in/at_user_plugin/plugin.py @@ -21,12 +21,12 @@ class AtAction(BaseAction): # === 基本信息(必须填写)=== action_name = "at_user" action_description = "发送艾特消息" - activation_type = ActionActivationType.LLM_JUDGE # 消息接收时激活(?) + activation_type = ActionActivationType.LLM_JUDGE parallel_action = False chat_type_allow = ChatType.GROUP # === 功能描述(必须填写)=== - action_parameters = {"user_name": "需要艾特用户的名字", "at_message": "艾特用户时要发送的消,注意消息里不要有@"} + action_parameters = {"user_name": "需要艾特用户的名字", "at_message": "艾特用户时要发送的消息"} action_require = [ "当需要艾特某个用户时使用", "当你需要提醒特定用户查看消息时使用", @@ -48,24 +48,43 @@ class AtAction(BaseAction): if not user_name or not at_message: logger.warning("艾特用户的动作缺少必要参数。") - await self.store_action_info( - action_build_into_prompt=True, - action_prompt_display=f"执行了艾特用户动作:艾特用户 {user_name} 并发送消息: {at_message},失败了,因为没有提供必要参数", - action_done=False, - ) return False, "缺少必要参数" - user_info = await get_person_info_manager().get_person_info_by_name(user_name) - if not user_info or not user_info.get("user_id"): - logger.info(f"找不到名为 '{user_name}' 的用户。") + from src.plugin_system.apis import send_api + from fuzzywuzzy import process + + group_id = self.chat_stream.group_info.group_id + if not group_id: + return False, "无法获取群组ID" + + response = await send_api.adapter_command_to_stream( + action="get_group_member_list", + params={"group_id": group_id}, + stream_id=self.chat_id, + ) + + if response.get("status") != "ok": + return False, f"获取群成员列表失败: {response.get('message')}" + + member_list = response.get("data", []) + if not member_list: + return False, "群成员列表为空" + + # 使用模糊匹配找到最接近的用户名 + choices = {member["card"] or member["nickname"]: member["user_id"] for member in member_list} + best_match, score = process.extractOne(user_name, choices.keys()) + + if score < 30: # 设置一个匹配度阈值 + logger.info(f"找不到与 '{user_name}' 高度匹配的用户 (最佳匹配: {best_match}, 分数: {score})") return False, "用户不存在" + + user_id = choices[best_match] + user_info = {"user_id": user_id, "user_nickname": best_match} try: - # 使用回复器生成艾特回复,而不是直接发送命令 from src.chat.replyer.default_generator import DefaultReplyer from src.chat.message_receive.chat_stream import get_chat_manager - - # 获取当前聊天流 + chat_manager = get_chat_manager() chat_stream = chat_manager.get_stream(self.chat_id) @@ -73,97 +92,51 @@ class AtAction(BaseAction): logger.error(f"找不到聊天流: {self.stream_id}") return False, "聊天流不存在" - # 创建回复器实例 replyer = DefaultReplyer(chat_stream) - - # 构建回复对象,将艾特消息作为回复目标 - reply_to = f"{user_name}:{at_message}" extra_info = f"你需要艾特用户 {user_name} 并回复他们说: {at_message}" - # 使用回复器生成回复 - success, llm_response, prompt = await replyer.generate_reply_with_context( - reply_to=reply_to, + success, llm_response, _ = await replyer.generate_reply_with_context( + reply_to=f"{user_name}:{at_message}", extra_info=extra_info, - enable_tool=False, # 艾特回复通常不需要工具调用 + enable_tool=False, from_plugin=False ) - if success and llm_response: - # 获取生成的回复内容 - reply_content = llm_response.get("content", "") - if reply_content: - # 获取用户QQ号,发送真正的艾特消息 - user_id = user_info.get("user_id") - - # 发送真正的艾特命令,使用回复器生成的智能内容 - await self.send_command( - "SEND_AT_MESSAGE", - args={"qq_id": user_id, "text": reply_content}, - display_message=f"艾特用户 {user_name} 并发送智能回复: {reply_content}", - ) - - await self.store_action_info( - action_build_into_prompt=True, - action_prompt_display=f"执行了艾特用户动作:艾特用户 {user_name} 并发送智能回复: {reply_content}", - action_done=True, - ) - - logger.info(f"成功通过回复器生成智能内容并发送真正的艾特消息给 {user_name}: {reply_content}") - return True, "智能艾特消息发送成功" - else: - logger.warning("回复器生成了空内容") - return False, "回复内容为空" - else: + if not success or not llm_response: logger.error("回复器生成回复失败") return False, "回复生成失败" + + final_message = llm_response.get("content", "") + if not final_message: + logger.warning("回复器生成了空内容") + return False, "回复内容为空" + + await self.send_command( + "SEND_AT_MESSAGE", + args={"group_id": self.chat_stream.group_info.group_id, "qq_id": user_id, "text": final_message}, + display_message=f"艾特用户 {user_name} 并发送消息: {final_message}", + ) + + await self.store_action_info( + action_build_into_prompt=True, + action_prompt_display=f"执行了艾特用户动作:艾特用户 {user_name} 并发送消息: {final_message}", + action_done=True, + ) + + logger.info(f"成功发送艾特消息给 {user_name}: {final_message}") + return True, "艾特消息发送成功" except Exception as e: logger.error(f"执行艾特用户动作时发生异常: {e}", exc_info=True) - await self.store_action_info( - action_build_into_prompt=True, - action_prompt_display=f"执行艾特用户动作失败:{str(e)}", - action_done=False, - ) return False, f"执行失败: {str(e)}" -class AtCommand(BaseCommand): - command_name: str = "at_user" - description: str = "通过名字艾特用户" - command_pattern: str = r"/at\s+@?(?P[\S]+)(?:\s+(?P.*))?" - - async def execute(self) -> Tuple[bool, str, bool]: - name = self.matched_groups.get("name") - text = self.matched_groups.get("text", "") - - if not name: - await self.send_text("请指定要艾特的用户名称。") - return False, "缺少用户名称", True - - person_info_manager = get_person_info_manager() - user_info = await person_info_manager.get_person_info_by_name(name) - - if not user_info or not user_info.get("user_id"): - await self.send_text(f"找不到名为 '{name}' 的用户。") - return False, "用户不存在", True - - user_id = user_info.get("user_id") - - await self.send_command( - "SEND_AT_MESSAGE", - args={"qq_id": user_id, "text": text}, - display_message=f"艾特用户 {name} 并发送消息: {text}", - ) - - return True, "艾特消息已发送", True - - @register_plugin class AtUserPlugin(BasePlugin): plugin_name: str = "at_user_plugin" enable_plugin: bool = True dependencies: list[str] = [] - python_dependencies: list[str] = [] + python_dependencies: list[str] = ["fuzzywuzzy", "python-Levenshtein"] config_file_name: str = "config.toml" config_schema: dict = {}