diff --git a/src/common/logger.py b/src/common/logger.py index 53043f405..62dbfb051 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -432,7 +432,7 @@ PFC_ACTION_PLANNER_STYLE_CONFIG = { "simple": { "console_format": "{time:MM-DD HH:mm} | PFC私聊规划 | {message} ", # noqa: E501 "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | PFC私聊规划 | {message}", - }, + }, } EMOJI_STYLE_CONFIG = { @@ -513,7 +513,9 @@ CONFIRM_STYLE_CONFIG = { # 根据SIMPLE_OUTPUT选择配置 MAIN_STYLE_CONFIG = MAIN_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else MAIN_STYLE_CONFIG["advanced"] EMOJI_STYLE_CONFIG = EMOJI_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else EMOJI_STYLE_CONFIG["advanced"] -PFC_ACTION_PLANNER_STYLE_CONFIG = PFC_ACTION_PLANNER_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else PFC_ACTION_PLANNER_STYLE_CONFIG["advanced"] +PFC_ACTION_PLANNER_STYLE_CONFIG = ( + PFC_ACTION_PLANNER_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else PFC_ACTION_PLANNER_STYLE_CONFIG["advanced"] +) REMOTE_STYLE_CONFIG = REMOTE_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else REMOTE_STYLE_CONFIG["advanced"] BASE_TOOL_STYLE_CONFIG = BASE_TOOL_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else BASE_TOOL_STYLE_CONFIG["advanced"] PERSON_INFO_STYLE_CONFIG = PERSON_INFO_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else PERSON_INFO_STYLE_CONFIG["advanced"] diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 8659bf6e0..b4889dc52 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -16,6 +16,7 @@ pfc_action_log_config = LogConfig( logger = get_module_logger("action_planner", config=pfc_action_log_config) + # 注意:这个 ActionPlannerInfo 类似乎没有在 ActionPlanner 中使用, # 如果确实没用,可以考虑移除,但暂时保留以防万一。 class ActionPlannerInfo: @@ -25,9 +26,11 @@ class ActionPlannerInfo: self.knowledge_list = [] self.memory_list = [] + # ActionPlanner 类定义,顶格 class ActionPlanner: """行动规划器""" + def __init__(self, stream_id: str): self.llm = LLMRequest( model=global_config.llm_PFC_action_planner, @@ -54,18 +57,20 @@ class ActionPlanner: time_since_last_bot_message_info = "" try: bot_id = str(global_config.BOT_QQ) - if hasattr(observation_info, 'chat_history') and observation_info.chat_history: + if hasattr(observation_info, "chat_history") and observation_info.chat_history: for i in range(len(observation_info.chat_history) - 1, -1, -1): msg = observation_info.chat_history[i] if not isinstance(msg, dict): continue - sender_info = msg.get('user_info', {}) - sender_id = str(sender_info.get('user_id')) if isinstance(sender_info, dict) else None - msg_time = msg.get('time') + sender_info = msg.get("user_info", {}) + sender_id = str(sender_info.get("user_id")) if isinstance(sender_info, dict) else None + msg_time = msg.get("time") if sender_id == bot_id and msg_time: time_diff = time.time() - msg_time if time_diff < 60.0: - time_since_last_bot_message_info = f"提示:你上一条成功发送的消息是在 {time_diff:.1f} 秒前。\n" + time_since_last_bot_message_info = ( + f"提示:你上一条成功发送的消息是在 {time_diff:.1f} 秒前。\n" + ) break else: logger.debug("Observation info chat history is empty or not available for bot time check.") @@ -76,31 +81,31 @@ class ActionPlanner: # --- 获取 Bot 上次发言时间信息结束 --- timeout_context = "" - try: # 添加 try-except 以增加健壮性 - if hasattr(conversation_info, 'goal_list') and conversation_info.goal_list: + try: # 添加 try-except 以增加健壮性 + if hasattr(conversation_info, "goal_list") and conversation_info.goal_list: last_goal_tuple = conversation_info.goal_list[-1] if isinstance(last_goal_tuple, tuple) and len(last_goal_tuple) > 0: last_goal_text = last_goal_tuple[0] if isinstance(last_goal_text, str) and "分钟,思考接下来要做什么" in last_goal_text: try: - timeout_minutes_text = last_goal_text.split(',')[0].replace('你等待了','') + timeout_minutes_text = last_goal_text.split(",")[0].replace("你等待了", "") timeout_context = f"重要提示:你刚刚因为对方长时间({timeout_minutes_text})没有回复而结束了等待,这可能代表在对方看来本次聊天已结束,请基于此情况规划下一步,不要重复等待前的发言。\n" except Exception: timeout_context = "重要提示:你刚刚因为对方长时间没有回复而结束了等待,这可能代表在对方看来本次聊天已结束,请基于此情况规划下一步,不要重复等待前的发言。\n" else: logger.debug("Conversation info goal_list is empty or not available for timeout check.") except AttributeError: - logger.warning("ConversationInfo object might not have goal_list attribute yet for timeout check.") + logger.warning("ConversationInfo object might not have goal_list attribute yet for timeout check.") except Exception as e: - logger.warning(f"检查超时目标时出错: {e}") + logger.warning(f"检查超时目标时出错: {e}") # 构建提示词 - logger.debug(f"开始规划行动:当前目标: {getattr(conversation_info, 'goal_list', '不可用')}") # 使用 getattr + logger.debug(f"开始规划行动:当前目标: {getattr(conversation_info, 'goal_list', '不可用')}") # 使用 getattr # 构建对话目标 (goals_str) goals_str = "" - try: # 添加 try-except - if hasattr(conversation_info, 'goal_list') and conversation_info.goal_list: + try: # 添加 try-except + if hasattr(conversation_info, "goal_list") and conversation_info.goal_list: for goal_reason in conversation_info.goal_list: if isinstance(goal_reason, tuple) and len(goal_reason) > 0: goal = goal_reason[0] @@ -114,36 +119,36 @@ class ActionPlanner: goal = str(goal) if goal is not None else "目标内容缺失" reasoning = str(reasoning) if reasoning is not None else "没有明确原因" goals_str += f"- 目标:{goal}\n 原因:{reasoning}\n" - if not goals_str: # 如果循环后 goals_str 仍为空 + if not goals_str: # 如果循环后 goals_str 仍为空 goals_str = "- 目前没有明确对话目标,请考虑设定一个。\n" except AttributeError: - logger.warning("ConversationInfo object might not have goal_list attribute yet.") - goals_str = "- 获取对话目标时出错。\n" + logger.warning("ConversationInfo object might not have goal_list attribute yet.") + goals_str = "- 获取对话目标时出错。\n" except Exception as e: - logger.error(f"构建对话目标字符串时出错: {e}") - goals_str = "- 构建对话目标时出错。\n" + logger.error(f"构建对话目标字符串时出错: {e}") + goals_str = "- 构建对话目标时出错。\n" # 获取聊天历史记录 (chat_history_text) chat_history_text = "" try: - if hasattr(observation_info, 'chat_history') and observation_info.chat_history: + if hasattr(observation_info, "chat_history") and observation_info.chat_history: chat_history_list = observation_info.chat_history[-20:] for msg in chat_history_list: - if isinstance(msg, dict) and 'detailed_plain_text' in msg: + if isinstance(msg, dict) and "detailed_plain_text" in msg: chat_history_text += f"{msg.get('detailed_plain_text', '')}\n" elif isinstance(msg, str): chat_history_text += f"{msg}\n" - if not chat_history_text: # 如果历史记录是空列表 - chat_history_text = "还没有聊天记录。\n" + if not chat_history_text: # 如果历史记录是空列表 + chat_history_text = "还没有聊天记录。\n" else: chat_history_text = "还没有聊天记录。\n" - if hasattr(observation_info, 'new_messages_count') and observation_info.new_messages_count > 0: - if hasattr(observation_info, 'unprocessed_messages') and observation_info.unprocessed_messages: + if hasattr(observation_info, "new_messages_count") and observation_info.new_messages_count > 0: + if hasattr(observation_info, "unprocessed_messages") and observation_info.unprocessed_messages: new_messages_list = observation_info.unprocessed_messages chat_history_text += f"--- 以下是 {observation_info.new_messages_count} 条新消息 ---\n" for msg in new_messages_list: - if isinstance(msg, dict) and 'detailed_plain_text' in msg: + if isinstance(msg, dict) and "detailed_plain_text" in msg: chat_history_text += f"{msg.get('detailed_plain_text', '')}\n" elif isinstance(msg, str): chat_history_text += f"{msg}\n" @@ -151,7 +156,9 @@ class ActionPlanner: # if hasattr(observation_info, 'clear_unprocessed_messages'): # observation_info.clear_unprocessed_messages() else: - logger.warning("ObservationInfo has new_messages_count > 0 but unprocessed_messages is empty or missing.") + logger.warning( + "ObservationInfo has new_messages_count > 0 but unprocessed_messages is empty or missing." + ) except AttributeError: logger.warning("ObservationInfo object might be missing expected attributes for chat history.") chat_history_text = "获取聊天记录时出错。\n" @@ -159,7 +166,6 @@ class ActionPlanner: logger.error(f"处理聊天记录时发生未知错误: {e}") chat_history_text = "处理聊天记录时出错。\n" - # 构建 Persona 文本 (persona_text) identity_details_only = self.identity_detail_info identity_addon = "" @@ -168,11 +174,11 @@ class ActionPlanner: # original_details = identity_details_only for p in pronouns: if identity_details_only.startswith(p): - identity_details_only = identity_details_only[len(p):] + identity_details_only = identity_details_only[len(p) :] break if identity_details_only.endswith("。"): - identity_details_only = identity_details_only[:-1] - cleaned_details = identity_details_only.strip(',, ') + identity_details_only = identity_details_only[:-1] + cleaned_details = identity_details_only.strip(",, ") if cleaned_details: identity_addon = f"并且{cleaned_details}" persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" @@ -182,15 +188,15 @@ class ActionPlanner: last_action_context = "关于你【上一次尝试】的行动:\n" action_history_list = [] - try: # 添加 try-except - if hasattr(conversation_info, 'done_action') and conversation_info.done_action: + try: # 添加 try-except + if hasattr(conversation_info, "done_action") and conversation_info.done_action: action_history_list = conversation_info.done_action[-5:] else: logger.debug("Conversation info done_action is empty or not available.") except AttributeError: - logger.warning("ConversationInfo object might not have done_action attribute yet.") + logger.warning("ConversationInfo object might not have done_action attribute yet.") except Exception as e: - logger.error(f"访问行动历史时出错: {e}") + logger.error(f"访问行动历史时出错: {e}") if not action_history_list: action_history_summary += "- 还没有执行过行动。\n" @@ -210,13 +216,13 @@ class ActionPlanner: final_reason = action_data.get("final_reason", "") action_time = action_data.get("time", "") elif isinstance(action_data, tuple): - if len(action_data) > 0: + if len(action_data) > 0: action_type = action_data[0] - if len(action_data) > 1: + if len(action_data) > 1: plan_reason = action_data[1] - if len(action_data) > 2: + if len(action_data) > 2: status = action_data[2] - if status == "recall" and len(action_data) > 3: + if status == "recall" and len(action_data) > 3: final_reason = action_data[3] reason_text = f", 失败/取消原因: {final_reason}" if final_reason else "" @@ -231,9 +237,9 @@ class ActionPlanner: elif status == "recall": last_action_context += "- 但该行动最终【未能执行/被取消】。\n" if final_reason: - last_action_context += f"- 【重要】失败/取消的具体原因是: “{final_reason}”\n" + last_action_context += f"- 【重要】失败/取消的具体原因是: “{final_reason}”\n" else: - last_action_context += "- 【重要】失败/取消原因未明确记录。\n" + last_action_context += "- 【重要】失败/取消原因未明确记录。\n" else: last_action_context += f"- 该行动当前状态: {status}\n" @@ -279,14 +285,15 @@ end_conversation: 决定结束对话,对方长时间没回复或者当你觉 logger.debug(f"LLM原始返回内容: {content}") success, result = get_items_from_json( - content, "action", "reason", - default_values={"action": "wait", "reason": "LLM返回格式错误或未提供原因,默认等待"} + content, + "action", + "reason", + default_values={"action": "wait", "reason": "LLM返回格式错误或未提供原因,默认等待"}, ) action = result.get("action", "wait") reason = result.get("reason", "LLM未提供原因,默认等待") - # 验证action类型 valid_actions = ["direct_reply", "fetch_knowledge", "wait", "listening", "rethink_goal", "end_conversation"] if action not in valid_actions: @@ -300,4 +307,4 @@ end_conversation: 决定结束对话,对方长时间没回复或者当你觉 except Exception as e: logger.error(f"规划行动时调用 LLM 或处理结果出错: {str(e)}") - return "wait", f"行动规划处理中发生错误,暂时等待: {str(e)}" \ No newline at end of file + return "wait", f"行动规划处理中发生错误,暂时等待: {str(e)}" diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 23a555446..d4888ff79 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -75,34 +75,36 @@ class Conversation: raise try: logger.info(f"为 {self.stream_id} 加载初始聊天记录...") - storage = MongoDBMessageStorage() # 创建存储实例 + storage = MongoDBMessageStorage() # 创建存储实例 # 获取当前时间点之前最多 N 条消息 (比如 30 条) # get_messages_before 返回的是按时间正序排列的列表 initial_messages = await storage.get_messages_before( - chat_id=self.stream_id, - time_point=time.time(), - limit=30 # 加载最近20条作为初始上下文,可以调整 + chat_id=self.stream_id, + time_point=time.time(), + limit=30, # 加载最近20条作为初始上下文,可以调整 ) if initial_messages: # 将加载的消息填充到 ObservationInfo 的 chat_history self.observation_info.chat_history = initial_messages self.observation_info.chat_history_count = len(initial_messages) - + # 更新 ObservationInfo 中的时间戳等信息 last_msg = initial_messages[-1] - self.observation_info.last_message_time = last_msg.get('time') + self.observation_info.last_message_time = last_msg.get("time") last_user_info = UserInfo.from_dict(last_msg.get("user_info", {})) self.observation_info.last_message_sender = last_user_info.user_id self.observation_info.last_message_content = last_msg.get("processed_plain_text", "") - + # (可选)可以遍历 initial_messages 来设置 last_bot_speak_time 和 last_user_speak_time # 这里为了简化,只用了最后一条消息的时间,如果需要精确的发言者时间需要遍历 - - logger.info(f"成功加载 {len(initial_messages)} 条初始聊天记录。最后一条消息时间: {self.observation_info.last_message_time}") - + + logger.info( + f"成功加载 {len(initial_messages)} 条初始聊天记录。最后一条消息时间: {self.observation_info.last_message_time}" + ) + # 让 ChatObserver 从加载的最后一条消息之后开始同步 self.chat_observer.last_message_time = self.observation_info.last_message_time - self.chat_observer.last_message_read = last_msg # 更新 observer 的最后读取记录 + self.chat_observer.last_message_read = last_msg # 更新 observer 的最后读取记录 else: logger.info("没有找到初始聊天记录。") @@ -128,49 +130,52 @@ class Conversation: try: # --- 在规划前记录当前新消息数量 --- initial_new_message_count = 0 - if hasattr(self.observation_info, 'new_messages_count'): + if hasattr(self.observation_info, "new_messages_count"): initial_new_message_count = self.observation_info.new_messages_count else: - logger.warning("ObservationInfo missing 'new_messages_count' before planning.") + logger.warning("ObservationInfo missing 'new_messages_count' before planning.") # 使用决策信息来辅助行动规划 - action, reason = await self.action_planner.plan(self.observation_info, self.conversation_info) # 注意:plan 函数内部现在不应再调用 clear_unprocessed_messages + action, reason = await self.action_planner.plan( + self.observation_info, self.conversation_info + ) # 注意:plan 函数内部现在不应再调用 clear_unprocessed_messages # --- 规划后检查是否有 *更多* 新消息到达 --- current_new_message_count = 0 - if hasattr(self.observation_info, 'new_messages_count'): - current_new_message_count = self.observation_info.new_messages_count + if hasattr(self.observation_info, "new_messages_count"): + current_new_message_count = self.observation_info.new_messages_count else: - logger.warning("ObservationInfo missing 'new_messages_count' after planning.") + logger.warning("ObservationInfo missing 'new_messages_count' after planning.") if current_new_message_count > initial_new_message_count: # 只有当规划期间消息数量 *增加* 了,才认为需要重新规划 - logger.info(f"规划期间发现新增消息 ({initial_new_message_count} -> {current_new_message_count}),跳过本次行动,重新规划") - await asyncio.sleep(0.1) # 短暂延时 - continue # 跳过本次行动,重新规划 + logger.info( + f"规划期间发现新增消息 ({initial_new_message_count} -> {current_new_message_count}),跳过本次行动,重新规划" + ) + await asyncio.sleep(0.1) # 短暂延时 + continue # 跳过本次行动,重新规划 # --- 如果没有在规划期间收到更多新消息,则准备执行行动 --- # --- 清理未处理消息:移到这里,在执行动作前 --- # 只有当确实有新消息被 planner 看到,并且 action 是要处理它们的时候才清理 if initial_new_message_count > 0 and action == "direct_reply": - if hasattr(self.observation_info, 'clear_unprocessed_messages'): - # 确保 clear_unprocessed_messages 方法存在 - logger.debug(f"准备执行 direct_reply,清理 {initial_new_message_count} 条规划时已知的新消息。") - self.observation_info.clear_unprocessed_messages() - # 手动重置计数器,确保状态一致性(理想情况下 clear 方法会做这个) - if hasattr(self.observation_info, 'new_messages_count'): - self.observation_info.new_messages_count = 0 + if hasattr(self.observation_info, "clear_unprocessed_messages"): + # 确保 clear_unprocessed_messages 方法存在 + logger.debug(f"准备执行 direct_reply,清理 {initial_new_message_count} 条规划时已知的新消息。") + self.observation_info.clear_unprocessed_messages() + # 手动重置计数器,确保状态一致性(理想情况下 clear 方法会做这个) + if hasattr(self.observation_info, "new_messages_count"): + self.observation_info.new_messages_count = 0 else: - logger.error("无法清理未处理消息: ObservationInfo 缺少 clear_unprocessed_messages 方法!") - # 这里可能需要考虑是否继续执行 action,或者抛出错误 - + logger.error("无法清理未处理消息: ObservationInfo 缺少 clear_unprocessed_messages 方法!") + # 这里可能需要考虑是否继续执行 action,或者抛出错误 # --- 执行行动 --- await self._handle_action(action, reason, self.observation_info, self.conversation_info) goal_ended = False - if hasattr(self.conversation_info, 'goal_list') and self.conversation_info.goal_list: + if hasattr(self.conversation_info, "goal_list") and self.conversation_info.goal_list: for goal in self.conversation_info.goal_list: if isinstance(goal, tuple) and len(goal) > 0 and goal[0] == "结束对话": goal_ended = True @@ -185,15 +190,15 @@ class Conversation: # break # 可以选择在这里直接跳出循环 except Exception as loop_err: - logger.error(f"PFC主循环出错: {loop_err}") - logger.error(traceback.format_exc()) - # 发生严重错误时可以考虑停止,或者至少等待一下再继续 - await asyncio.sleep(1) # 发生错误时等待1秒 - #添加短暂的异步睡眠 - if self.should_continue: # 只有在还需要继续循环时才 sleep - await asyncio.sleep(0.1) # 等待 0.1 秒,给其他任务执行时间 + logger.error(f"PFC主循环出错: {loop_err}") + logger.error(traceback.format_exc()) + # 发生严重错误时可以考虑停止,或者至少等待一下再继续 + await asyncio.sleep(1) # 发生错误时等待1秒 + # 添加短暂的异步睡眠 + if self.should_continue: # 只有在还需要继续循环时才 sleep + await asyncio.sleep(0.1) # 等待 0.1 秒,给其他任务执行时间 - logger.info(f"PFC 循环结束 for stream_id: {self.stream_id}") # 添加日志表明循环正常结束 + logger.info(f"PFC 循环结束 for stream_id: {self.stream_id}") # 添加日志表明循环正常结束 def _check_new_messages_after_planning(self): """检查在规划后是否有新消息""" @@ -226,16 +231,16 @@ class Conversation: self, action: str, reason: str, observation_info: ObservationInfo, conversation_info: ConversationInfo ): """处理规划的行动""" - + logger.info(f"执行行动: {action}, 原因: {reason}") # 记录action历史,先设置为start,完成后再设置为done (这个 update 移到后面执行成功后再做) current_action_record = { "action": action, - "plan_reason": reason, #使用 plan_reason 存储规划原因 - "status": "start", # 初始状态为 start + "plan_reason": reason, # 使用 plan_reason 存储规划原因 + "status": "start", # 初始状态为 start "time": datetime.datetime.now().strftime("%H:%M:%S"), - "final_reason": None + "final_reason": None, } conversation_info.done_action.append(current_action_record) # 获取刚刚添加记录的索引,方便后面更新状态 @@ -244,33 +249,33 @@ class Conversation: # --- 根据不同的 action 执行 --- if action == "direct_reply": # --- 这个 if 块内部的所有代码都需要正确缩进 --- - self.waiter.wait_accumulated_time = 0 # 重置等待时间 + self.waiter.wait_accumulated_time = 0 # 重置等待时间 self.state = ConversationState.GENERATING # 生成回复 self.generated_reply = await self.reply_generator.generate(observation_info, conversation_info) - logger.info(f"生成回复: {self.generated_reply}") # 使用 logger + logger.info(f"生成回复: {self.generated_reply}") # 使用 logger # --- 调用 ReplyChecker 检查回复 --- - is_suitable = False # 先假定不合适,检查通过再改为 True - check_reason = "检查未执行" # 用不同的变量名存储检查原因 + is_suitable = False # 先假定不合适,检查通过再改为 True + check_reason = "检查未执行" # 用不同的变量名存储检查原因 need_replan = False try: # 尝试获取当前主要目标 current_goal_str = conversation_info.goal_list[0][0] if conversation_info.goal_list else "" - + # 调用检查器 is_suitable, check_reason, need_replan = await self.reply_generator.check_reply( reply=self.generated_reply, goal=current_goal_str, - chat_history=observation_info.chat_history, # 传入最新的历史记录! - retry_count=0 + chat_history=observation_info.chat_history, # 传入最新的历史记录! + retry_count=0, ) logger.info(f"回复检查结果: 合适={is_suitable}, 原因='{check_reason}', 需重新规划={need_replan}") except Exception as check_err: logger.error(f"调用 ReplyChecker 时出错: {check_err}") - check_reason = f"检查过程出错: {check_err}" # 记录错误原因 + check_reason = f"检查过程出错: {check_err}" # 记录错误原因 # is_suitable 保持 False # --- 处理检查结果 --- @@ -280,38 +285,44 @@ class Conversation: if self._check_new_messages_after_planning(): logger.info("检查到新消息,取消发送已生成的回复,重新规划行动") # 更新 action 状态为 recall - conversation_info.done_action[action_index].update({ - "status": "recall", - "reason": f"有新消息,取消发送: {self.generated_reply}", # 更新原因 - "time": datetime.datetime.now().strftime("%H:%M:%S"), - }) - return None # 退出 _handle_action + conversation_info.done_action[action_index].update( + { + "status": "recall", + "reason": f"有新消息,取消发送: {self.generated_reply}", # 更新原因 + "time": datetime.datetime.now().strftime("%H:%M:%S"), + } + ) + return None # 退出 _handle_action # 发送回复 - await self._send_reply() # 这个函数内部会处理自己的错误 + await self._send_reply() # 这个函数内部会处理自己的错误 # 更新 action 历史状态为 done - conversation_info.done_action[action_index].update({ - "status": "done", - "time": datetime.datetime.now().strftime("%H:%M:%S"), - }) + conversation_info.done_action[action_index].update( + { + "status": "done", + "time": datetime.datetime.now().strftime("%H:%M:%S"), + } + ) else: # 回复不合适 logger.warning(f"生成的回复被 ReplyChecker 拒绝: '{self.generated_reply}'. 原因: {check_reason}") # 更新 action 状态为 recall (因为没执行发送) - conversation_info.done_action[action_index].update({ - "status": "recall", - "final_reason": check_reason, - "time": datetime.datetime.now().strftime("%H:%M:%S"), - }) + conversation_info.done_action[action_index].update( + { + "status": "recall", + "final_reason": check_reason, + "time": datetime.datetime.now().strftime("%H:%M:%S"), + } + ) # 如果检查器建议重新规划 if need_replan: logger.info("ReplyChecker 建议重新规划目标。") # 可选:在此处清空目标列表以强制重新规划 - # conversation_info.goal_list = [] - + # conversation_info.goal_list = [] + # 注意:不发送消息,也不执行后面的代码 # --- 之前重复的代码块已被删除 --- @@ -323,22 +334,26 @@ class Conversation: topic = "TODO:关键词" logger.info(f"假装获取到知识{knowledge},关键词是: {topic}") if knowledge: - pass # 简单处理 + pass # 简单处理 # 标记 action 为 done - conversation_info.done_action[action_index].update({ - "status": "done", - "time": datetime.datetime.now().strftime("%H:%M:%S"), - }) + conversation_info.done_action[action_index].update( + { + "status": "done", + "time": datetime.datetime.now().strftime("%H:%M:%S"), + } + ) elif action == "rethink_goal": self.waiter.wait_accumulated_time = 0 self.state = ConversationState.RETHINKING await self.goal_analyzer.analyze_goal(conversation_info, observation_info) # 标记 action 为 done - conversation_info.done_action[action_index].update({ - "status": "done", - "time": datetime.datetime.now().strftime("%H:%M:%S"), - }) + conversation_info.done_action[action_index].update( + { + "status": "done", + "time": datetime.datetime.now().strftime("%H:%M:%S"), + } + ) elif action == "listening": self.state = ConversationState.LISTENING @@ -347,31 +362,36 @@ class Conversation: # listening 和 wait 通常在完成后不需要标记为 done,因为它们是持续状态, # 但如果需要记录,可以在 waiter 返回后标记。目前逻辑是 waiter 返回后主循环继续。 # 为了统一,可以暂时在这里也标记一下(或者都不标记) - conversation_info.done_action[action_index].update({ - "status": "done", # 或 "completed" - "time": datetime.datetime.now().strftime("%H:%M:%S"), - }) - + conversation_info.done_action[action_index].update( + { + "status": "done", # 或 "completed" + "time": datetime.datetime.now().strftime("%H:%M:%S"), + } + ) elif action == "end_conversation": - self.should_continue = False # 设置循环停止标志 + self.should_continue = False # 设置循环停止标志 logger.info("决定结束对话...") # 标记 action 为 done - conversation_info.done_action[action_index].update({ - "status": "done", - "time": datetime.datetime.now().strftime("%H:%M:%S"), - }) + conversation_info.done_action[action_index].update( + { + "status": "done", + "time": datetime.datetime.now().strftime("%H:%M:%S"), + } + ) # 这里不需要 return,主循环会在下一轮检查 should_continue - else: # 对应 'wait' 动作 + else: # 对应 'wait' 动作 self.state = ConversationState.WAITING logger.info("等待更多信息...") await self.waiter.wait(self.conversation_info) # 同 listening,可以考虑是否标记状态 - conversation_info.done_action[action_index].update({ - "status": "done", # 或 "completed" - "time": datetime.datetime.now().strftime("%H:%M:%S"), - }) + conversation_info.done_action[action_index].update( + { + "status": "done", # 或 "completed" + "time": datetime.datetime.now().strftime("%H:%M:%S"), + } + ) async def _send_timeout_message(self): """发送超时结束消息""" @@ -395,35 +415,35 @@ class Conversation: try: # 外层 try: 捕获发送消息和后续处理中的主要错误 - current_time = time.time() # 获取当前时间戳 - reply_content = self.generated_reply # 获取要发送的内容 + current_time = time.time() # 获取当前时间戳 + reply_content = self.generated_reply # 获取要发送的内容 # 发送消息 await self.direct_sender.send_message(chat_stream=self.chat_stream, content=reply_content) - logger.info(f"消息已发送: {reply_content}") # 可以在发送后加个日志确认 + logger.info(f"消息已发送: {reply_content}") # 可以在发送后加个日志确认 # --- 添加的立即更新状态逻辑开始 --- try: # 内层 try: 专门捕获手动更新状态时可能出现的错误 # 创建一个代表刚刚发送的消息的字典 bot_message_info = { - "message_id": f"bot_sent_{current_time}", # 创建一个简单的唯一ID + "message_id": f"bot_sent_{current_time}", # 创建一个简单的唯一ID "time": current_time, - "user_info": UserInfo( # 使用 UserInfo 类构建用户信息 - user_id=str(global_config.BOT_QQ), - user_nickname=global_config.BOT_NICKNAME, - platform=self.chat_stream.platform # 从 chat_stream 获取平台信息 - ).to_dict(), # 转换为字典格式存储 - "processed_plain_text": reply_content, # 使用发送的内容 - "detailed_plain_text": f"{int(current_time)},{global_config.BOT_NICKNAME}:{reply_content}", # 构造一个简单的详细文本, 时间戳取整 + "user_info": UserInfo( # 使用 UserInfo 类构建用户信息 + user_id=str(global_config.BOT_QQ), + user_nickname=global_config.BOT_NICKNAME, + platform=self.chat_stream.platform, # 从 chat_stream 获取平台信息 + ).to_dict(), # 转换为字典格式存储 + "processed_plain_text": reply_content, # 使用发送的内容 + "detailed_plain_text": f"{int(current_time)},{global_config.BOT_NICKNAME}:{reply_content}", # 构造一个简单的详细文本, 时间戳取整 # 可以根据需要添加其他字段,保持与 observation_info.chat_history 中其他消息结构一致 } # 直接更新 ObservationInfo 实例 if self.observation_info: - self.observation_info.chat_history.append(bot_message_info) # 将消息添加到历史记录末尾 - self.observation_info.last_bot_speak_time = current_time # 更新 Bot 最后发言时间 - self.observation_info.last_message_time = current_time # 更新最后消息时间 + self.observation_info.chat_history.append(bot_message_info) # 将消息添加到历史记录末尾 + self.observation_info.last_bot_speak_time = current_time # 更新 Bot 最后发言时间 + self.observation_info.last_message_time = current_time # 更新最后消息时间 logger.debug("已手动将Bot发送的消息添加到 ObservationInfo") else: logger.warning("无法手动更新 ObservationInfo:实例不存在") @@ -432,15 +452,14 @@ class Conversation: logger.error(f"手动更新 ObservationInfo 时出错: {update_err}") # --- 添加的立即更新状态逻辑结束 --- - # 原有的触发更新和等待代码 self.chat_observer.trigger_update() if not await self.chat_observer.wait_for_update(): logger.warning("等待 ChatObserver 更新完成超时") - self.state = ConversationState.ANALYZING # 更新对话状态 + self.state = ConversationState.ANALYZING # 更新对话状态 except Exception as e: # 这是外层 try 对应的 except logger.error(f"发送消息或更新状态时失败: {str(e)}") - self.state = ConversationState.ANALYZING # 出错也要尝试恢复状态 \ No newline at end of file + self.state = ConversationState.ANALYZING # 出错也要尝试恢复状态 diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index 08d4fabf1..873d14674 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -100,15 +100,15 @@ class GoalAnalyzer: pronouns = ["你", "我", "他"] for p in pronouns: if identity_details_only.startswith(p): - identity_details_only = identity_details_only[len(p):] + identity_details_only = identity_details_only[len(p) :] break if identity_details_only.endswith("。"): - identity_details_only = identity_details_only[:-1] - cleaned_details = identity_details_only.strip(',, ') + identity_details_only = identity_details_only[:-1] + cleaned_details = identity_details_only.strip(",, ") if cleaned_details: identity_addon = f"并且{cleaned_details}" - persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" + persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" # 构建action历史文本 action_history_list = conversation_info.done_action action_history_text = "你之前做的事情是:" @@ -263,18 +263,18 @@ class GoalAnalyzer: pronouns = ["你", "我", "他"] for p in pronouns: if identity_details_only.startswith(p): - identity_details_only = identity_details_only[len(p):] + identity_details_only = identity_details_only[len(p) :] break if identity_details_only.endswith("。"): - identity_details_only = identity_details_only[:-1] - cleaned_details = identity_details_only.strip(',, ') + identity_details_only = identity_details_only[:-1] + cleaned_details = identity_details_only.strip(",, ") if cleaned_details: identity_addon = f"并且{cleaned_details}" persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" - # ===> Persona 文本构建结束 <=== + # ===> Persona 文本构建结束 <=== - # --- 修改 Prompt 字符串,使用 persona_text --- + # --- 修改 Prompt 字符串,使用 persona_text --- prompt = f"""{persona_text}。现在你在参与一场QQ聊天, 当前对话目标:{goal} 产生该对话目标的原因:{reasoning} diff --git a/src/plugins/PFC/reply_checker.py b/src/plugins/PFC/reply_checker.py index 72489251c..f4e1c9901 100644 --- a/src/plugins/PFC/reply_checker.py +++ b/src/plugins/PFC/reply_checker.py @@ -21,7 +21,9 @@ class ReplyChecker: self.chat_observer = ChatObserver.get_instance(stream_id) self.max_retries = 2 # 最大重试次数 - async def check(self, reply: str, goal: str, chat_history: List[Dict[str, Any]], retry_count: int = 0) -> Tuple[bool, str, bool]: + async def check( + self, reply: str, goal: str, chat_history: List[Dict[str, Any]], retry_count: int = 0 + ) -> Tuple[bool, str, bool]: """检查生成的回复是否合适 Args: @@ -40,19 +42,24 @@ class ReplyChecker: bot_messages = [] for msg in reversed(chat_history): user_info = UserInfo.from_dict(msg.get("user_info", {})) - if str(user_info.user_id) == str(global_config.BOT_QQ): # 确保比较的是字符串 - bot_messages.append(msg.get('processed_plain_text', '')) - if len(bot_messages) >= 2: # 只和最近的两条比较 + if str(user_info.user_id) == str(global_config.BOT_QQ): # 确保比较的是字符串 + bot_messages.append(msg.get("processed_plain_text", "")) + if len(bot_messages) >= 2: # 只和最近的两条比较 break # 进行比较 if bot_messages: # 可以用简单比较,或者更复杂的相似度库 (如 difflib) # 简单比较:是否完全相同 - if reply == bot_messages[0]: # 和最近一条完全一样 + if reply == bot_messages[0]: # 和最近一条完全一样 logger.warning(f"ReplyChecker 检测到回复与上一条 Bot 消息完全相同: '{reply}'") - return False, "回复内容与你上一条发言完全相同,请修改,可以选择深入话题或寻找其它话题或等待", False # 不合适,无需重新规划 + return ( + False, + "回复内容与你上一条发言完全相同,请修改,可以选择深入话题或寻找其它话题或等待", + False, + ) # 不合适,无需重新规划 # 2. 相似度检查 (如果精确匹配未通过) - import difflib # 导入 difflib 库 + import difflib # 导入 difflib 库 + # 计算编辑距离相似度,ratio() 返回 0 到 1 之间的浮点数 similarity_ratio = difflib.SequenceMatcher(None, reply, bot_messages[0]).ratio() logger.debug(f"ReplyChecker - 相似度: {similarity_ratio:.2f}") @@ -60,11 +67,17 @@ class ReplyChecker: # 设置一个相似度阈值 similarity_threshold = 0.9 if similarity_ratio > similarity_threshold: - logger.warning(f"ReplyChecker 检测到回复与上一条 Bot 消息高度相似 (相似度 {similarity_ratio:.2f}): '{reply}'") - return False, f"拒绝发送:回复内容与你上一条发言高度相似 (相似度 {similarity_ratio:.2f}),请修改,可以选择深入话题或寻找其它话题或等待。", False + logger.warning( + f"ReplyChecker 检测到回复与上一条 Bot 消息高度相似 (相似度 {similarity_ratio:.2f}): '{reply}'" + ) + return ( + False, + f"拒绝发送:回复内容与你上一条发言高度相似 (相似度 {similarity_ratio:.2f}),请修改,可以选择深入话题或寻找其它话题或等待。", + False, + ) except Exception as self_check_err: - logger.error(f"检查自身重复发言时出错: {self_check_err}") + logger.error(f"检查自身重复发言时出错: {self_check_err}") for msg in chat_history[-20:]: time_str = datetime.datetime.fromtimestamp(msg["time"]).strftime("%H:%M:%S") diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index 5ef58e271..15cd7dee5 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -92,14 +92,14 @@ class ReplyGenerator: pronouns = ["你", "我", "他"] for p in pronouns: if identity_details_only.startswith(p): - identity_details_only = identity_details_only[len(p):] - break + identity_details_only = identity_details_only[len(p) :] + break if identity_details_only.endswith("。"): - identity_details_only = identity_details_only[:-1] - cleaned_details = identity_details_only.strip(',, ') - if cleaned_details: - identity_addon = f"并且{cleaned_details}" - persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" + identity_details_only = identity_details_only[:-1] + cleaned_details = identity_details_only.strip(",, ") + if cleaned_details: + identity_addon = f"并且{cleaned_details}" + persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" # 构建action历史文本 action_history_list = ( conversation_info.done_action[-10:] @@ -172,7 +172,9 @@ class ReplyGenerator: logger.error(f"生成回复时出错: {e}") return "抱歉,我现在有点混乱,让我重新思考一下..." - async def check_reply(self, reply: str, goal: str, chat_history: List[Dict[str, Any]], retry_count: int = 0) -> Tuple[bool, str, bool]: + async def check_reply( + self, reply: str, goal: str, chat_history: List[Dict[str, Any]], retry_count: int = 0 + ) -> Tuple[bool, str, bool]: """检查回复是否合适 Args: diff --git a/src/plugins/PFC/waiter.py b/src/plugins/PFC/waiter.py index 05702a214..eaf8a768a 100644 --- a/src/plugins/PFC/waiter.py +++ b/src/plugins/PFC/waiter.py @@ -1,6 +1,7 @@ from src.common.logger import get_module_logger from .chat_observer import ChatObserver from .conversation_info import ConversationInfo + # from src.individuality.individuality import Individuality # 不再需要 from ...config.config import global_config import time @@ -10,7 +11,8 @@ logger = get_module_logger("waiter") # --- 在这里设定你想要的超时时间(秒) --- # 例如: 120 秒 = 2 分钟 -DESIRED_TIMEOUT_SECONDS = 300 +DESIRED_TIMEOUT_SECONDS = 300 + class Waiter: """等待处理类""" @@ -29,7 +31,7 @@ class Waiter: # 检查是否有新消息 if self.chat_observer.new_message_after(wait_start_time): logger.info("等待结束,收到新消息") - return False # 返回 False 表示不是超时 + return False # 返回 False 表示不是超时 # 检查是否超时 elapsed_time = time.time() - wait_start_time @@ -41,10 +43,10 @@ class Waiter: } conversation_info.goal_list.append(wait_goal) logger.info(f"添加目标: {wait_goal}") - return True # 返回 True 表示超时 + return True # 返回 True 表示超时 - await asyncio.sleep(5) # 每 5 秒检查一次 - logger.info("等待中...") # 可以考虑把这个频繁日志注释掉,只在超时或收到消息时输出 + await asyncio.sleep(5) # 每 5 秒检查一次 + logger.info("等待中...") # 可以考虑把这个频繁日志注释掉,只在超时或收到消息时输出 async def wait_listening(self, conversation_info: ConversationInfo) -> bool: """倾听用户发言或超时""" @@ -55,7 +57,7 @@ class Waiter: # 检查是否有新消息 if self.chat_observer.new_message_after(wait_start_time): logger.info("倾听等待结束,收到新消息") - return False # 返回 False 表示不是超时 + return False # 返回 False 表示不是超时 # 检查是否超时 elapsed_time = time.time() - wait_start_time @@ -63,12 +65,12 @@ class Waiter: logger.info(f"倾听等待超过 {DESIRED_TIMEOUT_SECONDS} 秒...添加思考目标。") wait_goal = { # 保持 goal 文本一致 - "goal": f"你等待了{elapsed_time / 60:.1f}分钟,注意可能在对方看来聊天已经结束,思考接下来要做什么", + "goal": f"你等待了{elapsed_time / 60:.1f}分钟,注意可能在对方看来聊天已经结束,思考接下来要做什么", "reason": "对方话说一半消失了,很久没有回复", } conversation_info.goal_list.append(wait_goal) logger.info(f"添加目标: {wait_goal}") - return True # 返回 True 表示超时 + return True # 返回 True 表示超时 - await asyncio.sleep(5) # 每 5 秒检查一次 - logger.info("倾听等待中...") # 同上,可以考虑注释掉 \ No newline at end of file + await asyncio.sleep(5) # 每 5 秒检查一次 + logger.info("倾听等待中...") # 同上,可以考虑注释掉