From 8b97558467c1abeae714731c7e95e057428614a8 Mon Sep 17 00:00:00 2001 From: 114514 <2514624910@qq.com> Date: Sun, 27 Apr 2025 15:04:16 +0800 Subject: [PATCH 01/19] src/plugins/PFC/action_planner.py --- src/plugins/PFC/action_planner.py | 194 +++++++---- src/plugins/PFC/conversation.py | 496 +++++++++++++++++---------- src/plugins/PFC/conversation_info.py | 2 + src/plugins/PFC/reply_generator.py | 197 ++++++----- 4 files changed, 531 insertions(+), 358 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 082741263..701770181 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -1,5 +1,5 @@ import time -from typing import Tuple +from typing import Tuple, Optional # 增加了 Optional from src.common.logger import get_module_logger, LogConfig, PFC_ACTION_PLANNER_STYLE_CONFIG from ..models.utils_model import LLMRequest from ...config.config import global_config @@ -18,14 +18,73 @@ pfc_action_log_config = LogConfig( logger = get_module_logger("action_planner", config=pfc_action_log_config) -# 注意:这个 ActionPlannerInfo 类似乎没有在 ActionPlanner 中使用, -# 如果确实没用,可以考虑移除,但暂时保留以防万一。 -class ActionPlannerInfo: - def __init__(self): - self.done_action = [] - self.goal_list = [] - self.knowledge_list = [] - self.memory_list = [] +# --- 定义 Prompt 模板 --- + +# Prompt(1): 首次回复或非连续回复时的决策 Prompt +PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以发言,可以等待,可以倾听,可以调取知识,甚至可以屏蔽对方: + +【当前对话目标】 +{goals_str} + +【最近行动历史概要】 +{action_history_summary} +【上一次行动的详细情况和结果】 +{last_action_context} +【时间和超时提示】 +{time_since_last_bot_message_info}{timeout_context} +【最近的对话记录】(包括你已成功发送的消息 和 新收到的消息) +{chat_history_text} + +------ +可选行动类型以及解释: +fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择 +wait: 暂时不说话,等待对方回复(尤其是在你刚发言后、或上次发言因重复、发言过多被拒时、或不确定做什么时,这是较安全的选择) +listening: 倾听对方发言,当你认为对方话才说到一半,发言明显未结束时选择 +direct_reply: 直接回复对方 +rethink_goal: 重新思考对话目标,当发现对话目标不再适用或对话卡住时选择,注意私聊的环境是灵活的,有可能需要经常选择 +end_conversation: 结束对话,对方长时间没回复或者当你觉得对话告一段落时可以选择 +block_and_ignore: 更加极端的结束对话方式,直接结束对话并在一段时间内无视对方所有发言(屏蔽),当对话让你感到十分不适,或你遭到各类骚扰时选择 + +请以JSON格式输出你的决策: +{{ + "action": "选择的行动类型 (必须是上面列表中的一个)", + "reason": "选择该行动的详细原因 (必须有解释你是如何根据“上一次行动结果”、“对话记录”和自身设定人设做出合理判断的)" +}} + +注意:请严格按照JSON格式输出,不要包含任何其他内容。""" + +# Prompt(2): 上一次成功回复后,决定继续发言时的决策 Prompt +PROMPT_FOLLOW_UP = """{persona_text}。现在你在参与一场QQ私聊,刚刚你已经回复了对方,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以继续发送新消息,可以等待,可以倾听,可以调取知识,甚至可以屏蔽对方: + +【当前对话目标】 +{goals_str} + +【最近行动历史概要】 +{action_history_summary} +【上一次行动的详细情况和结果】 +{last_action_context} +【时间和超时提示】 +{time_since_last_bot_message_info}{timeout_context} +【最近的对话记录】(包括你已成功发送的消息 和 新收到的消息) +{chat_history_text} + +------ +可选行动类型以及解释: +fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择 +wait: 暂时不说话,等待对方回复(尤其是在你刚发言后、或上次发言因重复、发言过多被拒时、或不确定做什么时,这是不错的选择) +listening: 倾听对方发言(虽然你刚发过言,但如果对方立刻回复且明显话没说完,可以选择这个) +send_new_message: 发送一条新消息继续对话,允许适当的追问、补充、深入话题,或开启相关新话题。**但是避免在因重复被拒后立即使用,也不要在对方没有回复的情况下过多的“消息轰炸”或重复发言** +rethink_goal: 重新思考对话目标,当发现对话目标不再适用或对话卡住时选择,注意私聊的环境是灵活的,有可能需要经常选择 +end_conversation: 结束对话,对方长时间没回复或者当你觉得对话告一段落时可以选择 +block_and_ignore: 更加极端的结束对话方式,直接结束对话并在一段时间内无视对方所有发言(屏蔽),当对话让你感到十分不适,或你遭到各类骚扰时选择 + +请以JSON格式输出你的决策: +{{ + "action": "选择的行动类型 (必须是上面列表中的一个)", + "reason": "选择该行动的详细原因 (必须有解释你是如何根据“上一次行动结果”、“对话记录”和自身设定人设做出合理判断的。请说明你为什么选择继续发言而不是等待,以及打算发送什么类型的新消息连续发言,必须记录已经发言了几次)" +}} + +注意:请严格按照JSON格式输出,不要包含任何其他内容。""" # ActionPlanner 类定义,顶格 @@ -43,18 +102,22 @@ class ActionPlanner: self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2) self.name = global_config.BOT_NICKNAME self.chat_observer = ChatObserver.get_instance(stream_id) + # self.action_planner_info = ActionPlannerInfo() # 移除未使用的变量 - async def plan(self, observation_info: ObservationInfo, conversation_info: ConversationInfo) -> Tuple[str, str]: + # 修改 plan 方法签名,增加 last_successful_reply_action 参数 + async def plan(self, observation_info: ObservationInfo, conversation_info: ConversationInfo, last_successful_reply_action: Optional[str]) -> Tuple[str, str]: """规划下一步行动 Args: observation_info: 决策信息 conversation_info: 对话信息 + last_successful_reply_action: 上一次成功的回复动作类型 ('direct_reply' 或 'send_new_message' 或 None) Returns: Tuple[str, str]: (行动类型, 行动原因) """ # --- 获取 Bot 上次发言时间信息 --- + # (这部分逻辑不变) time_since_last_bot_message_info = "" try: bot_id = str(global_config.BOT_QQ) @@ -79,10 +142,12 @@ class ActionPlanner: logger.warning("ObservationInfo object might not have chat_history attribute yet for bot time check.") except Exception as e: logger.warning(f"获取 Bot 上次发言时间时出错: {e}") - # --- 获取 Bot 上次发言时间信息结束 --- + + # --- 获取超时提示信息 --- + # (这部分逻辑不变) timeout_context = "" - try: # 添加 try-except 以增加健壮性 + try: 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: @@ -100,12 +165,12 @@ class ActionPlanner: except Exception as e: logger.warning(f"检查超时目标时出错: {e}") - # 构建提示词 - logger.debug(f"开始规划行动:当前目标: {getattr(conversation_info, 'goal_list', '不可用')}") # 使用 getattr + # --- 构建通用 Prompt 参数 --- + logger.debug(f"开始规划行动:当前目标: {getattr(conversation_info, 'goal_list', '不可用')}") # 构建对话目标 (goals_str) goals_str = "" - try: # 添加 try-except + try: 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: @@ -120,7 +185,7 @@ 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 = "- 目前没有明确对话目标,请考虑设定一个。\n" except AttributeError: logger.warning("ConversationInfo object might not have goal_list attribute yet.") @@ -134,7 +199,7 @@ class ActionPlanner: try: if hasattr(observation_info, "chat_history") and observation_info.chat_history: chat_history_text = observation_info.chat_history_str - if not chat_history_text: # 如果历史记录是空列表 + if not chat_history_text: chat_history_text = "还没有聊天记录。\n" else: chat_history_text = "还没有聊天记录。\n" @@ -152,9 +217,6 @@ class ActionPlanner: chat_history_text += ( f"\n--- 以下是 {observation_info.new_messages_count} 条新消息 ---\n{new_messages_str}" ) - # 清理消息应该由调用者或 observation_info 内部逻辑处理,这里不再调用 clear - # 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." @@ -167,11 +229,11 @@ class ActionPlanner: chat_history_text = "处理聊天记录时出错。\n" # 构建 Persona 文本 (persona_text) + # (这部分逻辑不变) identity_details_only = self.identity_detail_info identity_addon = "" if isinstance(identity_details_only, str): pronouns = ["你", "我", "他"] - # original_details = identity_details_only for p in pronouns: if identity_details_only.startswith(p): identity_details_only = identity_details_only[len(p) :] @@ -183,12 +245,13 @@ class ActionPlanner: identity_addon = f"并且{cleaned_details}" persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" - # --- 构建更清晰的行动历史和上一次行动结果 --- + + # 构建行动历史和上一次行动结果 (action_history_summary, last_action_context) + # (这部分逻辑不变) action_history_summary = "你最近执行的行动历史:\n" last_action_context = "关于你【上一次尝试】的行动:\n" - action_history_list = [] - try: # 添加 try-except + try: if hasattr(conversation_info, "done_action") and conversation_info.done_action: action_history_list = conversation_info.done_action[-5:] else: @@ -216,14 +279,12 @@ 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: - action_type = action_data[0] - if len(action_data) > 1: - plan_reason = action_data[1] - if len(action_data) > 2: - status = action_data[2] - if status == "recall" and len(action_data) > 3: - final_reason = action_data[3] + # 假设旧格式兼容 + if len(action_data) > 0: action_type = action_data[0] + if len(action_data) > 1: plan_reason = action_data[1] # 可能是规划原因或最终原因 + if len(action_data) > 2: status = action_data[2] + if status == "recall" and len(action_data) > 3: final_reason = action_data[3] + elif status == "done" and action_type in ["direct_reply", "send_new_message"]: plan_reason = "成功发送" # 简化显示 reason_text = f", 失败/取消原因: {final_reason}" if final_reason else "" summary_line = f"- 时间:{action_time}, 尝试行动:'{action_type}', 状态:{status}{reason_text}" @@ -234,50 +295,39 @@ class ActionPlanner: last_action_context += f"- 当时规划的【原因】是: {plan_reason}\n" if status == "done": last_action_context += "- 该行动已【成功执行】。\n" + # 记录这次成功的行动类型,供下次决策 + # self.last_successful_action_type = action_type # 不在这里记录,由 conversation 控制 elif status == "recall": last_action_context += "- 但该行动最终【未能执行/被取消】。\n" if final_reason: last_action_context += f"- 【重要】失败/取消的具体原因是: “{final_reason}”\n" else: last_action_context += "- 【重要】失败/取消原因未明确记录。\n" + # self.last_successful_action_type = None # 行动失败,清除记录 else: last_action_context += f"- 该行动当前状态: {status}\n" + # self.last_successful_action_type = None # 非完成状态,清除记录 - # --- 构建最终的 Prompt --- - prompt = f"""{persona_text}。现在你在参与一场QQ私聊,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以发言,可以等待,可以倾听,可以调取知识,甚至可以屏蔽对方: + # --- 选择 Prompt --- + if last_successful_reply_action in ['direct_reply', 'send_new_message']: + prompt_template = PROMPT_FOLLOW_UP + logger.info("使用 PROMPT_FOLLOW_UP (追问决策)") + else: + prompt_template = PROMPT_INITIAL_REPLY + logger.info("使用 PROMPT_INITIAL_REPLY (首次/非连续回复决策)") -【当前对话目标】 -{goals_str if goals_str.strip() else "- 目前没有明确对话目标,请考虑设定一个。"} + # --- 格式化最终的 Prompt --- + prompt = prompt_template.format( + persona_text=persona_text, + goals_str=goals_str if goals_str.strip() else "- 目前没有明确对话目标,请考虑设定一个。", + action_history_summary=action_history_summary, + last_action_context=last_action_context, + time_since_last_bot_message_info=time_since_last_bot_message_info, + timeout_context=timeout_context, + chat_history_text=chat_history_text if chat_history_text.strip() else "还没有聊天记录。" + ) - -【最近行动历史概要】 -{action_history_summary} -【上一次行动的详细情况和结果】 -{last_action_context} -【时间和超时提示】 -{time_since_last_bot_message_info}{timeout_context} -【最近的对话记录】(包括你已成功发送的消息 和 新收到的消息) -{chat_history_text if chat_history_text.strip() else "还没有聊天记录。"} - ------- -可选行动类型以及解释: -fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择 -wait: 暂时不说话,等待对方回复(尤其是在你刚发言后、或上次发言因重复、发言过多被拒时、或不确定做什么时,这是较安全的选择) -listening: 倾听对方发言,当你认为对方话才说到一半,发言明显未结束时选择 -direct_reply: 直接回复或发送新消息,允许适当的追问和深入话题,**但是避免在因重复被拒后立即使用,也不要在对方没有回复的情况下过多的“消息轰炸”或重复发言** -rethink_goal: 重新思考对话目标,当发现对话目标不再适用或对话卡住时选择,注意私聊的环境是灵活的,有可能需要经常选择 -end_conversation: 结束对话,对方长时间没回复或者当你觉得对话告一段落时可以选择 -block_and_ignore: 更加极端的结束对话方式,直接结束对话并在一段时间内无视对方所有发言(屏蔽),当对话让你感到十分不适,或你遭到各类骚扰时选择 - -请以JSON格式输出你的决策: -{{ - "action": "选择的行动类型 (必须是上面列表中的一个)", - "reason": "选择该行动的详细原因 (必须有解释你是如何根据“上一次行动结果”、“对话记录”和自身设定人设做出合理判断的,如果你连续发言,必须记录已经发言了几次)" -}} - -注意:请严格按照JSON格式输出,不要包含任何其他内容。""" - - logger.debug(f"发送到LLM的提示词 (已更新): {prompt}") + logger.debug(f"发送到LLM的最终提示词:\n------\n{prompt}\n------") try: content, _ = await self.llm.generate_response_async(prompt) logger.debug(f"LLM原始返回内容: {content}") @@ -293,7 +343,17 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在 reason = result.get("reason", "LLM未提供原因,默认等待") # 验证action类型 - valid_actions = ["direct_reply", "fetch_knowledge", "wait", "listening", "rethink_goal", "end_conversation", "block_and_ignore"] + # 更新 valid_actions 列表以包含 send_new_message + valid_actions = [ + "direct_reply", + "send_new_message", # 添加新动作 + "fetch_knowledge", + "wait", + "listening", + "rethink_goal", + "end_conversation", + "block_and_ignore" + ] if action not in valid_actions: logger.warning(f"LLM返回了未知的行动类型: '{action}',强制改为 wait") reason = f"(原始行动'{action}'无效,已强制改为wait) {reason}" @@ -305,4 +365,4 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在 except Exception as e: logger.error(f"规划行动时调用 LLM 或处理结果出错: {str(e)}") - return "wait", f"行动规划处理中发生错误,暂时等待: {str(e)}" + return "wait", f"行动规划处理中发生错误,暂时等待: {str(e)}" \ No newline at end of file diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index f68098f13..500990805 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -11,7 +11,7 @@ from .pfc import ChatObserver, GoalAnalyzer, DirectMessageSender from src.common.logger import get_module_logger from .action_planner import ActionPlanner from .observation_info import ObservationInfo -from .conversation_info import ConversationInfo +from .conversation_info import ConversationInfo # 确保导入 ConversationInfo from .reply_generator import ReplyGenerator from ..chat.chat_stream import ChatStream from maim_message import UserInfo @@ -102,9 +102,6 @@ class Conversation: 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}" ) @@ -134,18 +131,15 @@ class Conversation: async def _plan_and_action_loop(self): """思考步,PFC核心循环模块""" while self.should_continue: + # 忽略逻辑 if self.ignore_until_timestamp and time.time() < self.ignore_until_timestamp: - # 仍在忽略期间,等待下次检查 - await asyncio.sleep(30) # 每 30 秒检查一次 - continue # 跳过本轮循环的剩余部分 + await asyncio.sleep(30) + continue elif self.ignore_until_timestamp and time.time() >= self.ignore_until_timestamp: - # 忽略期结束,现在正常地结束对话 logger.info(f"忽略时间已到 {self.stream_id},准备结束对话。") - self.ignore_until_timestamp = None # 清除时间戳 - self.should_continue = False # 现在停止循环 - # (可选)在这里记录一个 'end_conversation' 动作 - # 或者确保管理器会基于 should_continue 为 False 来清理它 - continue # 跳过本轮循环的剩余部分,让它终止 + self.ignore_until_timestamp = None + self.should_continue = False + continue try: # --- 在规划前记录当前新消息数量 --- initial_new_message_count = 0 @@ -154,10 +148,13 @@ class Conversation: else: logger.warning("ObservationInfo missing 'new_messages_count' before planning.") - # 使用决策信息来辅助行动规划 + # --- 调用 Action Planner --- + # 传递 self.conversation_info.last_successful_reply_action action, reason = await self.action_planner.plan( - self.observation_info, self.conversation_info - ) # 注意:plan 函数内部现在不应再调用 clear_unprocessed_messages + self.observation_info, + self.conversation_info, + self.conversation_info.last_successful_reply_action + ) # --- 规划后检查是否有 *更多* 新消息到达 --- current_new_message_count = 0 @@ -167,84 +164,101 @@ class Conversation: 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 # 跳过本次行动,重新规划 + # 如果规划期间有新消息,也应该重置上次回复状态,因为现在要响应新消息了 + self.conversation_info.last_successful_reply_action = None + await asyncio.sleep(0.1) + continue - # --- 如果没有在规划期间收到更多新消息,则准备执行行动 --- - - # --- 清理未处理消息:移到这里,在执行动作前 --- - # 只有当确实有新消息被 planner 看到,并且 action 是要处理它们的时候才清理 - if initial_new_message_count > 0 and action == "direct_reply": + # 包含 send_new_message + if initial_new_message_count > 0 and action in ["direct_reply", "send_new_message"]: if hasattr(self.observation_info, "clear_unprocessed_messages"): - # 确保 clear_unprocessed_messages 方法存在 - logger.debug(f"准备执行 direct_reply,清理 {initial_new_message_count} 条规划时已知的新消息。") + logger.debug(f"准备执行 {action},清理 {initial_new_message_count} 条规划时已知的新消息。") await 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,或者抛出错误 - # --- 执行行动 --- + 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: - for goal in self.conversation_info.goal_list: - if isinstance(goal, tuple) and len(goal) > 0 and goal[0] == "结束对话": - goal_ended = True - break - elif isinstance(goal, dict) and goal.get("goal") == "结束对话": + for goal_item in self.conversation_info.goal_list: + current_goal = None + if isinstance(goal_item, tuple) and len(goal_item) > 0: + current_goal = goal_item[0] + elif isinstance(goal_item, dict): + current_goal = goal_item.get("goal") + + if current_goal == "结束对话": goal_ended = True break if goal_ended: self.should_continue = False logger.info("检测到'结束对话'目标,停止循环。") - # 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 秒,给其他任务执行时间 + await asyncio.sleep(1) - logger.info(f"PFC 循环结束 for stream_id: {self.stream_id}") # 添加日志表明循环正常结束 + if self.should_continue: + await asyncio.sleep(0.1) + + logger.info(f"PFC 循环结束 for stream_id: {self.stream_id}") def _check_new_messages_after_planning(self): """检查在规划后是否有新消息""" + # 检查 ObservationInfo 是否已初始化并且有 new_messages_count 属性 + if not hasattr(self, 'observation_info') or not hasattr(self.observation_info, 'new_messages_count'): + logger.warning("ObservationInfo 未初始化或缺少 'new_messages_count' 属性,无法检查新消息。") + return False # 或者根据需要抛出错误 + if self.observation_info.new_messages_count > 0: - logger.info(f"发现{self.observation_info.new_messages_count}条新消息,可能需要重新考虑行动") - # 如果需要,可以在这里添加逻辑来根据新消息重新决定行动 + logger.info(f"生成/执行动作期间收到 {self.observation_info.new_messages_count} 条新消息,取消当前动作并重新规划") + # 如果有新消息,也应该重置上次回复状态 + if hasattr(self, 'conversation_info'): # 确保 conversation_info 已初始化 + self.conversation_info.last_successful_reply_action = None + else: + logger.warning("ConversationInfo 未初始化,无法重置 last_successful_reply_action。") return True return False def _convert_to_message(self, msg_dict: Dict[str, Any]) -> Message: """将消息字典转换为Message对象""" try: - chat_info = msg_dict.get("chat_info", {}) - chat_stream = ChatStream.from_dict(chat_info) + # 尝试从 msg_dict 直接获取 chat_stream,如果失败则从全局 chat_manager 获取 + chat_info = msg_dict.get("chat_info") + if chat_info and isinstance(chat_info, dict): + chat_stream = ChatStream.from_dict(chat_info) + elif self.chat_stream: # 使用实例变量中的 chat_stream + chat_stream = self.chat_stream + else: # Fallback: 尝试从 manager 获取 (可能需要 stream_id) + chat_stream = chat_manager.get_stream(self.stream_id) + if not chat_stream: + raise ValueError(f"无法确定 ChatStream for stream_id {self.stream_id}") + user_info = UserInfo.from_dict(msg_dict.get("user_info", {})) return Message( - message_id=msg_dict["message_id"], - chat_stream=chat_stream, - time=msg_dict["time"], + message_id=msg_dict.get("message_id", f"gen_{time.time()}"), # 提供默认 ID + chat_stream=chat_stream, # 使用确定的 chat_stream + time=msg_dict.get("time", time.time()), # 提供默认时间 user_info=user_info, processed_plain_text=msg_dict.get("processed_plain_text", ""), detailed_plain_text=msg_dict.get("detailed_plain_text", ""), ) except Exception as e: logger.warning(f"转换消息时出错: {e}") - raise + # 可以选择返回 None 或重新抛出异常,这里选择重新抛出以指示问题 + raise ValueError(f"无法将字典转换为 Message 对象: {e}") from e async def _handle_action( self, action: str, reason: str, observation_info: ObservationInfo, conversation_info: ConversationInfo @@ -253,21 +267,27 @@ class Conversation: logger.info(f"执行行动: {action}, 原因: {reason}") - # 记录action历史,先设置为start,完成后再设置为done (这个 update 移到后面执行成功后再做) + # 记录action历史 (逻辑不变) current_action_record = { "action": action, - "plan_reason": reason, # 使用 plan_reason 存储规划原因 - "status": "start", # 初始状态为 start + "plan_reason": reason, + "status": "start", "time": datetime.datetime.now().strftime("%H:%M:%S"), "final_reason": None, } + # 确保 done_action 列表存在 + if not hasattr(conversation_info, 'done_action'): + conversation_info.done_action = [] conversation_info.done_action.append(current_action_record) - # 获取刚刚添加记录的索引,方便后面更新状态 action_index = len(conversation_info.done_action) - 1 + action_successful = False # 用于标记动作是否成功完成 + # --- 根据不同的 action 执行 --- - if action == "direct_reply": - max_reply_attempts = 3 # 设置最大尝试次数(与 reply_checker.py 中的 max_retries 保持一致或稍大) + + # send_new_message 失败后执行 wait + if action == "send_new_message": + max_reply_attempts = 3 reply_attempt_count = 0 is_suitable = False need_replan = False @@ -276,179 +296,301 @@ class Conversation: while reply_attempt_count < max_reply_attempts and not is_suitable: reply_attempt_count += 1 - logger.info(f"尝试生成回复 (第 {reply_attempt_count}/{max_reply_attempts} 次)...") + logger.info(f"尝试生成追问回复 (第 {reply_attempt_count}/{max_reply_attempts} 次)...") self.state = ConversationState.GENERATING - # 1. 生成回复 - self.generated_reply = await self.reply_generator.generate(observation_info, conversation_info) - logger.info(f"第 {reply_attempt_count} 次生成的回复: {self.generated_reply}") + # 1. 生成回复 (调用 generate 时传入 action_type) + self.generated_reply = await self.reply_generator.generate(observation_info, conversation_info, action_type='send_new_message') + logger.info(f"第 {reply_attempt_count} 次生成的追问回复: {self.generated_reply}") - # 2. 检查回复 + # 2. 检查回复 (逻辑不变) self.state = ConversationState.CHECKING try: current_goal_str = conversation_info.goal_list[0][0] if conversation_info.goal_list else "" - # 注意:这里传递的是 reply_attempt_count - 1 作为 retry_count 给 checker 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, chat_history_str=observation_info.chat_history_str, - retry_count=reply_attempt_count - 1, # 传递当前尝试次数(从0开始计数) + retry_count=reply_attempt_count - 1, ) logger.info( - f"第 {reply_attempt_count} 次检查结果: 合适={is_suitable}, 原因='{check_reason}', 需重新规划={need_replan}" + f"第 {reply_attempt_count} 次追问检查结果: 合适={is_suitable}, 原因='{check_reason}', 需重新规划={need_replan}" ) - if is_suitable: - final_reply_to_send = self.generated_reply # 保存合适的回复 - break # 回复合适,跳出循环 - + final_reply_to_send = self.generated_reply + break elif need_replan: - logger.warning(f"第 {reply_attempt_count} 次检查建议重新规划,停止尝试。原因: {check_reason}") - break # 如果检查器建议重新规划,也停止尝试 - - # 如果不合适但不需要重新规划,循环会继续进行下一次尝试 + logger.warning(f"第 {reply_attempt_count} 次追问检查建议重新规划,停止尝试。原因: {check_reason}") + break except Exception as check_err: - logger.error(f"第 {reply_attempt_count} 次调用 ReplyChecker 时出错: {check_err}") + logger.error(f"第 {reply_attempt_count} 次调用 ReplyChecker (追问) 时出错: {check_err}") check_reason = f"第 {reply_attempt_count} 次检查过程出错: {check_err}" - # 如果检查本身出错,可以选择跳出循环或继续尝试 - # 这里选择跳出循环,避免无限循环在检查错误上 break # 循环结束,处理最终结果 if is_suitable: - # 回复合适且已保存在 final_reply_to_send 中 - # 检查是否有新消息进来 (在所有尝试结束后再检查一次) + # 检查是否有新消息 if self._check_new_messages_after_planning(): - logger.info("生成回复期间收到新消息,取消发送,重新规划行动") + logger.info("生成追问回复期间收到新消息,取消发送,重新规划行动") conversation_info.done_action[action_index].update( - { - "status": "recall", - "final_reason": f"有新消息,取消发送: {final_reply_to_send}", - "time": datetime.datetime.now().strftime("%H:%M:%S"), - } + {"status": "recall", "final_reason": f"有新消息,取消发送追问: {final_reply_to_send}"} ) - # 这里直接返回,不执行后续发送和wait - return + return # 直接返回,重新规划 # 发送合适的回复 - self.generated_reply = final_reply_to_send # 确保 self.generated_reply 是最终要发送的内容 - await self._send_reply() + self.generated_reply = final_reply_to_send + # --- 在这里调用 _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"), - } - ) + # 更新状态: 标记上次成功是 send_new_message + self.conversation_info.last_successful_reply_action = 'send_new_message' + action_successful = True # 标记动作成功 else: - # 循环结束但没有找到合适的回复(达到最大次数或检查出错/建议重规划) - logger.warning(f"经过 {reply_attempt_count} 次尝试,未能生成合适的回复。最终原因: {check_reason}") + # 追问失败 + logger.warning(f"经过 {reply_attempt_count} 次尝试,未能生成合适的追问回复。最终原因: {check_reason}") conversation_info.done_action[action_index].update( - { - "status": "recall", # 标记为 recall 因为没有成功发送 - "final_reason": f"尝试{reply_attempt_count}次后失败: {check_reason}", - "time": datetime.datetime.now().strftime("%H:%M:%S"), - } + {"status": "recall", "final_reason": f"追问尝试{reply_attempt_count}次后失败: {check_reason}"} ) + # 重置状态: 追问失败,下次用初始 prompt + self.conversation_info.last_successful_reply_action = None # 执行 Wait 操作 - logger.info("由于无法生成合适回复,执行 'wait' 操作...") + logger.info("由于无法生成合适追问回复,执行 'wait' 操作...") + self.state = ConversationState.WAITING + await self.waiter.wait(self.conversation_info) + wait_action_record = { + "action": "wait", + "plan_reason": "因 send_new_message 多次尝试失败而执行的后备等待", + "status": "done", + "time": datetime.datetime.now().strftime("%H:%M:%S"), + "final_reason": None, + } + conversation_info.done_action.append(wait_action_record) + + + elif action == "direct_reply": + max_reply_attempts = 3 + reply_attempt_count = 0 + is_suitable = False + need_replan = False + check_reason = "未进行尝试" + final_reply_to_send = "" + + while reply_attempt_count < max_reply_attempts and not is_suitable: + reply_attempt_count += 1 + logger.info(f"尝试生成首次回复 (第 {reply_attempt_count}/{max_reply_attempts} 次)...") + self.state = ConversationState.GENERATING + + # 1. 生成回复 + self.generated_reply = await self.reply_generator.generate(observation_info, conversation_info, action_type='direct_reply') + logger.info(f"第 {reply_attempt_count} 次生成的首次回复: {self.generated_reply}") + + # 2. 检查回复 + self.state = ConversationState.CHECKING + 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, + chat_history_str=observation_info.chat_history_str, + retry_count=reply_attempt_count - 1, + ) + logger.info( + f"第 {reply_attempt_count} 次首次回复检查结果: 合适={is_suitable}, 原因='{check_reason}', 需重新规划={need_replan}" + ) + if is_suitable: + final_reply_to_send = self.generated_reply + break + elif need_replan: + logger.warning(f"第 {reply_attempt_count} 次首次回复检查建议重新规划,停止尝试。原因: {check_reason}") + break + except Exception as check_err: + logger.error(f"第 {reply_attempt_count} 次调用 ReplyChecker (首次回复) 时出错: {check_err}") + check_reason = f"第 {reply_attempt_count} 次检查过程出错: {check_err}" + break + + # 循环结束,处理最终结果 + if is_suitable: + # 检查是否有新消息 + if self._check_new_messages_after_planning(): + logger.info("生成首次回复期间收到新消息,取消发送,重新规划行动") + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"有新消息,取消发送首次回复: {final_reply_to_send}"} + ) + return # 直接返回,重新规划 + + # 发送合适的回复 + self.generated_reply = final_reply_to_send + # --- 在这里调用 _send_reply --- + await self._send_reply() # <--- 调用恢复后的函数 + + # 更新状态: 标记上次成功是 direct_reply + self.conversation_info.last_successful_reply_action = 'direct_reply' + action_successful = True # 标记动作成功 + + else: + # 首次回复失败 + logger.warning(f"经过 {reply_attempt_count} 次尝试,未能生成合适的首次回复。最终原因: {check_reason}") + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"首次回复尝试{reply_attempt_count}次后失败: {check_reason}"} + ) + # 重置状态: 首次回复失败,下次还是用初始 prompt + self.conversation_info.last_successful_reply_action = None + + # 执行 Wait 操作 (保持原有逻辑) + logger.info("由于无法生成合适首次回复,执行 'wait' 操作...") self.state = ConversationState.WAITING - # 直接调用 wait 方法 await self.waiter.wait(self.conversation_info) - # 可以选择添加一条新的 action 记录来表示这个 wait wait_action_record = { "action": "wait", "plan_reason": "因 direct_reply 多次尝试失败而执行的后备等待", - "status": "done", # wait 完成后可以认为是 done + "status": "done", "time": datetime.datetime.now().strftime("%H:%M:%S"), "final_reason": None, } conversation_info.done_action.append(wait_action_record) elif action == "fetch_knowledge": - self.waiter.wait_accumulated_time = 0 self.state = ConversationState.FETCHING - knowledge = "TODO:知识" - topic = "TODO:关键词" - logger.info(f"假装获取到知识{knowledge},关键词是: {topic}") - if knowledge: - pass # 简单处理 - # 标记 action 为 done - conversation_info.done_action[action_index].update( - { - "status": "done", - "time": datetime.datetime.now().strftime("%H:%M:%S"), - } - ) + knowledge_query = reason + try: + # 检查 knowledge_fetcher 是否存在 + if not hasattr(self, 'knowledge_fetcher'): + logger.error("KnowledgeFetcher 未初始化,无法获取知识。") + raise AttributeError("KnowledgeFetcher not initialized") + + knowledge, source = await self.knowledge_fetcher.fetch(knowledge_query, observation_info.chat_history) + logger.info(f"获取到知识: {knowledge[:100]}..., 来源: {source}") + if knowledge: + # 确保 knowledge_list 存在 + if not hasattr(conversation_info, 'knowledge_list'): + conversation_info.knowledge_list = [] + conversation_info.knowledge_list.append({"query": knowledge_query, "knowledge": knowledge, "source": source}) + action_successful = True + except Exception as fetch_err: + logger.error(f"获取知识时出错: {fetch_err}") + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"获取知识失败: {fetch_err}"} + ) + self.conversation_info.last_successful_reply_action = None # 重置状态 + 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"), - } - ) + try: + # 检查 goal_analyzer 是否存在 + if not hasattr(self, 'goal_analyzer'): + logger.error("GoalAnalyzer 未初始化,无法重新思考目标。") + raise AttributeError("GoalAnalyzer not initialized") + await self.goal_analyzer.analyze_goal(conversation_info, observation_info) + action_successful = True + except Exception as rethink_err: + logger.error(f"重新思考目标时出错: {rethink_err}") + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"重新思考目标失败: {rethink_err}"} + ) + self.conversation_info.last_successful_reply_action = None # 重置状态 + elif action == "listening": self.state = ConversationState.LISTENING logger.info("倾听对方发言...") - await self.waiter.wait_listening(conversation_info) - # listening 和 wait 通常在完成后不需要标记为 done,因为它们是持续状态, - # 但如果需要记录,可以在 waiter 返回后标记。目前逻辑是 waiter 返回后主循环继续。 - # 为了统一,可以暂时在这里也标记一下(或者都不标记) - conversation_info.done_action[action_index].update( - { - "status": "done", # 或 "completed" - "time": datetime.datetime.now().strftime("%H:%M:%S"), - } - ) + try: + # 检查 waiter 是否存在 + if not hasattr(self, 'waiter'): + logger.error("Waiter 未初始化,无法倾听。") + raise AttributeError("Waiter not initialized") + timeout_occurred = await self.waiter.wait_listening(conversation_info) + action_successful = True # Listening 完成就算成功 + except Exception as listen_err: + logger.error(f"倾听时出错: {listen_err}") + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"倾听失败: {listen_err}"} + ) + self.conversation_info.last_successful_reply_action = None # 重置状态 + elif action == "end_conversation": - self.should_continue = False # 设置循环停止标志 + self.should_continue = False logger.info("决定结束对话...") - # 标记 action 为 done + action_successful = True # 标记动作成功 + + elif action == "block_and_ignore": + logger.info("不想再理你了...") + ignore_duration_seconds = 10 * 60 + self.ignore_until_timestamp = time.time() + ignore_duration_seconds + logger.info(f"将忽略此对话直到: {datetime.datetime.fromtimestamp(self.ignore_until_timestamp)}") + self.state = ConversationState.IGNORED + action_successful = True # 标记动作成功 + + else: # 对应 'wait' 动作 + self.state = ConversationState.WAITING + logger.info("等待更多信息...") + try: + # 检查 waiter 是否存在 + if not hasattr(self, 'waiter'): + logger.error("Waiter 未初始化,无法等待。") + raise AttributeError("Waiter not initialized") + timeout_occurred = await self.waiter.wait(self.conversation_info) + action_successful = True # Wait 完成就算成功 + except Exception as wait_err: + logger.error(f"等待时出错: {wait_err}") + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"等待失败: {wait_err}"} + ) + self.conversation_info.last_successful_reply_action = None # 重置状态 + + # --- 更新 Action History 状态 --- + # 只有当动作本身成功时,才更新状态为 done + if action_successful: conversation_info.done_action[action_index].update( { "status": "done", "time": datetime.datetime.now().strftime("%H:%M:%S"), } ) - # 这里不需要 return,主循环会在下一轮检查 should_continue + # 重置状态: 对于非回复类动作的成功,清除上次回复状态 + if action not in ['direct_reply', 'send_new_message']: + self.conversation_info.last_successful_reply_action = None + logger.debug(f"动作 {action} 成功完成,重置 last_successful_reply_action") + # 如果动作是 recall 状态,在各自的处理逻辑中已经更新了 done_action - elif action == "block_and_ignore": - logger.info("不想再理你了...") - # 1. 标记对话为暂时忽略 - ignore_duration_seconds = 10 * 60 # 10 分钟 - self.ignore_until_timestamp = time.time() + ignore_duration_seconds - logger.info(f"将忽略此对话直到: {datetime.datetime.fromtimestamp(self.ignore_until_timestamp)}") - conversation_info.done_action[action_index].update( - { - "status": "done", # 或者一个自定义状态,比如 "ignored" - "final_reason": "Detected potential harassment, ignoring temporarily.", # 检测到潜在骚扰,暂时忽略 - "time": datetime.datetime.now().strftime("%H:%M:%S"), - } - ) - self.state = ConversationState.IGNORED + async def _send_reply(self): + """发送回复""" + if not self.generated_reply: + logger.warning("没有生成回复内容,无法发送。") + return - 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"), - } - ) + try: + _current_time = time.time() + reply_content = self.generated_reply + + # 发送消息 (确保 direct_sender 和 chat_stream 有效) + if not hasattr(self, 'direct_sender') or not self.direct_sender: + logger.error("DirectMessageSender 未初始化,无法发送回复。") + return + if not self.chat_stream: + logger.error("ChatStream 未初始化,无法发送回复。") + return + + await self.direct_sender.send_message(chat_stream=self.chat_stream, content=reply_content) + + # 发送成功后,手动触发 observer 更新可能导致重复处理自己发送的消息 + # 更好的做法是依赖 observer 的自动轮询或数据库触发器(如果支持) + # 暂时注释掉,观察是否影响 ObservationInfo 的更新 + # self.chat_observer.trigger_update() + # if not await self.chat_observer.wait_for_update(): + # logger.warning("等待 ChatObserver 更新完成超时") + + self.state = ConversationState.ANALYZING # 更新状态 + + except Exception as e: + logger.error(f"发送消息或更新状态时失败: {str(e)}") + logger.error(traceback.format_exc()) + self.state = ConversationState.ANALYZING async def _send_timeout_message(self): """发送超时结束消息""" @@ -463,29 +605,3 @@ class Conversation: ) except Exception as e: logger.error(f"发送超时消息失败: {str(e)}") - - async def _send_reply(self): - """发送回复""" - if not self.generated_reply: - logger.warning("没有生成回复") - return - - try: - # 外层 try: 捕获发送消息和后续处理中的主要错误 - _current_time = time.time() # 获取当前时间戳 - reply_content = self.generated_reply # 获取要发送的内容 - - # 发送消息 - await self.direct_sender.send_message(chat_stream=self.chat_stream, content=reply_content) - - # 原有的触发更新和等待代码 - self.chat_observer.trigger_update() - if not await self.chat_observer.wait_for_update(): - logger.warning("等待 ChatObserver 更新完成超时") - - self.state = ConversationState.ANALYZING # 更新对话状态 - - except Exception as e: - # 这是外层 try 对应的 except - logger.error(f"发送消息或更新状态时失败: {str(e)}") - self.state = ConversationState.ANALYZING # 出错也要尝试恢复状态 diff --git a/src/plugins/PFC/conversation_info.py b/src/plugins/PFC/conversation_info.py index cae9f0b34..c4350ed93 100644 --- a/src/plugins/PFC/conversation_info.py +++ b/src/plugins/PFC/conversation_info.py @@ -1,6 +1,8 @@ +from typing import Optional class ConversationInfo: def __init__(self): self.done_action = [] self.goal_list = [] self.knowledge_list = [] self.memory_list = [] + self.last_successful_reply_action: Optional[str] = None diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index 65afbf64d..ebde0a67a 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -11,6 +11,52 @@ from src.plugins.utils.chat_message_builder import build_readable_messages logger = get_module_logger("reply_generator") +# --- 定义 Prompt 模板 --- + +# Prompt for direct_reply (首次回复) +PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下信息生成一条回复: + +当前对话目标:{goals_str} +最近的聊天记录: +{chat_history_text} + + +请根据上述信息,结合聊天记录,回复对方。该回复应该: +1. 符合对话目标,以"你"的角度发言(不要自己与自己对话!) +2. 符合你的性格特征和身份细节 +3. 通俗易懂,自然流畅,像正常聊天一样,简短(通常20字以内,除非特殊情况) +4. 适当利用相关知识,但不要生硬引用 +5. 自然、得体,结合聊天记录逻辑合理,且没有重复表达同质内容 + +请注意把握聊天内容,不要回复的太有条理,可以有个性。请分清"你"和对方说的话,不要把"你"说的话当做对方说的话,这是你自己说的话。 +可以回复得自然随意自然一些,就像真人一样,注意把握聊天内容,整体风格可以平和、简短,不要刻意突出自身学科背景,不要说你说过的话,可以简短,多简短都可以,但是避免冗长。 +请你注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。 +不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 )。 + +请直接输出回复内容,不需要任何额外格式。""" + +# Prompt for send_new_message (追问/补充) +PROMPT_SEND_NEW_MESSAGE = """{persona_text}。现在你在参与一场QQ私聊,**刚刚你已经发送了一条或多条消息**,现在请根据以下信息再发一条新消息: + +当前对话目标:{goals_str} +最近的聊天记录: +{chat_history_text} + + +请根据上述信息,结合聊天记录,继续发一条新消息(例如对之前消息的补充,深入话题,或追问等等)。该消息应该: +1. 符合对话目标,以"你"的角度发言(不要自己与自己对话!) +2. 符合你的性格特征和身份细节 +3. 通俗易懂,自然流畅,像正常聊天一样,简短(通常20字以内,除非特殊情况) +4. 适当利用相关知识,但不要生硬引用 +5. 跟之前你发的消息自然的衔接,逻辑合理,且没有重复表达同质内容或部分重叠内容 + +请注意把握聊天内容,不用太有条理,可以有个性。请分清"你"和对方说的话,不要把"你"说的话当做对方说的话,这是你自己说的话。 +这条消息可以自然随意自然一些,就像真人一样,注意把握聊天内容,整体风格可以平和、简短,不要刻意突出自身学科背景,不要说你说过的话,可以简短,多简短都可以,但是避免冗长。 +请你注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出消息内容。 +不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 )。 + +请直接输出回复内容,不需要任何额外格式。""" + class ReplyGenerator: """回复生成器""" @@ -28,61 +74,60 @@ class ReplyGenerator: self.chat_observer = ChatObserver.get_instance(stream_id) self.reply_checker = ReplyChecker(stream_id) - async def generate(self, observation_info: ObservationInfo, conversation_info: ConversationInfo) -> str: + # 修改 generate 方法签名,增加 action_type 参数 + async def generate(self, observation_info: ObservationInfo, conversation_info: ConversationInfo, action_type: str) -> str: """生成回复 Args: - goal: 对话目标 - chat_history: 聊天历史 - knowledge_cache: 知识缓存 - previous_reply: 上一次生成的回复(如果有) - retry_count: 当前重试次数 + observation_info: 观察信息 + conversation_info: 对话信息 + action_type: 当前执行的动作类型 ('direct_reply' 或 'send_new_message') Returns: str: 生成的回复 """ # 构建提示词 - logger.debug(f"开始生成回复:当前目标: {conversation_info.goal_list}") + logger.debug(f"开始生成回复 (动作类型: {action_type}):当前目标: {conversation_info.goal_list}") - # 构建对话目标 + # --- 构建通用 Prompt 参数 --- + # (这部分逻辑基本不变) + + # 构建对话目标 (goals_str) goals_str = "" if conversation_info.goal_list: for goal_reason in conversation_info.goal_list: - # 处理字典或元组格式 if isinstance(goal_reason, tuple): - # 假设元组的第一个元素是目标,第二个元素是原因 - goal = goal_reason[0] + goal = goal_reason[0] if len(goal_reason) > 0 else "目标内容缺失" reasoning = goal_reason[1] if len(goal_reason) > 1 else "没有明确原因" elif isinstance(goal_reason, dict): - goal = goal_reason.get("goal") + goal = goal_reason.get("goal", "目标内容缺失") reasoning = goal_reason.get("reasoning", "没有明确原因") else: - # 如果是其他类型,尝试转为字符串 goal = str(goal_reason) reasoning = "没有明确原因" - - goal_str = f"目标:{goal},产生该对话目标的原因:{reasoning}\n" - goals_str += goal_str + 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" else: - goal = "目前没有明确对话目标" - reasoning = "目前没有明确对话目标,最好思考一个对话目标" - goals_str = f"目标:{goal},产生该对话目标的原因:{reasoning}\n" + goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况 - # 获取聊天历史记录 + # 获取聊天历史记录 (chat_history_text) chat_history_text = observation_info.chat_history_str + if observation_info.new_messages_count > 0 and observation_info.unprocessed_messages: + new_messages_list = observation_info.unprocessed_messages + new_messages_str = await build_readable_messages( + new_messages_list, + replace_bot_name=True, + merge_messages=False, + timestamp_mode="relative", + read_mark=0.0, + ) + chat_history_text += f"\n--- 以下是 {observation_info.new_messages_count} 条新消息 ---\n{new_messages_str}" + elif not chat_history_text: + chat_history_text = "还没有聊天记录。" - if observation_info.new_messages_count > 0: - new_messages_list = observation_info.unprocessed_messages - new_messages_str = await build_readable_messages( - new_messages_list, - replace_bot_name=True, - merge_messages=False, - timestamp_mode="relative", - read_mark=0.0, - ) - chat_history_text += f"\n--- 以下是 {observation_info.new_messages_count} 条新消息 ---\n{new_messages_str}" - # await observation_info.clear_unprocessed_messages() + # 构建 Persona 文本 (persona_text) identity_details_only = self.identity_detail_info identity_addon = "" if isinstance(identity_details_only, str): @@ -97,89 +142,39 @@ class ReplyGenerator: 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:] - if len(conversation_info.done_action) >= 10 - else conversation_info.done_action + + # --- 选择 Prompt --- + if action_type == 'send_new_message': + prompt_template = PROMPT_SEND_NEW_MESSAGE + logger.info("使用 PROMPT_SEND_NEW_MESSAGE (追问生成)") + else: # 默认使用 direct_reply 的 prompt + prompt_template = PROMPT_DIRECT_REPLY + logger.info("使用 PROMPT_DIRECT_REPLY (首次/非连续回复生成)") + + # --- 格式化最终的 Prompt --- + prompt = prompt_template.format( + persona_text=persona_text, + goals_str=goals_str, + chat_history_text=chat_history_text ) - action_history_text = "你之前做的事情是:" - for action in action_history_list: - if isinstance(action, dict): - action_type = action.get("action") - action_reason = action.get("reason") - action_status = action.get("status") - if action_status == "recall": - action_history_text += ( - f"原本打算:{action_type},但是因为有新消息,你发现这个行动不合适,所以你没做\n" - ) - elif action_status == "done": - action_history_text += f"你之前做了:{action_type},原因:{action_reason}\n" - elif isinstance(action, tuple): - # 假设元组的格式是(action_type, action_reason, action_status) - action_type = action[0] if len(action) > 0 else "未知行动" - action_reason = action[1] if len(action) > 1 else "未知原因" - action_status = action[2] if len(action) > 2 else "done" - if action_status == "recall": - action_history_text += ( - f"原本打算:{action_type},但是因为有新消息,你发现这个行动不合适,所以你没做\n" - ) - elif action_status == "done": - action_history_text += f"你之前做了:{action_type},原因:{action_reason}\n" - - prompt = f"""{persona_text}。现在你在参与一场QQ私聊,请根据以下信息生成一条新消息: - -当前对话目标:{goals_str} -最近的聊天记录: -{chat_history_text} - - -请根据上述信息,结合聊天记录,发一条消息(可以是回复,补充,深入话题,或追问等等)。该消息应该: -1. 符合对话目标,以"你"的角度发言(不要自己与自己对话!) -2. 符合你的性格特征和身份细节 -3. 通俗易懂,自然流畅,像正常聊天一样,简短(通常20字以内,除非特殊情况) -4. 适当利用相关知识,但不要生硬引用 -5. 自然、得体,结合聊天记录逻辑合理,且没有重复表达同质内容 - -请注意把握聊天内容,不要回复的太有条理,可以有个性。请分清"你"和对方说的话,不要把"你"说的话当做对方说的话,这是你自己说的话。 -可以回复得自然随意自然一些,就像真人一样,注意把握聊天内容,整体风格可以平和、简短,不要刻意突出自身学科背景,不要说你说过的话,可以简短,多简短都可以,但是避免冗长。 -请你注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出回复内容。 -不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 )。 -**注意:如果聊天记录中最新的消息是你自己发送的,那么你的思路不应该是“回复”,而是应该紧紧衔接你发送的消息,进行话题的深入,补充,或追问等等,避免与最新消息内容重叠;** - -请直接输出回复内容,不需要任何额外格式。""" + # --- 调用 LLM 生成 --- + logger.debug(f"发送到LLM的生成提示词:\n------\n{prompt}\n------") try: content, _ = await self.llm.generate_response_async(prompt) logger.info(f"生成的回复: {content}") - # is_new = self.chat_observer.check() - # logger.debug(f"再看一眼聊天记录,{'有' if is_new else '没有'}新消息") - - # 如果有新消息,重新生成回复 - # if is_new: - # logger.info("检测到新消息,重新生成回复") - # return await self.generate( - # goal, chat_history, knowledge_cache, - # None, retry_count - # ) - + # 移除旧的检查新消息逻辑,这应该由 conversation 控制流处理 return content except Exception as e: logger.error(f"生成回复时出错: {e}") return "抱歉,我现在有点混乱,让我重新思考一下..." + # check_reply 方法保持不变 async def check_reply( self, reply: str, goal: str, chat_history: List[Dict[str, Any]], chat_history_str: str, retry_count: int = 0 ) -> Tuple[bool, str, bool]: """检查回复是否合适 - - Args: - reply: 生成的回复 - goal: 对话目标 - retry_count: 当前重试次数 - - Returns: - Tuple[bool, str, bool]: (是否合适, 原因, 是否需要重新规划) + (此方法逻辑保持不变) """ - return await self.reply_checker.check(reply, goal, chat_history, chat_history_str, retry_count) + return await self.reply_checker.check(reply, goal, chat_history, chat_history_str, retry_count) \ No newline at end of file From 02b7ea79db09dd69469c525ad7610262c611ef7c Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 17:48:25 +0800 Subject: [PATCH 02/19] =?UTF-8?q?=E5=85=88=E4=BF=AE=E4=B8=AA=E7=A9=BA?= =?UTF-8?q?=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 2 +- src/plugins/PFC/conversation.py | 114 ++++++++++++++--------------- src/plugins/PFC/reply_generator.py | 18 ++--- 3 files changed, 67 insertions(+), 67 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 701770181..ff13b5fad 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -279,7 +279,7 @@ 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: action_type = action_data[0] if len(action_data) > 1: plan_reason = action_data[1] # 可能是规划原因或最终原因 if len(action_data) > 2: status = action_data[2] diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 4a309b1d9..04c2cca39 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -135,13 +135,13 @@ class Conversation: while self.should_continue: # 忽略逻辑 if self.ignore_until_timestamp and time.time() < self.ignore_until_timestamp: - await asyncio.sleep(30) - continue + await asyncio.sleep(30) + continue elif self.ignore_until_timestamp and time.time() >= self.ignore_until_timestamp: - logger.info(f"忽略时间已到 {self.stream_id},准备结束对话。") - self.ignore_until_timestamp = None - self.should_continue = False - continue + logger.info(f"忽略时间已到 {self.stream_id},准备结束对话。") + self.ignore_until_timestamp = None + self.should_continue = False + continue try: # --- 在规划前记录当前新消息数量 --- initial_new_message_count = 0 @@ -239,13 +239,13 @@ class Conversation: # 尝试从 msg_dict 直接获取 chat_stream,如果失败则从全局 chat_manager 获取 chat_info = msg_dict.get("chat_info") if chat_info and isinstance(chat_info, dict): - chat_stream = ChatStream.from_dict(chat_info) + chat_stream = ChatStream.from_dict(chat_info) elif self.chat_stream: # 使用实例变量中的 chat_stream - chat_stream = self.chat_stream + chat_stream = self.chat_stream else: # Fallback: 尝试从 manager 获取 (可能需要 stream_id) - chat_stream = chat_manager.get_stream(self.stream_id) - if not chat_stream: - raise ValueError(f"无法确定 ChatStream for stream_id {self.stream_id}") + chat_stream = chat_manager.get_stream(self.stream_id) + if not chat_stream: + raise ValueError(f"无法确定 ChatStream for stream_id {self.stream_id}") user_info = UserInfo.from_dict(msg_dict.get("user_info", {})) @@ -416,7 +416,7 @@ class Conversation: # 循环结束,处理最终结果 if is_suitable: - # 检查是否有新消息 + # 检查是否有新消息 if self._check_new_messages_after_planning(): logger.info("生成首次回复期间收到新消息,取消发送,重新规划行动") conversation_info.done_action[action_index].update( @@ -461,58 +461,58 @@ class Conversation: try: # 检查 knowledge_fetcher 是否存在 if not hasattr(self, 'knowledge_fetcher'): - logger.error("KnowledgeFetcher 未初始化,无法获取知识。") - raise AttributeError("KnowledgeFetcher not initialized") + logger.error("KnowledgeFetcher 未初始化,无法获取知识。") + raise AttributeError("KnowledgeFetcher not initialized") knowledge, source = await self.knowledge_fetcher.fetch(knowledge_query, observation_info.chat_history) logger.info(f"获取到知识: {knowledge[:100]}..., 来源: {source}") if knowledge: - # 确保 knowledge_list 存在 - if not hasattr(conversation_info, 'knowledge_list'): - conversation_info.knowledge_list = [] - conversation_info.knowledge_list.append({"query": knowledge_query, "knowledge": knowledge, "source": source}) + # 确保 knowledge_list 存在 + if not hasattr(conversation_info, 'knowledge_list'): + conversation_info.knowledge_list = [] + conversation_info.knowledge_list.append({"query": knowledge_query, "knowledge": knowledge, "source": source}) action_successful = True except Exception as fetch_err: - logger.error(f"获取知识时出错: {fetch_err}") - conversation_info.done_action[action_index].update( - {"status": "recall", "final_reason": f"获取知识失败: {fetch_err}"} - ) - self.conversation_info.last_successful_reply_action = None # 重置状态 + logger.error(f"获取知识时出错: {fetch_err}") + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"获取知识失败: {fetch_err}"} + ) + self.conversation_info.last_successful_reply_action = None # 重置状态 elif action == "rethink_goal": self.state = ConversationState.RETHINKING try: - # 检查 goal_analyzer 是否存在 + # 检查 goal_analyzer 是否存在 if not hasattr(self, 'goal_analyzer'): - logger.error("GoalAnalyzer 未初始化,无法重新思考目标。") - raise AttributeError("GoalAnalyzer not initialized") + logger.error("GoalAnalyzer 未初始化,无法重新思考目标。") + raise AttributeError("GoalAnalyzer not initialized") await self.goal_analyzer.analyze_goal(conversation_info, observation_info) action_successful = True except Exception as rethink_err: - logger.error(f"重新思考目标时出错: {rethink_err}") - conversation_info.done_action[action_index].update( - {"status": "recall", "final_reason": f"重新思考目标失败: {rethink_err}"} - ) - self.conversation_info.last_successful_reply_action = None # 重置状态 + logger.error(f"重新思考目标时出错: {rethink_err}") + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"重新思考目标失败: {rethink_err}"} + ) + self.conversation_info.last_successful_reply_action = None # 重置状态 elif action == "listening": self.state = ConversationState.LISTENING logger.info("倾听对方发言...") try: - # 检查 waiter 是否存在 + # 检查 waiter 是否存在 if not hasattr(self, 'waiter'): - logger.error("Waiter 未初始化,无法倾听。") - raise AttributeError("Waiter not initialized") + logger.error("Waiter 未初始化,无法倾听。") + raise AttributeError("Waiter not initialized") timeout_occurred = await self.waiter.wait_listening(conversation_info) action_successful = True # Listening 完成就算成功 except Exception as listen_err: - logger.error(f"倾听时出错: {listen_err}") - conversation_info.done_action[action_index].update( - {"status": "recall", "final_reason": f"倾听失败: {listen_err}"} - ) - self.conversation_info.last_successful_reply_action = None # 重置状态 + logger.error(f"倾听时出错: {listen_err}") + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"倾听失败: {listen_err}"} + ) + self.conversation_info.last_successful_reply_action = None # 重置状态 elif action == "end_conversation": @@ -521,29 +521,29 @@ class Conversation: action_successful = True # 标记动作成功 elif action == "block_and_ignore": - logger.info("不想再理你了...") - ignore_duration_seconds = 10 * 60 - self.ignore_until_timestamp = time.time() + ignore_duration_seconds - logger.info(f"将忽略此对话直到: {datetime.datetime.fromtimestamp(self.ignore_until_timestamp)}") - self.state = ConversationState.IGNORED - action_successful = True # 标记动作成功 + logger.info("不想再理你了...") + ignore_duration_seconds = 10 * 60 + self.ignore_until_timestamp = time.time() + ignore_duration_seconds + logger.info(f"将忽略此对话直到: {datetime.datetime.fromtimestamp(self.ignore_until_timestamp)}") + self.state = ConversationState.IGNORED + action_successful = True # 标记动作成功 else: # 对应 'wait' 动作 self.state = ConversationState.WAITING logger.info("等待更多信息...") try: - # 检查 waiter 是否存在 + # 检查 waiter 是否存在 if not hasattr(self, 'waiter'): - logger.error("Waiter 未初始化,无法等待。") - raise AttributeError("Waiter not initialized") + logger.error("Waiter 未初始化,无法等待。") + raise AttributeError("Waiter not initialized") timeout_occurred = await self.waiter.wait(self.conversation_info) action_successful = True # Wait 完成就算成功 except Exception as wait_err: - logger.error(f"等待时出错: {wait_err}") - conversation_info.done_action[action_index].update( - {"status": "recall", "final_reason": f"等待失败: {wait_err}"} - ) - self.conversation_info.last_successful_reply_action = None # 重置状态 + logger.error(f"等待时出错: {wait_err}") + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"等待失败: {wait_err}"} + ) + self.conversation_info.last_successful_reply_action = None # 重置状态 # --- 更新 Action History 状态 --- # 只有当动作本身成功时,才更新状态为 done @@ -572,11 +572,11 @@ class Conversation: # 发送消息 (确保 direct_sender 和 chat_stream 有效) if not hasattr(self, 'direct_sender') or not self.direct_sender: - logger.error("DirectMessageSender 未初始化,无法发送回复。") - return + logger.error("DirectMessageSender 未初始化,无法发送回复。") + return if not self.chat_stream: - logger.error("ChatStream 未初始化,无法发送回复。") - return + logger.error("ChatStream 未初始化,无法发送回复。") + return await self.direct_sender.send_message(chat_stream=self.chat_stream, content=reply_content) diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index ebde0a67a..0035f845e 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -114,15 +114,15 @@ class ReplyGenerator: # 获取聊天历史记录 (chat_history_text) chat_history_text = observation_info.chat_history_str if observation_info.new_messages_count > 0 and observation_info.unprocessed_messages: - new_messages_list = observation_info.unprocessed_messages - new_messages_str = await build_readable_messages( - new_messages_list, - replace_bot_name=True, - merge_messages=False, - timestamp_mode="relative", - read_mark=0.0, - ) - chat_history_text += f"\n--- 以下是 {observation_info.new_messages_count} 条新消息 ---\n{new_messages_str}" + new_messages_list = observation_info.unprocessed_messages + new_messages_str = await build_readable_messages( + new_messages_list, + replace_bot_name=True, + merge_messages=False, + timestamp_mode="relative", + read_mark=0.0, + ) + chat_history_text += f"\n--- 以下是 {observation_info.new_messages_count} 条新消息 ---\n{new_messages_str}" elif not chat_history_text: chat_history_text = "还没有聊天记录。" From 2ee60ae9898e5ff058587d4c41773fe7b0c4bf5d Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 17:55:23 +0800 Subject: [PATCH 03/19] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A5=9E=E5=A5=87?= =?UTF-8?q?=E7=9A=840=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/conversation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 04c2cca39..1107527ae 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -308,7 +308,7 @@ class Conversation: # 2. 检查回复 (逻辑不变) self.state = ConversationState.CHECKING try: - current_goal_str = conversation_info.goal_list[0][0] if conversation_info.goal_list else "" + current_goal_str = conversation_info.goal_list[0]["goal"] 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, From 1a3e27b7808d74b6abbf2c8860b09409cbe01da4 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 18:01:52 +0800 Subject: [PATCH 04/19] =?UTF-8?q?=E8=AE=A9=E9=BA=A6=E9=BA=A6=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=E8=BF=9B=E5=85=A5=E8=BF=BD=E9=97=AE=E5=86=B3=E7=AD=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/conversation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 1107527ae..5e7dae4b3 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -146,7 +146,7 @@ class Conversation: # --- 在规划前记录当前新消息数量 --- initial_new_message_count = 0 if hasattr(self.observation_info, "new_messages_count"): - initial_new_message_count = self.observation_info.new_messages_count + initial_new_message_count = self.observation_info.new_messages_count + 1 # 算上麦麦自己发的那一条 else: logger.warning("ObservationInfo missing 'new_messages_count' before planning.") From 0b51aeb43eaa26003fb3de6e18508d83fee2c51c Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 18:11:01 +0800 Subject: [PATCH 05/19] =?UTF-8?q?=E5=85=88=E5=9C=A8=E8=BF=99=E9=87=8C?= =?UTF-8?q?=E5=8A=A0=E4=B8=AA=E8=B0=83=E8=AF=95=E8=AF=AD=E5=8F=A5=EF=BC=8C?= =?UTF-8?q?=E5=8E=BB=E4=BF=AE=E5=8F=A6=E5=A4=96=E4=B8=80=E4=B8=AAbug?= =?UTF-8?q?=E5=85=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/conversation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 5e7dae4b3..ccec0d8c9 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -308,7 +308,9 @@ class Conversation: # 2. 检查回复 (逻辑不变) self.state = ConversationState.CHECKING try: + print(conversation_info.goal_list) current_goal_str = conversation_info.goal_list[0]["goal"] if conversation_info.goal_list else "" + print("跳过小虫虫\n") is_suitable, check_reason, need_replan = await self.reply_generator.check_reply( reply=self.generated_reply, goal=current_goal_str, From 71f35d8f73467232da2a0a24c587032d4171f021 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 18:28:13 +0800 Subject: [PATCH 06/19] =?UTF-8?q?fix=20ReplyChecker=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/conversation.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index ccec0d8c9..b1fe0731c 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -308,9 +308,7 @@ class Conversation: # 2. 检查回复 (逻辑不变) self.state = ConversationState.CHECKING try: - print(conversation_info.goal_list) current_goal_str = conversation_info.goal_list[0]["goal"] if conversation_info.goal_list else "" - print("跳过小虫虫\n") is_suitable, check_reason, need_replan = await self.reply_generator.check_reply( reply=self.generated_reply, goal=current_goal_str, @@ -394,7 +392,7 @@ class Conversation: # 2. 检查回复 self.state = ConversationState.CHECKING try: - current_goal_str = conversation_info.goal_list[0][0] if conversation_info.goal_list else "" + current_goal_str = conversation_info.goal_list[0]["goal"] 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, From 60b3187227241000b3daf810d311fa4468dbc592 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 18:40:17 +0800 Subject: [PATCH 07/19] =?UTF-8?q?=E6=8A=8Alogger=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E8=BE=93=E5=87=BA=E5=A1=9E=E8=BF=9Bdebug?= =?UTF-8?q?=EF=BC=8C=E5=88=A0=E6=8E=89=E8=B0=83=E8=AF=95=E6=89=93=E5=8D=B0?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/chat_observer.py | 2 -- src/plugins/PFC/chat_states.py | 6 ------ src/plugins/PFC/conversation.py | 2 +- src/plugins/PFC/observation_info.py | 2 -- src/plugins/PFC/reply_generator.py | 2 +- 5 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/plugins/PFC/chat_observer.py b/src/plugins/PFC/chat_observer.py index 697833c84..0305289fa 100644 --- a/src/plugins/PFC/chat_observer.py +++ b/src/plugins/PFC/chat_observer.py @@ -94,11 +94,9 @@ class ChatObserver: """ try: # 发送新消息通知 - # logger.info(f"发送新ccchandleer消息通知: {message}") notification = create_new_message_notification( sender="chat_observer", target="observation_info", message=message ) - # logger.info(f"发送新消ddddd息通知: {notification}") # print(self.notification_manager) await self.notification_manager.send_notification(notification) except Exception as e: diff --git a/src/plugins/PFC/chat_states.py b/src/plugins/PFC/chat_states.py index 1f8ee15fb..4b839b7bd 100644 --- a/src/plugins/PFC/chat_states.py +++ b/src/plugins/PFC/chat_states.py @@ -98,15 +98,11 @@ class NotificationManager: notification_type: 要处理的通知类型 handler: 处理器实例 """ - # print(1145145511114445551111444) if target not in self._handlers: - # print("没11有target") self._handlers[target] = {} if notification_type not in self._handlers[target]: - # print("没11有notification_type") self._handlers[target][notification_type] = [] # print(self._handlers[target][notification_type]) - # print(f"注册1111111111111111111111处理器: {target} {notification_type} {handler}") self._handlers[target][notification_type].append(handler) # print(self._handlers[target][notification_type]) @@ -132,7 +128,6 @@ class NotificationManager: async def send_notification(self, notification: Notification): """发送通知""" self._notification_history.append(notification) - # print("kaishichul-----------------------------------i") # 如果是状态通知,更新活跃状态 if isinstance(notification, StateNotification): @@ -145,7 +140,6 @@ class NotificationManager: target = notification.target if target in self._handlers: handlers = self._handlers[target].get(notification.type, []) - # print(1111111) # print(handlers) for handler in handlers: # print(f"调用处理器: {handler}") diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index b1fe0731c..394fc1d74 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -267,7 +267,7 @@ class Conversation: ): """处理规划的行动""" - logger.info(f"执行行动: {action}, 原因: {reason}") + logger.debug(f"执行行动: {action}, 原因: {reason}") # 记录action历史 (逻辑不变) current_action_record = { diff --git a/src/plugins/PFC/observation_info.py b/src/plugins/PFC/observation_info.py index 072b1fb6f..050d839fb 100644 --- a/src/plugins/PFC/observation_info.py +++ b/src/plugins/PFC/observation_info.py @@ -140,7 +140,6 @@ class ObservationInfo: self.chat_observer.notification_manager.register_handler( target="observation_info", notification_type=NotificationType.COLD_CHAT, handler=self.handler ) - print("1919810------------------------绑定-----------------------------") def unbind_from_chat_observer(self): """解除与chat_observer的绑定""" @@ -159,7 +158,6 @@ class ObservationInfo: Args: message: 消息数据 """ - # print("1919810-----------------------------------------------------") # logger.debug(f"更新信息from_message: {message}") self.last_message_time = message["time"] self.last_message_id = message["message_id"] diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index 0035f845e..59e261ac5 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -162,7 +162,7 @@ class ReplyGenerator: logger.debug(f"发送到LLM的生成提示词:\n------\n{prompt}\n------") try: content, _ = await self.llm.generate_response_async(prompt) - logger.info(f"生成的回复: {content}") + logger.debug(f"生成的回复: {content}") # 移除旧的检查新消息逻辑,这应该由 conversation 控制流处理 return content From a7eebc8988a51ecba9ec5956a306fcce4a8cf8a9 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 18:59:21 +0800 Subject: [PATCH 08/19] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=8E=89=E9=A2=91?= =?UTF-8?q?=E7=B9=81=E6=97=A5=E5=BF=97=EF=BC=8C=E8=AE=A9=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=9B=B4=E5=8A=A0=E5=A5=BD=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/waiter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/PFC/waiter.py b/src/plugins/PFC/waiter.py index eaf8a768a..15405bc54 100644 --- a/src/plugins/PFC/waiter.py +++ b/src/plugins/PFC/waiter.py @@ -46,7 +46,7 @@ class Waiter: return True # 返回 True 表示超时 await asyncio.sleep(5) # 每 5 秒检查一次 - logger.info("等待中...") # 可以考虑把这个频繁日志注释掉,只在超时或收到消息时输出 + # logger.info("等待中...") # 可以考虑把这个频繁日志注释掉,只在超时或收到消息时输出 async def wait_listening(self, conversation_info: ConversationInfo) -> bool: """倾听用户发言或超时""" From e4b79aba4c9f700f18dcaa0792db3af054755131 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 19:10:54 +0800 Subject: [PATCH 09/19] =?UTF-8?q?=E6=8A=8A=E5=88=B7=E5=B1=8F=E7=9A=84?= =?UTF-8?q?=E4=B8=8D=E6=B3=A8=E9=87=8A=E6=8E=89=EF=BC=8C=E5=A1=9E=E8=BF=9B?= =?UTF-8?q?debug=E9=87=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 4 ++-- src/plugins/PFC/waiter.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index ff13b5fad..7cdb48e9c 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -311,10 +311,10 @@ class ActionPlanner: # --- 选择 Prompt --- if last_successful_reply_action in ['direct_reply', 'send_new_message']: prompt_template = PROMPT_FOLLOW_UP - logger.info("使用 PROMPT_FOLLOW_UP (追问决策)") + logger.debug(f"使用 PROMPT_FOLLOW_UP (追问决策)") else: prompt_template = PROMPT_INITIAL_REPLY - logger.info("使用 PROMPT_INITIAL_REPLY (首次/非连续回复决策)") + logger.debug("使用 PROMPT_INITIAL_REPLY (首次/非连续回复决策)") # --- 格式化最终的 Prompt --- prompt = prompt_template.format( diff --git a/src/plugins/PFC/waiter.py b/src/plugins/PFC/waiter.py index 15405bc54..2362e74ed 100644 --- a/src/plugins/PFC/waiter.py +++ b/src/plugins/PFC/waiter.py @@ -46,7 +46,7 @@ class Waiter: return True # 返回 True 表示超时 await asyncio.sleep(5) # 每 5 秒检查一次 - # logger.info("等待中...") # 可以考虑把这个频繁日志注释掉,只在超时或收到消息时输出 + logger.debug("等待中...") # 可以考虑把这个频繁日志注释掉,只在超时或收到消息时输出 async def wait_listening(self, conversation_info: ConversationInfo) -> bool: """倾听用户发言或超时""" @@ -73,4 +73,4 @@ class Waiter: return True # 返回 True 表示超时 await asyncio.sleep(5) # 每 5 秒检查一次 - logger.info("倾听等待中...") # 同上,可以考虑注释掉 + logger.debug("倾听等待中...") # 同上,可以考虑注释掉 From dba967c953582a681ade0d92d4e7062714b5a856 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 19:31:01 +0800 Subject: [PATCH 10/19] =?UTF-8?q?=E6=8A=8A=E6=89=80=E6=9C=89=E7=9A=84?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=E8=AE=B0=E5=BD=95=E9=83=BD=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/chat_observer.py | 4 ++-- src/plugins/PFC/pfc.py | 16 +++++++--------- src/plugins/PFC/pfc_KnowledgeFetcher.py | 12 ++++++++---- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/plugins/PFC/chat_observer.py b/src/plugins/PFC/chat_observer.py index 0305289fa..915618474 100644 --- a/src/plugins/PFC/chat_observer.py +++ b/src/plugins/PFC/chat_observer.py @@ -287,7 +287,7 @@ class ChatObserver: self._running = True self._task = asyncio.create_task(self._update_loop()) - logger.info(f"ChatObserver for {self.stream_id} started") + logger.debug(f"ChatObserver for {self.stream_id} started") def stop(self): """停止观察器""" @@ -296,7 +296,7 @@ class ChatObserver: self._update_complete.set() # 设置完成事件以解除等待 if self._task: self._task.cancel() - logger.info(f"ChatObserver for {self.stream_id} stopped") + logger.debug(f"ChatObserver for {self.stream_id} stopped") async def process_chat_history(self, messages: list): """处理聊天历史 diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index 5a70d02f3..a3acee47b 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -250,15 +250,13 @@ class GoalAnalyzer: async def analyze_conversation(self, goal, reasoning): messages = self.chat_observer.get_cached_messages() - chat_history_text = "" - for msg in messages: - time_str = datetime.datetime.fromtimestamp(msg["time"]).strftime("%H:%M:%S") - user_info = UserInfo.from_dict(msg.get("user_info", {})) - sender = user_info.user_nickname or f"用户{user_info.user_id}" - if sender == self.name: - sender = "你说" - chat_history_text += f"{time_str},{sender}:{msg.get('processed_plain_text', '')}\n" - + chat_history_text = await build_readable_messages( + messages, + replace_bot_name=True, + merge_messages=False, + timestamp_mode="relative", + read_mark=0.0, + ) identity_details_only = self.identity_detail_info identity_addon = "" if isinstance(identity_details_only, str): diff --git a/src/plugins/PFC/pfc_KnowledgeFetcher.py b/src/plugins/PFC/pfc_KnowledgeFetcher.py index 95e66c8cd..958b05bf8 100644 --- a/src/plugins/PFC/pfc_KnowledgeFetcher.py +++ b/src/plugins/PFC/pfc_KnowledgeFetcher.py @@ -5,6 +5,7 @@ from ..models.utils_model import LLMRequest from ...config.config import global_config from ..chat.message import Message from ..knowledge.knowledge_lib import qa_manager +from ..utils.chat_message_builder import build_readable_messages logger = get_module_logger("knowledge_fetcher") @@ -50,10 +51,13 @@ class KnowledgeFetcher: Tuple[str, str]: (获取的知识, 知识来源) """ # 构建查询上下文 - chat_history_text = "" - for msg in chat_history: - # sender = msg.message_info.user_info.user_nickname or f"用户{msg.message_info.user_info.user_id}" - chat_history_text += f"{msg.detailed_plain_text}\n" + chat_history_text = await build_readable_messages( + chat_history, + replace_bot_name=True, + merge_messages=False, + timestamp_mode="relative", + read_mark=0.0, + ) # 从记忆中获取相关知识 related_memory = await HippocampusManager.get_instance().get_memory_from_text( From 73a7e785d16f35ee5cebc2dba0a60bf8f960d29b Mon Sep 17 00:00:00 2001 From: 114514 <2514624910@qq.com> Date: Sun, 27 Apr 2025 21:21:57 +0800 Subject: [PATCH 11/19] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=B6=85=E6=97=B6promp?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 7cdb48e9c..f26e59ad0 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -21,7 +21,7 @@ logger = get_module_logger("action_planner", config=pfc_action_log_config) # --- 定义 Prompt 模板 --- # Prompt(1): 首次回复或非连续回复时的决策 Prompt -PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以发言,可以等待,可以倾听,可以调取知识,甚至可以屏蔽对方: +PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以回复,可以等待,可以倾听,可以调取知识,甚至可以屏蔽对方: 【当前对话目标】 {goals_str} @@ -38,7 +38,7 @@ PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊, ------ 可选行动类型以及解释: fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择 -wait: 暂时不说话,等待对方回复(尤其是在你刚发言后、或上次发言因重复、发言过多被拒时、或不确定做什么时,这是较安全的选择) +wait: 暂时不说话,等待对方回复 listening: 倾听对方发言,当你认为对方话才说到一半,发言明显未结束时选择 direct_reply: 直接回复对方 rethink_goal: 重新思考对话目标,当发现对话目标不再适用或对话卡住时选择,注意私聊的环境是灵活的,有可能需要经常选择 @@ -71,7 +71,7 @@ PROMPT_FOLLOW_UP = """{persona_text}。现在你在参与一场QQ私聊,刚刚 ------ 可选行动类型以及解释: fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择 -wait: 暂时不说话,等待对方回复(尤其是在你刚发言后、或上次发言因重复、发言过多被拒时、或不确定做什么时,这是不错的选择) +wait: 暂时不说话,留给对方交互空间,等待对方回复(尤其是在你刚发言后、或上次发言因重复、发言过多被拒时、或不确定做什么时,这是不错的选择) listening: 倾听对方发言(虽然你刚发过言,但如果对方立刻回复且明显话没说完,可以选择这个) send_new_message: 发送一条新消息继续对话,允许适当的追问、补充、深入话题,或开启相关新话题。**但是避免在因重复被拒后立即使用,也不要在对方没有回复的情况下过多的“消息轰炸”或重复发言** rethink_goal: 重新思考对话目标,当发现对话目标不再适用或对话卡住时选择,注意私聊的环境是灵活的,有可能需要经常选择 @@ -155,9 +155,9 @@ class ActionPlanner: if isinstance(last_goal_text, str) and "分钟,思考接下来要做什么" in last_goal_text: try: timeout_minutes_text = last_goal_text.split(",")[0].replace("你等待了", "") - timeout_context = f"重要提示:你刚刚因为对方长时间({timeout_minutes_text})没有回复而结束了等待,这可能代表在对方看来本次聊天已结束,请基于此情况规划下一步,不要重复等待前的发言。\n" + timeout_context = f"重要提示:对方已经长时间({timeout_minutes_text})没有回复你的消息了(这可能代表对方繁忙/不想回复/没注意到你的消息等情况,或在对方看来本次聊天已告一段落),请基于此情况规划下一步。\n" except Exception: - timeout_context = "重要提示:你刚刚因为对方长时间没有回复而结束了等待,这可能代表在对方看来本次聊天已结束,请基于此情况规划下一步,不要重复等待前的发言。\n" + timeout_context = "重要提示:对方已经长时间没有回复你的消息了(这可能代表对方繁忙/不想回复/没注意到你的消息等情况,或在对方看来本次聊天已告一段落),请基于此情况规划下一步。\n" else: logger.debug("Conversation info goal_list is empty or not available for timeout check.") except AttributeError: From 2b0bfa8d5073185dcdebf636e3e935a90e0e2daa Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 21:33:23 +0800 Subject: [PATCH 12/19] =?UTF-8?q?=E5=85=81=E8=AE=B8=E4=B8=A4=E6=9D=A1?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BF=A1=E6=81=AF=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=E7=B4=A7=E5=87=91=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/conversation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 394fc1d74..86503e5c5 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -165,7 +165,7 @@ class Conversation: else: logger.warning("ObservationInfo missing 'new_messages_count' after planning.") - if current_new_message_count > initial_new_message_count: + if current_new_message_count > initial_new_message_count + 2: logger.info( f"规划期间发现新增消息 ({initial_new_message_count} -> {current_new_message_count}),跳过本次行动,重新规划" ) From 788fd68464a3dbd9685fb797071cf3d30a0e3093 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 21:50:53 +0800 Subject: [PATCH 13/19] =?UTF-8?q?=E5=85=81=E8=AE=B8=E4=B8=A4=E6=9D=A1?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B6=88=E6=81=AF=EF=BC=8C=E4=BD=BF=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E6=9B=B4=E7=B4=A7=E5=87=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/conversation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 86503e5c5..ae94ceab0 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -223,7 +223,7 @@ class Conversation: logger.warning("ObservationInfo 未初始化或缺少 'new_messages_count' 属性,无法检查新消息。") return False # 或者根据需要抛出错误 - if self.observation_info.new_messages_count > 0: + if self.observation_info.new_messages_count > 2: logger.info(f"生成/执行动作期间收到 {self.observation_info.new_messages_count} 条新消息,取消当前动作并重新规划") # 如果有新消息,也应该重置上次回复状态 if hasattr(self, 'conversation_info'): # 确保 conversation_info 已初始化 From 9dea2bee557b1e878bff36ab9d22779465c55c2d Mon Sep 17 00:00:00 2001 From: 114514 <2514624910@qq.com> Date: Sun, 27 Apr 2025 21:53:31 +0800 Subject: [PATCH 14/19] =?UTF-8?q?=E4=BF=AE=E6=94=B9lis=E7=9A=84prompt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 1 - src/plugins/PFC/waiter.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index f26e59ad0..5994e915c 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -38,7 +38,6 @@ PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊, ------ 可选行动类型以及解释: fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择 -wait: 暂时不说话,等待对方回复 listening: 倾听对方发言,当你认为对方话才说到一半,发言明显未结束时选择 direct_reply: 直接回复对方 rethink_goal: 重新思考对话目标,当发现对话目标不再适用或对话卡住时选择,注意私聊的环境是灵活的,有可能需要经常选择 diff --git a/src/plugins/PFC/waiter.py b/src/plugins/PFC/waiter.py index 2362e74ed..b58d3367c 100644 --- a/src/plugins/PFC/waiter.py +++ b/src/plugins/PFC/waiter.py @@ -65,7 +65,7 @@ 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) From 16265838a28d16f3ba1c00483c0e9e09336f0f4b Mon Sep 17 00:00:00 2001 From: 114514 <2514624910@qq.com> Date: Sun, 27 Apr 2025 21:55:04 +0800 Subject: [PATCH 15/19] Merge branch 'FPC-test' of https://github.com/smartmita/MaiBot into FPC-test --- src/plugins/PFC/conversation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 86503e5c5..ae94ceab0 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -223,7 +223,7 @@ class Conversation: logger.warning("ObservationInfo 未初始化或缺少 'new_messages_count' 属性,无法检查新消息。") return False # 或者根据需要抛出错误 - if self.observation_info.new_messages_count > 0: + if self.observation_info.new_messages_count > 2: logger.info(f"生成/执行动作期间收到 {self.observation_info.new_messages_count} 条新消息,取消当前动作并重新规划") # 如果有新消息,也应该重置上次回复状态 if hasattr(self, 'conversation_info'): # 确保 conversation_info 已初始化 From e69fbe62c82d7ca61ea5b1e0e36474f4c1fe7e6e Mon Sep 17 00:00:00 2001 From: 114514 <2514624910@qq.com> Date: Sun, 27 Apr 2025 21:58:25 +0800 Subject: [PATCH 16/19] 132 --- src/plugins/PFC/action_planner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 5994e915c..21cbc9c12 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -21,7 +21,7 @@ logger = get_module_logger("action_planner", config=pfc_action_log_config) # --- 定义 Prompt 模板 --- # Prompt(1): 首次回复或非连续回复时的决策 Prompt -PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以回复,可以等待,可以倾听,可以调取知识,甚至可以屏蔽对方: +PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以回复,可以倾听,可以调取知识,甚至可以屏蔽对方: 【当前对话目标】 {goals_str} From 3e004bdd288f2accaa96f8c23e2e630f4079c4e8 Mon Sep 17 00:00:00 2001 From: 114514 <2514624910@qq.com> Date: Sun, 27 Apr 2025 22:40:33 +0800 Subject: [PATCH 17/19] 1 --- src/plugins/PFC/pfc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index a3acee47b..bd8602478 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -1,6 +1,6 @@ # Programmable Friendly Conversationalist # Prefrontal cortex -import datetime +# import datetime # import asyncio from typing import List, Optional, Tuple, TYPE_CHECKING From 99f568fdb51b31614f95bc40f432315a2fcc1f5d Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 22:43:16 +0800 Subject: [PATCH 18/19] ruff --- src/plugins/PFC/action_planner.py | 17 +++++++++++------ src/plugins/PFC/conversation.py | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 21cbc9c12..5412d657c 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -279,11 +279,16 @@ class ActionPlanner: action_time = action_data.get("time", "") elif isinstance(action_data, tuple): # 假设旧格式兼容 - if len(action_data) > 0: action_type = action_data[0] - if len(action_data) > 1: plan_reason = action_data[1] # 可能是规划原因或最终原因 - if len(action_data) > 2: status = action_data[2] - if status == "recall" and len(action_data) > 3: final_reason = action_data[3] - elif status == "done" and action_type in ["direct_reply", "send_new_message"]: plan_reason = "成功发送" # 简化显示 + if len(action_data) > 0: + action_type = action_data[0] + if len(action_data) > 1: + plan_reason = action_data[1] # 可能是规划原因或最终原因 + if len(action_data) > 2: + status = action_data[2] + if status == "recall" and len(action_data) > 3: + final_reason = action_data[3] + elif status == "done" and action_type in ["direct_reply", "send_new_message"]: + plan_reason = "成功发送" # 简化显示 reason_text = f", 失败/取消原因: {final_reason}" if final_reason else "" summary_line = f"- 时间:{action_time}, 尝试行动:'{action_type}', 状态:{status}{reason_text}" @@ -310,7 +315,7 @@ class ActionPlanner: # --- 选择 Prompt --- if last_successful_reply_action in ['direct_reply', 'send_new_message']: prompt_template = PROMPT_FOLLOW_UP - logger.debug(f"使用 PROMPT_FOLLOW_UP (追问决策)") + logger.debug("使用 PROMPT_FOLLOW_UP (追问决策)") else: prompt_template = PROMPT_INITIAL_REPLY logger.debug("使用 PROMPT_INITIAL_REPLY (首次/非连续回复决策)") diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index ae94ceab0..5b081324f 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -536,7 +536,7 @@ class Conversation: if not hasattr(self, 'waiter'): logger.error("Waiter 未初始化,无法等待。") raise AttributeError("Waiter not initialized") - timeout_occurred = await self.waiter.wait(self.conversation_info) + _timeout_occurred = await self.waiter.wait(self.conversation_info) action_successful = True # Wait 完成就算成功 except Exception as wait_err: logger.error(f"等待时出错: {wait_err}") From d0cdaac7d0a3797e52259a672d1f0106924507d6 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Sun, 27 Apr 2025 22:44:56 +0800 Subject: [PATCH 19/19] ruff --- src/plugins/PFC/conversation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 5b081324f..19e08ea52 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -505,7 +505,7 @@ class Conversation: if not hasattr(self, 'waiter'): logger.error("Waiter 未初始化,无法倾听。") raise AttributeError("Waiter not initialized") - timeout_occurred = await self.waiter.wait_listening(conversation_info) + await self.waiter.wait_listening(conversation_info) action_successful = True # Listening 完成就算成功 except Exception as listen_err: logger.error(f"倾听时出错: {listen_err}")