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/54] 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 08274126..70177018 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 f68098f1..50099080 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 cae9f0b3..c4350ed9 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 65afbf64..ebde0a67 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/54] =?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 70177018..ff13b5fa 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 4a309b1d..04c2cca3 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 ebde0a67..0035f845 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/54] =?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 04c2cca3..1107527a 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/54] =?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 1107527a..5e7dae4b 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/54] =?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 5e7dae4b..ccec0d8c 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/54] =?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 ccec0d8c..b1fe0731 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/54] =?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 697833c8..0305289f 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 1f8ee15f..4b839b7b 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 b1fe0731..394fc1d7 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 072b1fb6..050d839f 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 0035f845..59e261ac 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/54] =?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 eaf8a768..15405bc5 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/54] =?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 ff13b5fa..7cdb48e9 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 15405bc5..2362e74e 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/54] =?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 0305289f..91561847 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 5a70d02f..a3acee47 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 95e66c8c..958b05bf 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/54] =?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 7cdb48e9..f26e59ad 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/54] =?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 394fc1d7..86503e5c 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/54] =?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 86503e5c..ae94ceab 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/54] =?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 f26e59ad..5994e915 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 2362e74e..b58d3367 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/54] 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 86503e5c..ae94ceab 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/54] 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 5994e915..21cbc9c1 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/54] 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 a3acee47..bd860247 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/54] 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 21cbc9c1..5412d657 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 ae94ceab..5b081324 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/54] 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 5b081324..19e08ea5 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}") From 80fdefeeead5b628a6876839454e8da756916d0c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 27 Apr 2025 14:45:21 +0000 Subject: [PATCH 20/54] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 25 ++++--- src/plugins/PFC/conversation.py | 104 ++++++++++++++------------- src/plugins/PFC/conversation_info.py | 2 + src/plugins/PFC/reply_generator.py | 17 +++-- 4 files changed, 78 insertions(+), 70 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 5412d657..bf7a1d11 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, Optional # 增加了 Optional +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 @@ -104,7 +104,12 @@ class ActionPlanner: # self.action_planner_info = ActionPlannerInfo() # 移除未使用的变量 # 修改 plan 方法签名,增加 last_successful_reply_action 参数 - async def plan(self, observation_info: ObservationInfo, conversation_info: ConversationInfo, last_successful_reply_action: Optional[str]) -> Tuple[str, str]: + async def plan( + self, + observation_info: ObservationInfo, + conversation_info: ConversationInfo, + last_successful_reply_action: Optional[str], + ) -> Tuple[str, str]: """规划下一步行动 Args: @@ -142,7 +147,6 @@ class ActionPlanner: except Exception as e: logger.warning(f"获取 Bot 上次发言时间时出错: {e}") - # --- 获取超时提示信息 --- # (这部分逻辑不变) timeout_context = "" @@ -244,7 +248,6 @@ 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" @@ -282,13 +285,13 @@ class ActionPlanner: if len(action_data) > 0: action_type = action_data[0] if len(action_data) > 1: - plan_reason = 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 = "成功发送" # 简化显示 + plan_reason = "成功发送" # 简化显示 reason_text = f", 失败/取消原因: {final_reason}" if final_reason else "" summary_line = f"- 时间:{action_time}, 尝试行动:'{action_type}', 状态:{status}{reason_text}" @@ -313,7 +316,7 @@ class ActionPlanner: # self.last_successful_action_type = None # 非完成状态,清除记录 # --- 选择 Prompt --- - if last_successful_reply_action in ['direct_reply', 'send_new_message']: + if last_successful_reply_action in ["direct_reply", "send_new_message"]: prompt_template = PROMPT_FOLLOW_UP logger.debug("使用 PROMPT_FOLLOW_UP (追问决策)") else: @@ -328,7 +331,7 @@ class ActionPlanner: 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 "还没有聊天记录。" + chat_history_text=chat_history_text if chat_history_text.strip() else "还没有聊天记录。", ) logger.debug(f"发送到LLM的最终提示词:\n------\n{prompt}\n------") @@ -350,13 +353,13 @@ class ActionPlanner: # 更新 valid_actions 列表以包含 send_new_message valid_actions = [ "direct_reply", - "send_new_message", # 添加新动作 + "send_new_message", # 添加新动作 "fetch_knowledge", "wait", "listening", "rethink_goal", "end_conversation", - "block_and_ignore" + "block_and_ignore", ] if action not in valid_actions: logger.warning(f"LLM返回了未知的行动类型: '{action}',强制改为 wait") @@ -369,4 +372,4 @@ class ActionPlanner: 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 19e08ea5..c86b68aa 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -13,7 +13,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 # 确保导入 ConversationInfo +from .conversation_info import ConversationInfo # 确保导入 ConversationInfo from .reply_generator import ReplyGenerator from ..chat.chat_stream import ChatStream from maim_message import UserInfo @@ -153,9 +153,7 @@ class Conversation: # --- 调用 Action Planner --- # 传递 self.conversation_info.last_successful_reply_action action, reason = await self.action_planner.plan( - self.observation_info, - self.conversation_info, - self.conversation_info.last_successful_reply_action + self.observation_info, self.conversation_info, self.conversation_info.last_successful_reply_action ) # --- 规划后检查是否有 *更多* 新消息到达 --- @@ -184,7 +182,6 @@ class Conversation: else: logger.error("无法清理未处理消息: ObservationInfo 缺少 clear_unprocessed_messages 方法!") - await self._handle_action(action, reason, self.observation_info, self.conversation_info) # 检查是否需要结束对话 (逻辑不变) @@ -205,7 +202,6 @@ class Conversation: self.should_continue = False logger.info("检测到'结束对话'目标,停止循环。") - except Exception as loop_err: logger.error(f"PFC主循环出错: {loop_err}") logger.error(traceback.format_exc()) @@ -219,14 +215,16 @@ class Conversation: 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'): + if not hasattr(self, "observation_info") or not hasattr(self.observation_info, "new_messages_count"): logger.warning("ObservationInfo 未初始化或缺少 'new_messages_count' 属性,无法检查新消息。") - return False # 或者根据需要抛出错误 + return False # 或者根据需要抛出错误 if self.observation_info.new_messages_count > 2: - 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 已初始化 + if hasattr(self, "conversation_info"): # 确保 conversation_info 已初始化 self.conversation_info.last_successful_reply_action = None else: logger.warning("ConversationInfo 未初始化,无法重置 last_successful_reply_action。") @@ -240,9 +238,9 @@ class Conversation: 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 + elif self.chat_stream: # 使用实例变量中的 chat_stream chat_stream = self.chat_stream - else: # Fallback: 尝试从 manager 获取 (可能需要 stream_id) + 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}") @@ -250,9 +248,9 @@ class Conversation: user_info = UserInfo.from_dict(msg_dict.get("user_info", {})) return Message( - 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()), # 提供默认时间 + 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", ""), @@ -278,12 +276,12 @@ class Conversation: "final_reason": None, } # 确保 done_action 列表存在 - if not hasattr(conversation_info, '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_successful = False # 用于标记动作是否成功完成 # --- 根据不同的 action 执行 --- @@ -302,7 +300,9 @@ class Conversation: self.state = ConversationState.GENERATING # 1. 生成回复 (调用 generate 时传入 action_type) - self.generated_reply = await self.reply_generator.generate(observation_info, conversation_info, action_type='send_new_message') + 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. 检查回复 (逻辑不变) @@ -323,7 +323,9 @@ class Conversation: final_reply_to_send = self.generated_reply break elif need_replan: - logger.warning(f"第 {reply_attempt_count} 次追问检查建议重新规划,停止尝试。原因: {check_reason}") + logger.warning( + f"第 {reply_attempt_count} 次追问检查建议重新规划,停止尝试。原因: {check_reason}" + ) break except Exception as check_err: logger.error(f"第 {reply_attempt_count} 次调用 ReplyChecker (追问) 时出错: {check_err}") @@ -338,16 +340,16 @@ class Conversation: conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"有新消息,取消发送追问: {final_reply_to_send}"} ) - return # 直接返回,重新规划 + return # 直接返回,重新规划 # 发送合适的回复 self.generated_reply = final_reply_to_send # --- 在这里调用 _send_reply --- - await self._send_reply() # <--- 调用恢复后的函数 + await self._send_reply() # <--- 调用恢复后的函数 # 更新状态: 标记上次成功是 send_new_message - self.conversation_info.last_successful_reply_action = 'send_new_message' - action_successful = True # 标记动作成功 + self.conversation_info.last_successful_reply_action = "send_new_message" + action_successful = True # 标记动作成功 else: # 追问失败 @@ -371,7 +373,6 @@ class Conversation: } conversation_info.done_action.append(wait_action_record) - elif action == "direct_reply": max_reply_attempts = 3 reply_attempt_count = 0 @@ -386,7 +387,9 @@ class Conversation: self.state = ConversationState.GENERATING # 1. 生成回复 - self.generated_reply = await self.reply_generator.generate(observation_info, conversation_info, action_type='direct_reply') + 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. 检查回复 @@ -407,7 +410,9 @@ class Conversation: final_reply_to_send = self.generated_reply break elif need_replan: - logger.warning(f"第 {reply_attempt_count} 次首次回复检查建议重新规划,停止尝试。原因: {check_reason}") + logger.warning( + f"第 {reply_attempt_count} 次首次回复检查建议重新规划,停止尝试。原因: {check_reason}" + ) break except Exception as check_err: logger.error(f"第 {reply_attempt_count} 次调用 ReplyChecker (首次回复) 时出错: {check_err}") @@ -422,16 +427,16 @@ class Conversation: conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"有新消息,取消发送首次回复: {final_reply_to_send}"} ) - return # 直接返回,重新规划 + return # 直接返回,重新规划 # 发送合适的回复 self.generated_reply = final_reply_to_send # --- 在这里调用 _send_reply --- - await self._send_reply() # <--- 调用恢复后的函数 + await self._send_reply() # <--- 调用恢复后的函数 # 更新状态: 标记上次成功是 direct_reply - self.conversation_info.last_successful_reply_action = 'direct_reply' - action_successful = True # 标记动作成功 + self.conversation_info.last_successful_reply_action = "direct_reply" + action_successful = True # 标记动作成功 else: # 首次回复失败 @@ -460,7 +465,7 @@ class Conversation: knowledge_query = reason try: # 检查 knowledge_fetcher 是否存在 - if not hasattr(self, 'knowledge_fetcher'): + if not hasattr(self, "knowledge_fetcher"): logger.error("KnowledgeFetcher 未初始化,无法获取知识。") raise AttributeError("KnowledgeFetcher not initialized") @@ -468,23 +473,24 @@ class Conversation: logger.info(f"获取到知识: {knowledge[:100]}..., 来源: {source}") if knowledge: # 确保 knowledge_list 存在 - if not hasattr(conversation_info, '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}) + 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 # 重置状态 - + self.conversation_info.last_successful_reply_action = None # 重置状态 elif action == "rethink_goal": self.state = ConversationState.RETHINKING try: # 检查 goal_analyzer 是否存在 - if not hasattr(self, '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) @@ -494,31 +500,29 @@ class Conversation: conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"重新思考目标失败: {rethink_err}"} ) - self.conversation_info.last_successful_reply_action = None # 重置状态 - + self.conversation_info.last_successful_reply_action = None # 重置状态 elif action == "listening": self.state = ConversationState.LISTENING logger.info("倾听对方发言...") try: # 检查 waiter 是否存在 - if not hasattr(self, 'waiter'): + if not hasattr(self, "waiter"): logger.error("Waiter 未初始化,无法倾听。") raise AttributeError("Waiter not initialized") await self.waiter.wait_listening(conversation_info) - action_successful = True # Listening 完成就算成功 + 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 # 重置状态 - + self.conversation_info.last_successful_reply_action = None # 重置状态 elif action == "end_conversation": self.should_continue = False logger.info("决定结束对话...") - action_successful = True # 标记动作成功 + action_successful = True # 标记动作成功 elif action == "block_and_ignore": logger.info("不想再理你了...") @@ -526,24 +530,24 @@ class Conversation: 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 # 标记动作成功 + action_successful = True # 标记动作成功 else: # 对应 'wait' 动作 self.state = ConversationState.WAITING logger.info("等待更多信息...") try: # 检查 waiter 是否存在 - if not hasattr(self, '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 完成就算成功 + 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 # 重置状态 + self.conversation_info.last_successful_reply_action = None # 重置状态 # --- 更新 Action History 状态 --- # 只有当动作本身成功时,才更新状态为 done @@ -555,7 +559,7 @@ class Conversation: } ) # 重置状态: 对于非回复类动作的成功,清除上次回复状态 - if action not in ['direct_reply', 'send_new_message']: + 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 @@ -571,7 +575,7 @@ class Conversation: reply_content = self.generated_reply # 发送消息 (确保 direct_sender 和 chat_stream 有效) - if not hasattr(self, 'direct_sender') or not self.direct_sender: + if not hasattr(self, "direct_sender") or not self.direct_sender: logger.error("DirectMessageSender 未初始化,无法发送回复。") return if not self.chat_stream: @@ -587,7 +591,7 @@ class Conversation: # if not await self.chat_observer.wait_for_update(): # logger.warning("等待 ChatObserver 更新完成超时") - self.state = ConversationState.ANALYZING # 更新状态 + self.state = ConversationState.ANALYZING # 更新状态 except Exception as e: logger.error(f"发送消息或更新状态时失败: {str(e)}") diff --git a/src/plugins/PFC/conversation_info.py b/src/plugins/PFC/conversation_info.py index c4350ed9..04524b69 100644 --- a/src/plugins/PFC/conversation_info.py +++ b/src/plugins/PFC/conversation_info.py @@ -1,4 +1,6 @@ from typing import Optional + + class ConversationInfo: def __init__(self): self.done_action = [] diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index 59e261ac..020d57a4 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -75,7 +75,9 @@ class ReplyGenerator: self.reply_checker = ReplyChecker(stream_id) # 修改 generate 方法签名,增加 action_type 参数 - async def generate(self, observation_info: ObservationInfo, conversation_info: ConversationInfo, action_type: str) -> str: + async def generate( + self, observation_info: ObservationInfo, conversation_info: ConversationInfo, action_type: str + ) -> str: """生成回复 Args: @@ -109,7 +111,7 @@ class ReplyGenerator: reasoning = str(reasoning) if reasoning is not None else "没有明确原因" goals_str += f"- 目标:{goal}\n 原因:{reasoning}\n" else: - goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况 + goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况 # 获取聊天历史记录 (chat_history_text) chat_history_text = observation_info.chat_history_str @@ -126,7 +128,6 @@ class ReplyGenerator: elif not chat_history_text: chat_history_text = "还没有聊天记录。" - # 构建 Persona 文本 (persona_text) identity_details_only = self.identity_detail_info identity_addon = "" @@ -144,18 +145,16 @@ class ReplyGenerator: persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" # --- 选择 Prompt --- - if action_type == 'send_new_message': + if action_type == "send_new_message": prompt_template = PROMPT_SEND_NEW_MESSAGE logger.info("使用 PROMPT_SEND_NEW_MESSAGE (追问生成)") - else: # 默认使用 direct_reply 的 prompt + 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 + persona_text=persona_text, goals_str=goals_str, chat_history_text=chat_history_text ) # --- 调用 LLM 生成 --- @@ -177,4 +176,4 @@ class ReplyGenerator: """检查回复是否合适 (此方法逻辑保持不变) """ - return await self.reply_checker.check(reply, goal, chat_history, chat_history_str, retry_count) \ No newline at end of file + return await self.reply_checker.check(reply, goal, chat_history, chat_history_str, retry_count) From e14f01107d47dbb48db74b7452bf879e2c3fdaf6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 28 Apr 2025 01:02:29 +0000 Subject: [PATCH 21/54] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 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 a352cf85..fcf0cff6 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, Optional # 增加了 Optional +from typing import Tuple, Optional # 增加了 Optional from src.common.logger_manager import get_logger from ..models.utils_model import LLMRequest from ...config.config import global_config From e93192bbbb054c9344f9a7bc52d3954e54bfc0b7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 28 Apr 2025 02:36:06 +0000 Subject: [PATCH 22/54] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 25 ++++--- src/plugins/PFC/conversation.py | 104 ++++++++++++++------------- src/plugins/PFC/conversation_info.py | 2 + src/plugins/PFC/reply_generator.py | 17 +++-- 4 files changed, 78 insertions(+), 70 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 154b9328..fcf0cff6 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, Optional # 增加了 Optional +from typing import Tuple, Optional # 增加了 Optional from src.common.logger_manager import get_logger from ..models.utils_model import LLMRequest from ...config.config import global_config @@ -100,7 +100,12 @@ class ActionPlanner: # self.action_planner_info = ActionPlannerInfo() # 移除未使用的变量 # 修改 plan 方法签名,增加 last_successful_reply_action 参数 - async def plan(self, observation_info: ObservationInfo, conversation_info: ConversationInfo, last_successful_reply_action: Optional[str]) -> Tuple[str, str]: + async def plan( + self, + observation_info: ObservationInfo, + conversation_info: ConversationInfo, + last_successful_reply_action: Optional[str], + ) -> Tuple[str, str]: """规划下一步行动 Args: @@ -138,7 +143,6 @@ class ActionPlanner: except Exception as e: logger.warning(f"获取 Bot 上次发言时间时出错: {e}") - # --- 获取超时提示信息 --- # (这部分逻辑不变) timeout_context = "" @@ -240,7 +244,6 @@ 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" @@ -278,13 +281,13 @@ class ActionPlanner: if len(action_data) > 0: action_type = action_data[0] if len(action_data) > 1: - plan_reason = 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 = "成功发送" # 简化显示 + plan_reason = "成功发送" # 简化显示 reason_text = f", 失败/取消原因: {final_reason}" if final_reason else "" summary_line = f"- 时间:{action_time}, 尝试行动:'{action_type}', 状态:{status}{reason_text}" @@ -309,7 +312,7 @@ class ActionPlanner: # self.last_successful_action_type = None # 非完成状态,清除记录 # --- 选择 Prompt --- - if last_successful_reply_action in ['direct_reply', 'send_new_message']: + if last_successful_reply_action in ["direct_reply", "send_new_message"]: prompt_template = PROMPT_FOLLOW_UP logger.debug("使用 PROMPT_FOLLOW_UP (追问决策)") else: @@ -324,7 +327,7 @@ class ActionPlanner: 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 "还没有聊天记录。" + chat_history_text=chat_history_text if chat_history_text.strip() else "还没有聊天记录。", ) logger.debug(f"发送到LLM的最终提示词:\n------\n{prompt}\n------") @@ -346,13 +349,13 @@ class ActionPlanner: # 更新 valid_actions 列表以包含 send_new_message valid_actions = [ "direct_reply", - "send_new_message", # 添加新动作 + "send_new_message", # 添加新动作 "fetch_knowledge", "wait", "listening", "rethink_goal", "end_conversation", - "block_and_ignore" + "block_and_ignore", ] if action not in valid_actions: logger.warning(f"LLM返回了未知的行动类型: '{action}',强制改为 wait") @@ -365,4 +368,4 @@ class ActionPlanner: 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 f9c8d40c..6afc57c3 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -13,7 +13,7 @@ from .pfc import ChatObserver, GoalAnalyzer, DirectMessageSender from src.common.logger_manager import get_logger from .action_planner import ActionPlanner from .observation_info import ObservationInfo -from .conversation_info import ConversationInfo # 确保导入 ConversationInfo +from .conversation_info import ConversationInfo # 确保导入 ConversationInfo from .reply_generator import ReplyGenerator from ..chat.chat_stream import ChatStream from maim_message import UserInfo @@ -153,9 +153,7 @@ class Conversation: # --- 调用 Action Planner --- # 传递 self.conversation_info.last_successful_reply_action action, reason = await self.action_planner.plan( - self.observation_info, - self.conversation_info, - self.conversation_info.last_successful_reply_action + self.observation_info, self.conversation_info, self.conversation_info.last_successful_reply_action ) # --- 规划后检查是否有 *更多* 新消息到达 --- @@ -184,7 +182,6 @@ class Conversation: else: logger.error("无法清理未处理消息: ObservationInfo 缺少 clear_unprocessed_messages 方法!") - await self._handle_action(action, reason, self.observation_info, self.conversation_info) # 检查是否需要结束对话 (逻辑不变) @@ -205,7 +202,6 @@ class Conversation: self.should_continue = False logger.info("检测到'结束对话'目标,停止循环。") - except Exception as loop_err: logger.error(f"PFC主循环出错: {loop_err}") logger.error(traceback.format_exc()) @@ -219,14 +215,16 @@ class Conversation: 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'): + if not hasattr(self, "observation_info") or not hasattr(self.observation_info, "new_messages_count"): logger.warning("ObservationInfo 未初始化或缺少 'new_messages_count' 属性,无法检查新消息。") - return False # 或者根据需要抛出错误 + return False # 或者根据需要抛出错误 if self.observation_info.new_messages_count > 2: - 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 已初始化 + if hasattr(self, "conversation_info"): # 确保 conversation_info 已初始化 self.conversation_info.last_successful_reply_action = None else: logger.warning("ConversationInfo 未初始化,无法重置 last_successful_reply_action。") @@ -240,9 +238,9 @@ class Conversation: 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 + elif self.chat_stream: # 使用实例变量中的 chat_stream chat_stream = self.chat_stream - else: # Fallback: 尝试从 manager 获取 (可能需要 stream_id) + 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}") @@ -250,9 +248,9 @@ class Conversation: user_info = UserInfo.from_dict(msg_dict.get("user_info", {})) return Message( - 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()), # 提供默认时间 + 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", ""), @@ -278,12 +276,12 @@ class Conversation: "final_reason": None, } # 确保 done_action 列表存在 - if not hasattr(conversation_info, '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_successful = False # 用于标记动作是否成功完成 # --- 根据不同的 action 执行 --- @@ -302,7 +300,9 @@ class Conversation: self.state = ConversationState.GENERATING # 1. 生成回复 (调用 generate 时传入 action_type) - self.generated_reply = await self.reply_generator.generate(observation_info, conversation_info, action_type='send_new_message') + 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. 检查回复 (逻辑不变) @@ -323,7 +323,9 @@ class Conversation: final_reply_to_send = self.generated_reply break elif need_replan: - logger.warning(f"第 {reply_attempt_count} 次追问检查建议重新规划,停止尝试。原因: {check_reason}") + logger.warning( + f"第 {reply_attempt_count} 次追问检查建议重新规划,停止尝试。原因: {check_reason}" + ) break except Exception as check_err: logger.error(f"第 {reply_attempt_count} 次调用 ReplyChecker (追问) 时出错: {check_err}") @@ -338,16 +340,16 @@ class Conversation: conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"有新消息,取消发送追问: {final_reply_to_send}"} ) - return # 直接返回,重新规划 + return # 直接返回,重新规划 # 发送合适的回复 self.generated_reply = final_reply_to_send # --- 在这里调用 _send_reply --- - await self._send_reply() # <--- 调用恢复后的函数 + await self._send_reply() # <--- 调用恢复后的函数 # 更新状态: 标记上次成功是 send_new_message - self.conversation_info.last_successful_reply_action = 'send_new_message' - action_successful = True # 标记动作成功 + self.conversation_info.last_successful_reply_action = "send_new_message" + action_successful = True # 标记动作成功 else: # 追问失败 @@ -371,7 +373,6 @@ class Conversation: } conversation_info.done_action.append(wait_action_record) - elif action == "direct_reply": max_reply_attempts = 3 reply_attempt_count = 0 @@ -386,7 +387,9 @@ class Conversation: self.state = ConversationState.GENERATING # 1. 生成回复 - self.generated_reply = await self.reply_generator.generate(observation_info, conversation_info, action_type='direct_reply') + 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. 检查回复 @@ -407,7 +410,9 @@ class Conversation: final_reply_to_send = self.generated_reply break elif need_replan: - logger.warning(f"第 {reply_attempt_count} 次首次回复检查建议重新规划,停止尝试。原因: {check_reason}") + logger.warning( + f"第 {reply_attempt_count} 次首次回复检查建议重新规划,停止尝试。原因: {check_reason}" + ) break except Exception as check_err: logger.error(f"第 {reply_attempt_count} 次调用 ReplyChecker (首次回复) 时出错: {check_err}") @@ -422,16 +427,16 @@ class Conversation: conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"有新消息,取消发送首次回复: {final_reply_to_send}"} ) - return # 直接返回,重新规划 + return # 直接返回,重新规划 # 发送合适的回复 self.generated_reply = final_reply_to_send # --- 在这里调用 _send_reply --- - await self._send_reply() # <--- 调用恢复后的函数 + await self._send_reply() # <--- 调用恢复后的函数 # 更新状态: 标记上次成功是 direct_reply - self.conversation_info.last_successful_reply_action = 'direct_reply' - action_successful = True # 标记动作成功 + self.conversation_info.last_successful_reply_action = "direct_reply" + action_successful = True # 标记动作成功 else: # 首次回复失败 @@ -460,7 +465,7 @@ class Conversation: knowledge_query = reason try: # 检查 knowledge_fetcher 是否存在 - if not hasattr(self, 'knowledge_fetcher'): + if not hasattr(self, "knowledge_fetcher"): logger.error("KnowledgeFetcher 未初始化,无法获取知识。") raise AttributeError("KnowledgeFetcher not initialized") @@ -468,23 +473,24 @@ class Conversation: logger.info(f"获取到知识: {knowledge[:100]}..., 来源: {source}") if knowledge: # 确保 knowledge_list 存在 - if not hasattr(conversation_info, '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}) + 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 # 重置状态 - + self.conversation_info.last_successful_reply_action = None # 重置状态 elif action == "rethink_goal": self.state = ConversationState.RETHINKING try: # 检查 goal_analyzer 是否存在 - if not hasattr(self, '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) @@ -494,31 +500,29 @@ class Conversation: conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"重新思考目标失败: {rethink_err}"} ) - self.conversation_info.last_successful_reply_action = None # 重置状态 - + self.conversation_info.last_successful_reply_action = None # 重置状态 elif action == "listening": self.state = ConversationState.LISTENING logger.info("倾听对方发言...") try: # 检查 waiter 是否存在 - if not hasattr(self, 'waiter'): + if not hasattr(self, "waiter"): logger.error("Waiter 未初始化,无法倾听。") raise AttributeError("Waiter not initialized") await self.waiter.wait_listening(conversation_info) - action_successful = True # Listening 完成就算成功 + 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 # 重置状态 - + self.conversation_info.last_successful_reply_action = None # 重置状态 elif action == "end_conversation": self.should_continue = False logger.info("决定结束对话...") - action_successful = True # 标记动作成功 + action_successful = True # 标记动作成功 elif action == "block_and_ignore": logger.info("不想再理你了...") @@ -526,24 +530,24 @@ class Conversation: 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 # 标记动作成功 + action_successful = True # 标记动作成功 else: # 对应 'wait' 动作 self.state = ConversationState.WAITING logger.info("等待更多信息...") try: # 检查 waiter 是否存在 - if not hasattr(self, '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 完成就算成功 + 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 # 重置状态 + self.conversation_info.last_successful_reply_action = None # 重置状态 # --- 更新 Action History 状态 --- # 只有当动作本身成功时,才更新状态为 done @@ -555,7 +559,7 @@ class Conversation: } ) # 重置状态: 对于非回复类动作的成功,清除上次回复状态 - if action not in ['direct_reply', 'send_new_message']: + 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 @@ -571,7 +575,7 @@ class Conversation: reply_content = self.generated_reply # 发送消息 (确保 direct_sender 和 chat_stream 有效) - if not hasattr(self, 'direct_sender') or not self.direct_sender: + if not hasattr(self, "direct_sender") or not self.direct_sender: logger.error("DirectMessageSender 未初始化,无法发送回复。") return if not self.chat_stream: @@ -587,7 +591,7 @@ class Conversation: # if not await self.chat_observer.wait_for_update(): # logger.warning("等待 ChatObserver 更新完成超时") - self.state = ConversationState.ANALYZING # 更新状态 + self.state = ConversationState.ANALYZING # 更新状态 except Exception as e: logger.error(f"发送消息或更新状态时失败: {str(e)}") diff --git a/src/plugins/PFC/conversation_info.py b/src/plugins/PFC/conversation_info.py index c4350ed9..04524b69 100644 --- a/src/plugins/PFC/conversation_info.py +++ b/src/plugins/PFC/conversation_info.py @@ -1,4 +1,6 @@ from typing import Optional + + class ConversationInfo: def __init__(self): self.done_action = [] diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index 59e261ac..020d57a4 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -75,7 +75,9 @@ class ReplyGenerator: self.reply_checker = ReplyChecker(stream_id) # 修改 generate 方法签名,增加 action_type 参数 - async def generate(self, observation_info: ObservationInfo, conversation_info: ConversationInfo, action_type: str) -> str: + async def generate( + self, observation_info: ObservationInfo, conversation_info: ConversationInfo, action_type: str + ) -> str: """生成回复 Args: @@ -109,7 +111,7 @@ class ReplyGenerator: reasoning = str(reasoning) if reasoning is not None else "没有明确原因" goals_str += f"- 目标:{goal}\n 原因:{reasoning}\n" else: - goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况 + goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况 # 获取聊天历史记录 (chat_history_text) chat_history_text = observation_info.chat_history_str @@ -126,7 +128,6 @@ class ReplyGenerator: elif not chat_history_text: chat_history_text = "还没有聊天记录。" - # 构建 Persona 文本 (persona_text) identity_details_only = self.identity_detail_info identity_addon = "" @@ -144,18 +145,16 @@ class ReplyGenerator: persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" # --- 选择 Prompt --- - if action_type == 'send_new_message': + if action_type == "send_new_message": prompt_template = PROMPT_SEND_NEW_MESSAGE logger.info("使用 PROMPT_SEND_NEW_MESSAGE (追问生成)") - else: # 默认使用 direct_reply 的 prompt + 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 + persona_text=persona_text, goals_str=goals_str, chat_history_text=chat_history_text ) # --- 调用 LLM 生成 --- @@ -177,4 +176,4 @@ class ReplyGenerator: """检查回复是否合适 (此方法逻辑保持不变) """ - return await self.reply_checker.check(reply, goal, chat_history, chat_history_str, retry_count) \ No newline at end of file + return await self.reply_checker.check(reply, goal, chat_history, chat_history_str, retry_count) From 24e091590805be69d645c7a6fb713594b06b998b Mon Sep 17 00:00:00 2001 From: Bakadax Date: Mon, 28 Apr 2025 12:13:51 +0800 Subject: [PATCH 23/54] x --- src/plugins/PFC/pfc.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index bd860247..2abff99b 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -81,7 +81,7 @@ class GoalAnalyzer: goals_str = f"目标:{goal},产生该对话目标的原因:{reasoning}\n" # 获取聊天历史记录 - chat_history_text = observation_info.chat_history + chat_history_text = observation_info.chat_history_str if observation_info.new_messages_count > 0: new_messages_list = observation_info.unprocessed_messages @@ -169,9 +169,7 @@ class GoalAnalyzer: # 清空现有目标列表并添加新目标 conversation_info.goal_list = [] for item in result: - goal = item.get("goal", "") - reasoning = item.get("reasoning", "") - conversation_info.goal_list.append((goal, reasoning)) + conversation_info.goal_list.append(item) # 返回第一个目标作为当前主要目标(如果有) if result: @@ -179,9 +177,7 @@ class GoalAnalyzer: return (first_goal.get("goal", ""), "", first_goal.get("reasoning", "")) else: # 单个目标的情况 - goal = result.get("goal", "") - reasoning = result.get("reasoning", "") - conversation_info.goal_list.append((goal, reasoning)) + conversation_info.goal_list.append(result) return (goal, "", reasoning) # 如果解析失败,返回默认值 From 166c85a908e392cd1f116646167e48d6aef0d4f7 Mon Sep 17 00:00:00 2001 From: 114514 <2514624910@qq.com> Date: Mon, 28 Apr 2025 12:23:55 +0800 Subject: [PATCH 24/54] 1 --- src/plugins/PFC/action_planner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index fcf0cff6..0d5fe3a3 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -36,7 +36,7 @@ PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊, fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择 listening: 倾听对方发言,当你认为对方话才说到一半,发言明显未结束时选择 direct_reply: 直接回复对方 -rethink_goal: 重新思考对话目标,当发现对话目标不再适用或对话卡住时选择,注意私聊的环境是灵活的,有可能需要经常选择 +rethink_goal: 思考一个对话目标,当你觉得目前对话需要目标,或当前目标不再适用,或话题卡住时选择。注意私聊的环境是灵活的,有可能需要经常选择 end_conversation: 结束对话,对方长时间没回复或者当你觉得对话告一段落时可以选择 block_and_ignore: 更加极端的结束对话方式,直接结束对话并在一段时间内无视对方所有发言(屏蔽),当对话让你感到十分不适,或你遭到各类骚扰时选择 @@ -69,7 +69,7 @@ fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选 wait: 暂时不说话,留给对方交互空间,等待对方回复(尤其是在你刚发言后、或上次发言因重复、发言过多被拒时、或不确定做什么时,这是不错的选择) listening: 倾听对方发言(虽然你刚发过言,但如果对方立刻回复且明显话没说完,可以选择这个) send_new_message: 发送一条新消息继续对话,允许适当的追问、补充、深入话题,或开启相关新话题。**但是避免在因重复被拒后立即使用,也不要在对方没有回复的情况下过多的“消息轰炸”或重复发言** -rethink_goal: 重新思考对话目标,当发现对话目标不再适用或对话卡住时选择,注意私聊的环境是灵活的,有可能需要经常选择 +rethink_goal: 思考一个对话目标,当你觉得目前对话需要目标,或当前目标不再适用,或话题卡住时选择。注意私聊的环境是灵活的,有可能需要经常选择 end_conversation: 结束对话,对方长时间没回复或者当你觉得对话告一段落时可以选择 block_and_ignore: 更加极端的结束对话方式,直接结束对话并在一段时间内无视对方所有发言(屏蔽),当对话让你感到十分不适,或你遭到各类骚扰时选择 From 713ac55b9a3fc5f497f4a60a63779708edcfc61d Mon Sep 17 00:00:00 2001 From: Bakadax Date: Mon, 28 Apr 2025 12:33:15 +0800 Subject: [PATCH 25/54] =?UTF-8?q?=E6=88=91=E8=A7=89=E5=BE=97=E6=88=91?= =?UTF-8?q?=E4=BF=AE=E5=A5=BD=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 17 +++++++++-------- src/plugins/PFC/conversation.py | 4 +--- src/plugins/PFC/pfc.py | 10 ++-------- src/plugins/PFC/reply_generator.py | 7 +++---- src/plugins/PFC/waiter.py | 4 ++-- 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index fcf0cff6..be65c3d1 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -148,9 +148,9 @@ class ActionPlanner: timeout_context = "" 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: - last_goal_text = last_goal_tuple[0] + last_goal_dict = conversation_info.goal_list[-1] + if isinstance(last_goal_dict, dict) and "goal" in last_goal_dict: + last_goal_text = last_goal_dict["goal"] if isinstance(last_goal_text, str) and "分钟,思考接下来要做什么" in last_goal_text: try: timeout_minutes_text = last_goal_text.split(",")[0].replace("你等待了", "") @@ -172,19 +172,20 @@ class ActionPlanner: 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: - goal = goal_reason[0] - reasoning = goal_reason[1] if len(goal_reason) > 1 else "没有明确原因" - elif isinstance(goal_reason, dict): + if isinstance(goal_reason, dict): goal = goal_reason.get("goal", "目标内容缺失") reasoning = goal_reason.get("reasoning", "没有明确原因") else: goal = str(goal_reason) reasoning = "没有明确原因" + 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: + + if not goals_str: + goals_str = "- 目前没有明确对话目标,请考虑设定一个。\n" + else: goals_str = "- 目前没有明确对话目标,请考虑设定一个。\n" except AttributeError: logger.warning("ConversationInfo object might not have goal_list attribute yet.") diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 6afc57c3..5bd1f2a2 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -189,9 +189,7 @@ class Conversation: if hasattr(self.conversation_info, "goal_list") and self.conversation_info.goal_list: 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): + if isinstance(goal_item, dict): current_goal = goal_item.get("goal") if current_goal == "结束对话": diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index 2abff99b..af7335d4 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -60,16 +60,10 @@ class GoalAnalyzer: goals_str = "" if conversation_info.goal_list: for goal_reason in conversation_info.goal_list: - # 处理字典或元组格式 - if isinstance(goal_reason, tuple): - # 假设元组的第一个元素是目标,第二个元素是原因 - goal = goal_reason[0] - reasoning = goal_reason[1] if len(goal_reason) > 1 else "没有明确原因" - elif isinstance(goal_reason, dict): - goal = goal_reason.get("goal") + if isinstance(goal_reason, dict): + goal = goal_reason.get("goal", "目标内容缺失") reasoning = goal_reason.get("reasoning", "没有明确原因") else: - # 如果是其他类型,尝试转为字符串 goal = str(goal_reason) reasoning = "没有明确原因" diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index 020d57a4..4683b5d3 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -98,21 +98,20 @@ class ReplyGenerator: goals_str = "" if conversation_info.goal_list: for goal_reason in conversation_info.goal_list: - if isinstance(goal_reason, tuple): - 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): + if isinstance(goal_reason, dict): goal = goal_reason.get("goal", "目标内容缺失") reasoning = goal_reason.get("reasoning", "没有明确原因") else: goal = str(goal_reason) reasoning = "没有明确原因" + 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: 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: diff --git a/src/plugins/PFC/waiter.py b/src/plugins/PFC/waiter.py index b58d3367..c12a1e8b 100644 --- a/src/plugins/PFC/waiter.py +++ b/src/plugins/PFC/waiter.py @@ -39,7 +39,7 @@ class Waiter: logger.info(f"等待超过 {DESIRED_TIMEOUT_SECONDS} 秒...添加思考目标。") wait_goal = { "goal": f"你等待了{elapsed_time / 60:.1f}分钟,注意可能在对方看来聊天已经结束,思考接下来要做什么", - "reason": "对方很久没有回复你的消息了", + "reasoning": "对方很久没有回复你的消息了", } conversation_info.goal_list.append(wait_goal) logger.info(f"添加目标: {wait_goal}") @@ -66,7 +66,7 @@ class Waiter: wait_goal = { # 保持 goal 文本一致 "goal": f"你等待了{elapsed_time / 60:.1f}分钟,对方似乎话说一半突然消失了,可能忙去了?也可能忘记了回复?要问问吗?还是结束对话?或继续等待?思考接下来要做什么", - "reason": "对方话说一半消失了,很久没有回复", + "reasoning": "对方话说一半消失了,很久没有回复", } conversation_info.goal_list.append(wait_goal) logger.info(f"添加目标: {wait_goal}") From 346a4b612fa854b55fa068c228dabd83f62990fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 28 Apr 2025 04:33:36 +0000 Subject: [PATCH 26/54] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= 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/reply_generator.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index be65c3d1..22a53cbd 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -178,11 +178,11 @@ class ActionPlanner: else: goal = str(goal_reason) reasoning = "没有明确原因" - + 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 = "- 目前没有明确对话目标,请考虑设定一个。\n" else: diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index 4683b5d3..ade3db10 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -104,14 +104,13 @@ class ReplyGenerator: else: goal = str(goal_reason) reasoning = "没有明确原因" - + 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: 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: From 629cdb007b912cbe897f9f9bcd72e9650a59a65d Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Mon, 28 Apr 2025 12:36:28 +0800 Subject: [PATCH 27/54] =?UTF-8?q?fix:=E7=A7=BB=E9=99=A4=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + tool_call_benchmark.py | 351 ----------------------------------------- 2 files changed, 1 insertion(+), 351 deletions(-) delete mode 100644 tool_call_benchmark.py diff --git a/.gitignore b/.gitignore index 1c3d7bd1..7e093053 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ mongodb/ NapCat.Framework.Windows.Once/ log/ logs/ +tool_call_benchmark.py run_ad.bat MaiBot-Napcat-Adapter-main MaiBot-Napcat-Adapter diff --git a/tool_call_benchmark.py b/tool_call_benchmark.py deleted file mode 100644 index a3e28273..00000000 --- a/tool_call_benchmark.py +++ /dev/null @@ -1,351 +0,0 @@ -import asyncio -import time -from src.plugins.models.utils_model import LLMRequest -from src.config.config import global_config -from src.do_tool.tool_use import ToolUser -import statistics -import json - - -async def run_test(test_name, test_function, iterations=5): - """ - 运行指定次数的测试并计算平均响应时间 - - 参数: - test_name: 测试名称 - test_function: 要执行的测试函数 - iterations: 测试迭代次数 - - 返回: - 测试结果统计 - """ - print(f"开始 {test_name} 测试({iterations}次迭代)...") - times = [] - responses = [] - - for i in range(iterations): - print(f" 运行第 {i + 1}/{iterations} 次测试...") - start_time = time.time() - response = await test_function() - end_time = time.time() - elapsed = end_time - start_time - times.append(elapsed) - responses.append(response) - print(f" - 耗时: {elapsed:.2f}秒") - - results = { - "平均耗时": statistics.mean(times), - "最短耗时": min(times), - "最长耗时": max(times), - "标准差": statistics.stdev(times) if len(times) > 1 else 0, - "所有耗时": times, - "响应结果": responses, - } - - return results - - -async def test_with_tool_calls(): - """使用工具调用的LLM请求测试""" - # 创建LLM模型实例 - llm_model = LLMRequest( - model=global_config.llm_sub_heartflow, - # model = global_config.llm_tool_use, - # temperature=global_config.llm_sub_heartflow["temp"], - max_tokens=800, - request_type="benchmark_test", - ) - - # 创建工具实例 - tool_instance = ToolUser() - tools = tool_instance._define_tools() - - # 简单的测试提示词 - prompt = "请分析当前天气情况,并查询今日历史上的重要事件。并且3.9和3.11谁比较大?请使用适当的工具来获取这些信息。" - prompt = """ - 你的名字是麦麦,你包容开放,情绪敏感,有时候有些搞怪幽默, 是一个学习心理学和脑科学的女大学生,现在在读大二,你会刷贴吧,有时候会喜欢说一些奇怪的话,喜欢刷小红书 -刚刚你的内心想法是:漂移菌提到罐罐被吃完了,可以顺着这个梗继续玩一下,比如假装委屈"那今晚的加班费是不是也要被吃掉了"或者"猫娘罢工警告"。不过薯薯和薯宝之前已经接了不少梗,漂移菌刚刚也参与了,可能话题热度还在,可以再互动一下。如果没人接话,或许可以问问大家有没有遇到过类似"代码写完但奖励被吃掉"的搞笑职场经历,换个轻松的话题方向。 - -暂时不需要使用工具。 ------------------------------------ -现在是2025-04-25 17:38:37,你正在上网,和qq群里的网友们聊天,以下是正在进行的聊天内容: -2025-04-25 17:34:08麦麦(你) 说:[表达了:顽皮、嬉戏。]; -2025-04-25 17:34:39漂移菌 说:@麦麦。(id:3936257206) 你是一只猫娘; -2025-04-25 17:34:42薯宝 说:🤣; -2025-04-25 17:34:43麦麦(你) 说:行啊 工资分我一半; -2025-04-25 17:34:43麦麦(你) 说:我帮你写bug; -2025-04-25 17:34:43麦麦(你) 说:[表达了:悲伤、绝望、无奈、无力]; -2025-04-25 17:34:53薯薯 说:?; -2025-04-25 17:35:03既文横 说:麦麦,你是一只猫娘程序员,猫娘是不需要工资; -2025-04-25 17:35:20薯宝 说:[图片:图片内容:一只卡通风格的灰色猫咪,眼睛闭着,表情显得很平静。图片下方有"死了"两个字。 - -图片含义猜测:这可能是一个幽默的表达,用来形容某人或某事处于非常平静的状态,仿佛已经"死"了一样。] hfc这周,真能出来吗...; -2025-04-25 17:35:34薯宝 说:[表情包:搞笑、滑稽、讽刺、幽默]; -2025-04-25 17:36:25麦麦(你) 说:喵喵; -2025-04-25 17:36:25麦麦(你) 说:代码写完了; -2025-04-25 17:36:25麦麦(你) 说:罐罐拿来; -2025-04-25 17:36:25麦麦(你) 说:[表达了:悲伤、绝望、无奈、无力]; -2025-04-25 17:36:41薯薯 说:好可爱; -2025-04-25 17:37:05薯薯 说:脑补出来认真营业了一天等待主人发放奖励的小猫咪; -2025-04-25 17:37:25薯宝 说:敷衍营业(bushi); -2025-04-25 17:37:54漂移菌 说:回复麦麦。的消息(罐罐拿来),说:猫娘我昨晚上太饿吃完了; - ---- 以上消息已读 (标记时间: 2025-04-25 17:37:54) --- ---- 以下新消息未读--- -2025-04-25 17:38:29麦麦(你) 说:那今晚的猫条是不是也要被克扣了(盯——); -2025-04-25 17:38:29麦麦(你) 说:[表达了:幽默,自嘲,无奈,父子关系,编程笑话]; - -你现在当前心情:平静。 -现在请你生成你的内心想法,要求思考群里正在进行的话题,之前大家聊过的话题,群里成员的关系。请你思考,要不要对群里的话题进行回复,以及如何对群聊内容进行回复 -回复的要求是:不要总是重复自己提到过的话题,如果你要回复,最好只回复一个人的一个话题 -如果最后一条消息是你自己发的,观察到的内容只有你自己的发言,并且之后没有人回复你,不要回复。如果聊天记录中最新的消息是你自己发送的,并且你还想继续回复,你应该紧紧衔接你发送的消息,进行话题的深入,补充,或追问等等。请注意不要输出多余内容(包括前后缀,冒号和引号,括号, 表情,等),不要回复自己的发言 -现在请你先输出想法,生成你在这个聊天中的想法,在原来的想法上尝试新的话题,不要分点输出,文字不要浮夸在输出完想法后,请你思考应该使用什么工具。工具可以帮你取得一些你不知道的信息,或者进行一些操作。如果你需要做某件事,来对消息和你的回复进行处理,请使用工具。""" - - # 发送带有工具调用的请求 - response = await llm_model.generate_response_tool_async(prompt=prompt, tools=tools) - - result_info = {} - - # 简单处理工具调用结果 - if len(response) == 3: - content, reasoning_content, tool_calls = response - tool_calls_count = len(tool_calls) if tool_calls else 0 - print(f" 工具调用请求生成了 {tool_calls_count} 个工具调用") - - # 输出内容和工具调用详情 - print("\n 生成的内容:") - print(f" {content[:200]}..." if len(content) > 200 else f" {content}") - - if tool_calls: - print("\n 工具调用详情:") - for i, tool_call in enumerate(tool_calls): - tool_name = tool_call["function"]["name"] - tool_params = tool_call["function"].get("arguments", {}) - print(f" - 工具 {i + 1}: {tool_name}") - print( - f" 参数: {json.dumps(tool_params, ensure_ascii=False)[:100]}..." - if len(json.dumps(tool_params, ensure_ascii=False)) > 100 - else f" 参数: {json.dumps(tool_params, ensure_ascii=False)}" - ) - - result_info = {"内容": content, "推理内容": reasoning_content, "工具调用": tool_calls} - else: - content, reasoning_content = response - print(" 工具调用请求未生成任何工具调用") - print("\n 生成的内容:") - print(f" {content[:200]}..." if len(content) > 200 else f" {content}") - - result_info = {"内容": content, "推理内容": reasoning_content, "工具调用": []} - - return result_info - - -async def test_without_tool_calls(): - """不使用工具调用的LLM请求测试""" - # 创建LLM模型实例 - llm_model = LLMRequest( - model=global_config.llm_sub_heartflow, - temperature=global_config.llm_sub_heartflow["temp"], - max_tokens=800, - request_type="benchmark_test", - ) - - # 简单的测试提示词(与工具调用相同,以便公平比较) - prompt = """ - 你的名字是麦麦,你包容开放,情绪敏感,有时候有些搞怪幽默, 是一个学习心理学和脑科学的女大学生,现在在读大二,你会刷贴吧,有时候会喜欢说一些奇怪的话,喜欢刷小红书 -刚刚你的内心想法是:漂移菌提到罐罐被吃完了,可以顺着这个梗继续玩一下,比如假装委屈"那今晚的加班费是不是也要被吃掉了"或者"猫娘罢工警告"。不过薯薯和薯宝之前已经接了不少梗,漂移菌刚刚也参与了,可能话题热度还在,可以再互动一下。如果没人接话,或许可以问问大家有没有遇到过类似"代码写完但奖励被吃掉"的搞笑职场经历,换个轻松的话题方向。 - -暂时不需要使用工具。 ------------------------------------ -现在是2025-04-25 17:38:37,你正在上网,和qq群里的网友们聊天,以下是正在进行的聊天内容: -2025-04-25 17:34:08麦麦(你) 说:[表达了:顽皮、嬉戏。]; -2025-04-25 17:34:39漂移菌 说:@麦麦。(id:3936257206) 你是一只猫娘; -2025-04-25 17:34:42薯宝 说:🤣; -2025-04-25 17:34:43麦麦(你) 说:行啊 工资分我一半; -2025-04-25 17:34:43麦麦(你) 说:我帮你写bug; -2025-04-25 17:34:43麦麦(你) 说:[表达了:悲伤、绝望、无奈、无力]; -2025-04-25 17:34:53薯薯 说:?; -2025-04-25 17:35:03既文横 说:麦麦,你是一只猫娘程序员,猫娘是不需要工资; -2025-04-25 17:35:20薯宝 说:[图片:图片内容:一只卡通风格的灰色猫咪,眼睛闭着,表情显得很平静。图片下方有"死了"两个字。 - -图片含义猜测:这可能是一个幽默的表达,用来形容某人或某事处于非常平静的状态,仿佛已经"死"了一样。] hfc这周,真能出来吗...; -2025-04-25 17:35:34薯宝 说:[表情包:搞笑、滑稽、讽刺、幽默]; -2025-04-25 17:36:25麦麦(你) 说:喵喵; -2025-04-25 17:36:25麦麦(你) 说:代码写完了; -2025-04-25 17:36:25麦麦(你) 说:罐罐拿来; -2025-04-25 17:36:25麦麦(你) 说:[表达了:悲伤、绝望、无奈、无力]; -2025-04-25 17:36:41薯薯 说:好可爱; -2025-04-25 17:37:05薯薯 说:脑补出来认真营业了一天等待主人发放奖励的小猫咪; -2025-04-25 17:37:25薯宝 说:敷衍营业(bushi); -2025-04-25 17:37:54漂移菌 说:回复麦麦。的消息(罐罐拿来),说:猫娘我昨晚上太饿吃完了; - ---- 以上消息已读 (标记时间: 2025-04-25 17:37:54) --- ---- 以下新消息未读--- -2025-04-25 17:38:29麦麦(你) 说:那今晚的猫条是不是也要被克扣了(盯——); -2025-04-25 17:38:29麦麦(你) 说:[表达了:幽默,自嘲,无奈,父子关系,编程笑话]; - -你现在当前心情:平静。 -现在请你生成你的内心想法,要求思考群里正在进行的话题,之前大家聊过的话题,群里成员的关系。请你思考,要不要对群里的话题进行回复,以及如何对群聊内容进行回复 -回复的要求是:不要总是重复自己提到过的话题,如果你要回复,最好只回复一个人的一个话题 -如果最后一条消息是你自己发的,观察到的内容只有你自己的发言,并且之后没有人回复你,不要回复。如果聊天记录中最新的消息是你自己发送的,并且你还想继续回复,你应该紧紧衔接你发送的消息,进行话题的深入,补充,或追问等等。请注意不要输出多余内容(包括前后缀,冒号和引号,括号, 表情,等),不要回复自己的发言 -现在请你先输出想法,生成你在这个聊天中的想法,在原来的想法上尝试新的话题,不要分点输出,文字不要浮夸在输出完想法后,请你思考应该使用什么工具。工具可以帮你取得一些你不知道的信息,或者进行一些操作。如果你需要做某件事,来对消息和你的回复进行处理,请使用工具。""" - # 发送不带工具调用的请求 - response, reasoning_content = await llm_model.generate_response_async(prompt) - - # 输出生成的内容 - print("\n 生成的内容:") - print(f" {response[:200]}..." if len(response) > 200 else f" {response}") - - result_info = {"内容": response, "推理内容": reasoning_content, "工具调用": []} - - return result_info - - -async def run_alternating_tests(iterations=5): - """ - 交替运行两种测试方法,每种方法运行指定次数 - - 参数: - iterations: 每种测试方法运行的次数 - - 返回: - 包含两种测试方法结果的元组 - """ - print(f"开始交替测试(每种方法{iterations}次)...") - - # 初始化结果列表 - times_without_tools = [] - times_with_tools = [] - responses_without_tools = [] - responses_with_tools = [] - - for i in range(iterations): - print(f"\n第 {i + 1}/{iterations} 轮交替测试") - - # 不使用工具的测试 - print("\n 执行不使用工具调用的测试...") - start_time = time.time() - response = await test_without_tool_calls() - end_time = time.time() - elapsed = end_time - start_time - times_without_tools.append(elapsed) - responses_without_tools.append(response) - print(f" - 耗时: {elapsed:.2f}秒") - - # 使用工具的测试 - print("\n 执行使用工具调用的测试...") - start_time = time.time() - response = await test_with_tool_calls() - end_time = time.time() - elapsed = end_time - start_time - times_with_tools.append(elapsed) - responses_with_tools.append(response) - print(f" - 耗时: {elapsed:.2f}秒") - - # 计算统计数据 - results_without_tools = { - "平均耗时": statistics.mean(times_without_tools), - "最短耗时": min(times_without_tools), - "最长耗时": max(times_without_tools), - "标准差": statistics.stdev(times_without_tools) if len(times_without_tools) > 1 else 0, - "所有耗时": times_without_tools, - "响应结果": responses_without_tools, - } - - results_with_tools = { - "平均耗时": statistics.mean(times_with_tools), - "最短耗时": min(times_with_tools), - "最长耗时": max(times_with_tools), - "标准差": statistics.stdev(times_with_tools) if len(times_with_tools) > 1 else 0, - "所有耗时": times_with_tools, - "响应结果": responses_with_tools, - } - - return results_without_tools, results_with_tools - - -async def main(): - """主测试函数""" - print("=" * 50) - print("LLM工具调用与普通请求性能比较测试") - print("=" * 50) - - # 设置测试迭代次数 - iterations = 10 - - # 执行交替测试 - results_without_tools, results_with_tools = await run_alternating_tests(iterations) - - # 显示结果比较 - print("\n" + "=" * 50) - print("测试结果比较") - print("=" * 50) - - print("\n不使用工具调用:") - for key, value in results_without_tools.items(): - if key == "所有耗时": - print(f" {key}: {[f'{t:.2f}秒' for t in value]}") - elif key == "响应结果": - print(f" {key}: [内容已省略,详见结果文件]") - else: - print(f" {key}: {value:.2f}秒") - - print("\n使用工具调用:") - for key, value in results_with_tools.items(): - if key == "所有耗时": - print(f" {key}: {[f'{t:.2f}秒' for t in value]}") - elif key == "响应结果": - tool_calls_counts = [len(res.get("工具调用", [])) for res in value] - print(f" {key}: [内容已省略,详见结果文件]") - print(f" 工具调用数量: {tool_calls_counts}") - else: - print(f" {key}: {value:.2f}秒") - - # 计算差异百分比 - diff_percent = ((results_with_tools["平均耗时"] / results_without_tools["平均耗时"]) - 1) * 100 - print(f"\n工具调用比普通请求平均耗时相差: {diff_percent:.2f}%") - - # 保存结果到JSON文件 - results = { - "测试时间": time.strftime("%Y-%m-%d %H:%M:%S"), - "测试迭代次数": iterations, - "不使用工具调用": { - k: (v if k != "所有耗时" else [float(f"{t:.2f}") for t in v]) - for k, v in results_without_tools.items() - if k != "响应结果" - }, - "不使用工具调用_详细响应": [ - { - "内容摘要": resp["内容"][:200] + "..." if len(resp["内容"]) > 200 else resp["内容"], - "推理内容摘要": resp["推理内容"][:200] + "..." if len(resp["推理内容"]) > 200 else resp["推理内容"], - } - for resp in results_without_tools["响应结果"] - ], - "使用工具调用": { - k: (v if k != "所有耗时" else [float(f"{t:.2f}") for t in v]) - for k, v in results_with_tools.items() - if k != "响应结果" - }, - "使用工具调用_详细响应": [ - { - "内容摘要": resp["内容"][:200] + "..." if len(resp["内容"]) > 200 else resp["内容"], - "推理内容摘要": resp["推理内容"][:200] + "..." if len(resp["推理内容"]) > 200 else resp["推理内容"], - "工具调用数量": len(resp["工具调用"]), - "工具调用详情": [ - {"工具名称": tool["function"]["name"], "参数": tool["function"].get("arguments", {})} - for tool in resp["工具调用"] - ], - } - for resp in results_with_tools["响应结果"] - ], - "差异百分比": float(f"{diff_percent:.2f}"), - } - - with open("llm_tool_benchmark_results.json", "w", encoding="utf-8") as f: - json.dump(results, f, ensure_ascii=False, indent=2) - - print("\n测试结果已保存到 llm_tool_benchmark_results.json") - - -if __name__ == "__main__": - asyncio.run(main()) From 3cfa1e6b17340f82f2937a2243b8e99030196294 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Mon, 28 Apr 2025 14:29:40 +0800 Subject: [PATCH 28/54] =?UTF-8?q?ai=E5=93=A5=E8=AF=B4=E7=9A=84=E5=AF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/conversation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 5bd1f2a2..b06cc6ca 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -188,7 +188,6 @@ class Conversation: goal_ended = False if hasattr(self.conversation_info, "goal_list") and self.conversation_info.goal_list: for goal_item in self.conversation_info.goal_list: - current_goal = None if isinstance(goal_item, dict): current_goal = goal_item.get("goal") From 3b88a35a30827a5028a4d46dea6e5abe88cb28f1 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Mon, 28 Apr 2025 15:36:29 +0800 Subject: [PATCH 29/54] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A7=81=E8=81=8A?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 3 ++- src/plugins/PFC/conversation.py | 11 ++++++----- src/plugins/PFC/pfc.py | 3 ++- src/plugins/PFC/pfc_manager.py | 4 ++-- src/plugins/PFC/reply_generator.py | 3 ++- src/plugins/PFC/waiter.py | 3 ++- src/plugins/chat/bot.py | 3 ++- 7 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 14c25454..238382a3 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -86,7 +86,7 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在 class ActionPlanner: """行动规划器""" - def __init__(self, stream_id: str): + def __init__(self, stream_id: str, private_name: str): self.llm = LLMRequest( model=global_config.llm_PFC_action_planner, temperature=global_config.llm_PFC_action_planner["temp"], @@ -96,6 +96,7 @@ class ActionPlanner: self.personality_info = Individuality.get_instance().get_prompt(type="personality", x_person=2, level=3) self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2) self.name = global_config.BOT_NICKNAME + self.private_name = private_name self.chat_observer = ChatObserver.get_instance(stream_id) # self.action_planner_info = ActionPlannerInfo() # 移除未使用的变量 diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index b06cc6ca..1cd7a6ee 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -29,13 +29,14 @@ logger = get_logger("pfc") class Conversation: """对话类,负责管理单个对话的状态和行为""" - def __init__(self, stream_id: str): + def __init__(self, stream_id: str, private_name: str): """初始化对话实例 Args: stream_id: 聊天流ID """ self.stream_id = stream_id + self.private_name = private_name self.state = ConversationState.INIT self.should_continue = False self.ignore_until_timestamp: Optional[float] = None @@ -47,11 +48,11 @@ class Conversation: """初始化实例,注册所有组件""" try: - self.action_planner = ActionPlanner(self.stream_id) - self.goal_analyzer = GoalAnalyzer(self.stream_id) - self.reply_generator = ReplyGenerator(self.stream_id) + self.action_planner = ActionPlanner(self.stream_id, self.private_name) + self.goal_analyzer = GoalAnalyzer(self.stream_id, self.private_name) + self.reply_generator = ReplyGenerator(self.stream_id, self.private_name) self.knowledge_fetcher = KnowledgeFetcher() - self.waiter = Waiter(self.stream_id) + self.waiter = Waiter(self.stream_id, self.private_name) self.direct_sender = DirectMessageSender() # 获取聊天流信息 diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index af7335d4..7cb609e7 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -30,7 +30,7 @@ logger = get_module_logger("pfc") class GoalAnalyzer: """对话目标分析器""" - def __init__(self, stream_id: str): + def __init__(self, stream_id: str, private_name: str): self.llm = LLMRequest( model=global_config.llm_normal, temperature=0.7, max_tokens=1000, request_type="conversation_goal" ) @@ -39,6 +39,7 @@ class GoalAnalyzer: self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2) self.name = global_config.BOT_NICKNAME self.nick_name = global_config.BOT_ALIAS_NAMES + self.private_name = private_name self.chat_observer = ChatObserver.get_instance(stream_id) # 多目标存储结构 diff --git a/src/plugins/PFC/pfc_manager.py b/src/plugins/PFC/pfc_manager.py index 9aae33f2..6a0c0070 100644 --- a/src/plugins/PFC/pfc_manager.py +++ b/src/plugins/PFC/pfc_manager.py @@ -28,7 +28,7 @@ class PFCManager: cls._instance = PFCManager() return cls._instance - async def get_or_create_conversation(self, stream_id: str) -> Optional[Conversation]: + async def get_or_create_conversation(self, stream_id: str, private_name: str) -> Optional[Conversation]: """获取或创建对话实例 Args: @@ -67,7 +67,7 @@ class PFCManager: logger.info(f"创建新的对话实例: {stream_id}") self._initializing[stream_id] = True # 创建实例 - conversation_instance = Conversation(stream_id) + conversation_instance = Conversation(stream_id, private_name) self._instances[stream_id] = conversation_instance # 启动实例初始化 diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index ade3db10..a86051cf 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -61,7 +61,7 @@ PROMPT_SEND_NEW_MESSAGE = """{persona_text}。现在你在参与一场QQ私聊 class ReplyGenerator: """回复生成器""" - def __init__(self, stream_id: str): + def __init__(self, stream_id: str, private_name: str): self.llm = LLMRequest( model=global_config.llm_PFC_chat, temperature=global_config.llm_PFC_chat["temp"], @@ -71,6 +71,7 @@ class ReplyGenerator: self.personality_info = Individuality.get_instance().get_prompt(type="personality", x_person=2, level=3) self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2) self.name = global_config.BOT_NICKNAME + self.private_name = private_name self.chat_observer = ChatObserver.get_instance(stream_id) self.reply_checker = ReplyChecker(stream_id) diff --git a/src/plugins/PFC/waiter.py b/src/plugins/PFC/waiter.py index c12a1e8b..5d8fdecf 100644 --- a/src/plugins/PFC/waiter.py +++ b/src/plugins/PFC/waiter.py @@ -17,9 +17,10 @@ DESIRED_TIMEOUT_SECONDS = 300 class Waiter: """等待处理类""" - def __init__(self, stream_id: str): + def __init__(self, stream_id: str, private_name: str): self.chat_observer = ChatObserver.get_instance(stream_id) self.name = global_config.BOT_NICKNAME + self.private_name = private_name # self.wait_accumulated_time = 0 # 不再需要累加计时 async def wait(self, conversation_info: ConversationInfo) -> bool: diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index 0ad657f7..ae63d121 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -38,9 +38,10 @@ class ChatBot: async def _create_pfc_chat(self, message: MessageRecv): try: chat_id = str(message.chat_stream.stream_id) + private_name = str(message.message_info.user_info.user_nickname) if global_config.enable_pfc_chatting: - await self.pfc_manager.get_or_create_conversation(chat_id) + await self.pfc_manager.get_or_create_conversation(chat_id, private_name) except Exception as e: logger.error(f"创建PFC聊天失败: {e}") From ef24f013de0a73b6688b1b3ef4e28383022fd10f Mon Sep 17 00:00:00 2001 From: Bakadax Date: Mon, 28 Apr 2025 16:41:39 +0800 Subject: [PATCH 30/54] =?UTF-8?q?=E4=B8=BAPFC=E7=9A=84=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AE=B9=E6=98=93=E5=8C=BA=E5=88=86=E7=9A=84?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=E6=B5=81=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 49 ++++----- src/plugins/PFC/chat_observer.py | 35 +++--- src/plugins/PFC/conversation.py | 136 ++++++++++++------------ src/plugins/PFC/message_sender.py | 5 +- src/plugins/PFC/observation_info.py | 16 +-- src/plugins/PFC/pfc.py | 32 +++--- src/plugins/PFC/pfc_KnowledgeFetcher.py | 9 +- src/plugins/PFC/pfc_manager.py | 21 ++-- src/plugins/PFC/pfc_utils.py | 15 +-- src/plugins/PFC/reply_checker.py | 19 ++-- src/plugins/PFC/reply_generator.py | 16 +-- src/plugins/PFC/waiter.py | 22 ++-- 12 files changed, 193 insertions(+), 182 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 238382a3..161bc0d0 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -97,7 +97,7 @@ 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.private_name = private_name - self.chat_observer = ChatObserver.get_instance(stream_id) + self.chat_observer = ChatObserver.get_instance(stream_id, private_name) # self.action_planner_info = ActionPlannerInfo() # 移除未使用的变量 # 修改 plan 方法签名,增加 last_successful_reply_action 参数 @@ -138,11 +138,11 @@ class ActionPlanner: ) break else: - logger.debug("Observation info chat history is empty or not available for bot time check.") + logger.debug(f"[私聊][{self.private_name}]Observation info chat history is empty or not available for bot time check.") except AttributeError: - logger.warning("ObservationInfo object might not have chat_history attribute yet for bot time check.") + logger.warning(f"[私聊][{self.private_name}]ObservationInfo object might not have chat_history attribute yet for bot time check.") except Exception as e: - logger.warning(f"获取 Bot 上次发言时间时出错: {e}") + logger.warning(f"[私聊][{self.private_name}]获取 Bot 上次发言时间时出错: {e}") # --- 获取超时提示信息 --- # (这部分逻辑不变) @@ -159,14 +159,14 @@ class ActionPlanner: except Exception: timeout_context = "重要提示:对方已经长时间没有回复你的消息了(这可能代表对方繁忙/不想回复/没注意到你的消息等情况,或在对方看来本次聊天已告一段落),请基于此情况规划下一步。\n" else: - logger.debug("Conversation info goal_list is empty or not available for timeout check.") + logger.debug(f"[私聊][{self.private_name}]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(f"[私聊][{self.private_name}]ConversationInfo object might not have goal_list attribute yet for timeout check.") except Exception as e: - logger.warning(f"检查超时目标时出错: {e}") + logger.warning(f"[私聊][{self.private_name}]检查超时目标时出错: {e}") # --- 构建通用 Prompt 参数 --- - logger.debug(f"开始规划行动:当前目标: {getattr(conversation_info, 'goal_list', '不可用')}") + logger.debug(f"[私聊][{self.private_name}]开始规划行动:当前目标: {getattr(conversation_info, 'goal_list', '不可用')}") # 构建对话目标 (goals_str) goals_str = "" @@ -189,10 +189,10 @@ class ActionPlanner: else: goals_str = "- 目前没有明确对话目标,请考虑设定一个。\n" except AttributeError: - logger.warning("ConversationInfo object might not have goal_list attribute yet.") + logger.warning(f"[私聊][{self.private_name}]ConversationInfo object might not have goal_list attribute yet.") goals_str = "- 获取对话目标时出错。\n" except Exception as e: - logger.error(f"构建对话目标字符串时出错: {e}") + logger.error(f"[私聊][{self.private_name}]构建对话目标字符串时出错: {e}") goals_str = "- 构建对话目标时出错。\n" # 获取聊天历史记录 (chat_history_text) @@ -220,13 +220,13 @@ class ActionPlanner: ) else: logger.warning( - "ObservationInfo has new_messages_count > 0 but unprocessed_messages is empty or missing." + f"[私聊][{self.private_name}]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.") + logger.warning(f"[私聊][{self.private_name}]ObservationInfo object might be missing expected attributes for chat history.") chat_history_text = "获取聊天记录时出错。\n" except Exception as e: - logger.error(f"处理聊天记录时发生未知错误: {e}") + logger.error(f"[私聊][{self.private_name}]处理聊天记录时发生未知错误: {e}") chat_history_text = "处理聊天记录时出错。\n" # 构建 Persona 文本 (persona_text) @@ -255,11 +255,11 @@ class ActionPlanner: 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.") + logger.debug(f"[私聊][{self.private_name}]Conversation info done_action is empty or not available.") except AttributeError: - logger.warning("ConversationInfo object might not have done_action attribute yet.") + logger.warning(f"[私聊][{self.private_name}]ConversationInfo object might not have done_action attribute yet.") except Exception as e: - logger.error(f"访问行动历史时出错: {e}") + logger.error(f"[私聊][{self.private_name}]访问行动历史时出错: {e}") if not action_history_list: action_history_summary += "- 还没有执行过行动。\n" @@ -316,10 +316,10 @@ class ActionPlanner: # --- 选择 Prompt --- if last_successful_reply_action in ["direct_reply", "send_new_message"]: prompt_template = PROMPT_FOLLOW_UP - logger.debug("使用 PROMPT_FOLLOW_UP (追问决策)") + logger.debug(f"[私聊][{self.private_name}]使用 PROMPT_FOLLOW_UP (追问决策)") else: prompt_template = PROMPT_INITIAL_REPLY - logger.debug("使用 PROMPT_INITIAL_REPLY (首次/非连续回复决策)") + logger.debug(f"[私聊][{self.private_name}]使用 PROMPT_INITIAL_REPLY (首次/非连续回复决策)") # --- 格式化最终的 Prompt --- prompt = prompt_template.format( @@ -332,13 +332,14 @@ class ActionPlanner: chat_history_text=chat_history_text if chat_history_text.strip() else "还没有聊天记录。", ) - logger.debug(f"发送到LLM的最终提示词:\n------\n{prompt}\n------") + logger.debug(f"[私聊][{self.private_name}]发送到LLM的最终提示词:\n------\n{prompt}\n------") try: content, _ = await self.llm.generate_response_async(prompt) - logger.debug(f"LLM原始返回内容: {content}") + logger.debug(f"[私聊][{self.private_name}]LLM原始返回内容: {content}") success, result = get_items_from_json( content, + self.private_name, "action", "reason", default_values={"action": "wait", "reason": "LLM返回格式错误或未提供原因,默认等待"}, @@ -360,14 +361,14 @@ class ActionPlanner: "block_and_ignore", ] if action not in valid_actions: - logger.warning(f"LLM返回了未知的行动类型: '{action}',强制改为 wait") + logger.warning(f"[私聊][{self.private_name}]LLM返回了未知的行动类型: '{action}',强制改为 wait") reason = f"(原始行动'{action}'无效,已强制改为wait) {reason}" action = "wait" - logger.info(f"规划的行动: {action}") - logger.info(f"行动原因: {reason}") + logger.info(f"[私聊][{self.private_name}]规划的行动: {action}") + logger.info(f"[私聊][{self.private_name}]行动原因: {reason}") return action, reason except Exception as e: - logger.error(f"规划行动时调用 LLM 或处理结果出错: {str(e)}") + logger.error(f"[私聊][{self.private_name}]规划行动时调用 LLM 或处理结果出错: {str(e)}") return "wait", f"行动规划处理中发生错误,暂时等待: {str(e)}" diff --git a/src/plugins/PFC/chat_observer.py b/src/plugins/PFC/chat_observer.py index 91561847..69aede96 100644 --- a/src/plugins/PFC/chat_observer.py +++ b/src/plugins/PFC/chat_observer.py @@ -18,7 +18,7 @@ class ChatObserver: _instances: Dict[str, "ChatObserver"] = {} @classmethod - def get_instance(cls, stream_id: str) -> "ChatObserver": + def get_instance(cls, stream_id: str, private_name: str) -> "ChatObserver": """获取或创建观察器实例 Args: @@ -28,10 +28,10 @@ class ChatObserver: ChatObserver: 观察器实例 """ if stream_id not in cls._instances: - cls._instances[stream_id] = cls(stream_id) + cls._instances[stream_id] = cls(stream_id, private_name) return cls._instances[stream_id] - def __init__(self, stream_id: str): + def __init__(self, stream_id: str, private_name: str): """初始化观察器 Args: @@ -41,6 +41,7 @@ class ChatObserver: raise RuntimeError(f"ChatObserver for {stream_id} already exists. Use get_instance() instead.") self.stream_id = stream_id + self.private_name = private_name self.message_storage = MongoDBMessageStorage() # self.last_user_speak_time: Optional[float] = None # 对方上次发言时间 @@ -76,12 +77,12 @@ class ChatObserver: Returns: bool: 是否有新消息 """ - logger.debug(f"检查距离上一次观察之后是否有了新消息: {self.last_check_time}") + logger.debug(f"[私聊][{self.private_name}]检查距离上一次观察之后是否有了新消息: {self.last_check_time}") new_message_exists = await self.message_storage.has_new_messages(self.stream_id, self.last_check_time) if new_message_exists: - logger.debug("发现新消息") + logger.debug(f"[私聊][{self.private_name}]发现新消息") self.last_check_time = time.time() return new_message_exists @@ -100,7 +101,7 @@ class ChatObserver: # print(self.notification_manager) await self.notification_manager.send_notification(notification) except Exception as e: - logger.error(f"添加消息到历史记录时出错: {e}") + logger.error(f"[私聊][{self.private_name}]添加消息到历史记录时出错: {e}") print(traceback.format_exc()) # 检查并更新冷场状态 @@ -140,11 +141,11 @@ class ChatObserver: """ if self.last_message_time is None: - logger.debug("没有最后消息时间,返回 False") + logger.debug(f"[私聊][{self.private_name}]没有最后消息时间,返回 False") return False has_new = self.last_message_time > time_point - logger.debug(f"判断是否在指定时间点后有新消息: {self.last_message_time} > {time_point} = {has_new}") + logger.debug(f"[私聊][{self.private_name}]判断是否在指定时间点后有新消息: {self.last_message_time} > {time_point} = {has_new}") return has_new def get_message_history( @@ -213,7 +214,7 @@ class ChatObserver: if new_messages: self.last_message_read = new_messages[-1]["message_id"] - logger.debug(f"获取指定时间点111之前的消息: {new_messages}") + logger.debug(f"[私聊][{self.private_name}]获取指定时间点111之前的消息: {new_messages}") return new_messages @@ -226,9 +227,9 @@ class ChatObserver: # messages = await self._fetch_new_messages_before(start_time) # for message in messages: # await self._add_message_to_history(message) - # logger.debug(f"缓冲消息: {messages}") + # logger.debug(f"[私聊][{self.private_name}]缓冲消息: {messages}") # except Exception as e: - # logger.error(f"缓冲消息出错: {e}") + # logger.error(f"[私聊][{self.private_name}]缓冲消息出错: {e}") while self._running: try: @@ -256,8 +257,8 @@ class ChatObserver: self._update_complete.set() except Exception as e: - logger.error(f"更新循环出错: {e}") - logger.error(traceback.format_exc()) + logger.error(f"[私聊][{self.private_name}]更新循环出错: {e}") + logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") self._update_complete.set() # 即使出错也要设置完成事件 def trigger_update(self): @@ -277,7 +278,7 @@ class ChatObserver: await asyncio.wait_for(self._update_complete.wait(), timeout=timeout) return True except asyncio.TimeoutError: - logger.warning(f"等待更新完成超时({timeout}秒)") + logger.warning(f"[私聊][{self.private_name}]等待更新完成超时({timeout}秒)") return False def start(self): @@ -287,7 +288,7 @@ class ChatObserver: self._running = True self._task = asyncio.create_task(self._update_loop()) - logger.debug(f"ChatObserver for {self.stream_id} started") + logger.debug(f"[私聊][{self.private_name}]ChatObserver for {self.stream_id} started") def stop(self): """停止观察器""" @@ -296,7 +297,7 @@ class ChatObserver: self._update_complete.set() # 设置完成事件以解除等待 if self._task: self._task.cancel() - logger.debug(f"ChatObserver for {self.stream_id} stopped") + logger.debug(f"[私聊][{self.private_name}]ChatObserver for {self.stream_id} stopped") async def process_chat_history(self, messages: list): """处理聊天历史 @@ -314,7 +315,7 @@ class ChatObserver: else: self.update_user_speak_time(msg["time"]) except Exception as e: - logger.warning(f"处理消息时间时出错: {e}") + logger.warning(f"[私聊][{self.private_name}]处理消息时间时出错: {e}") continue def update_check_time(self): diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 1cd7a6ee..756e01e5 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -51,35 +51,35 @@ class Conversation: self.action_planner = ActionPlanner(self.stream_id, self.private_name) self.goal_analyzer = GoalAnalyzer(self.stream_id, self.private_name) self.reply_generator = ReplyGenerator(self.stream_id, self.private_name) - self.knowledge_fetcher = KnowledgeFetcher() + self.knowledge_fetcher = KnowledgeFetcher(self.private_name) self.waiter = Waiter(self.stream_id, self.private_name) - self.direct_sender = DirectMessageSender() + self.direct_sender = DirectMessageSender(self.private_name) # 获取聊天流信息 self.chat_stream = chat_manager.get_stream(self.stream_id) self.stop_action_planner = False except Exception as e: - logger.error(f"初始化对话实例:注册运行组件失败: {e}") - logger.error(traceback.format_exc()) + logger.error(f"[私聊][{self.private_name}]初始化对话实例:注册运行组件失败: {e}") + logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") raise try: # 决策所需要的信息,包括自身自信和观察信息两部分 # 注册观察器和观测信息 - self.chat_observer = ChatObserver.get_instance(self.stream_id) + self.chat_observer = ChatObserver.get_instance(self.stream_id, self.private_name) self.chat_observer.start() - self.observation_info = ObservationInfo() - self.observation_info.bind_to_chat_observer(self.chat_observer) + self.observation_info = ObservationInfo(self.private_name) + self.observation_info.bind_to_chat_observer(self.chat_observer, self.private_name) # print(self.chat_observer.get_cached_messages(limit=) self.conversation_info = ConversationInfo() except Exception as e: - logger.error(f"初始化对话实例:注册信息组件失败: {e}") - logger.error(traceback.format_exc()) + logger.error(f"[私聊][{self.private_name}]初始化对话实例:注册信息组件失败: {e}") + logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") raise try: - logger.info(f"为 {self.stream_id} 加载初始聊天记录...") + logger.info(f"[私聊][{self.private_name}]为 {self.stream_id} 加载初始聊天记录...") initial_messages = get_raw_msg_before_timestamp_with_chat( # chat_id=self.stream_id, timestamp=time.time(), @@ -106,17 +106,17 @@ class Conversation: self.observation_info.last_message_content = last_msg.get("processed_plain_text", "") logger.info( - f"成功加载 {len(initial_messages)} 条初始聊天记录。最后一条消息时间: {self.observation_info.last_message_time}" + f"[私聊][{self.private_name}]成功加载 {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 的最后读取记录 else: - logger.info("没有找到初始聊天记录。") + logger.info(f"[私聊][{self.private_name}]没有找到初始聊天记录。") except Exception as load_err: - logger.error(f"加载初始聊天记录时出错: {load_err}") + logger.error(f"[私聊][{self.private_name}]加载初始聊天记录时出错: {load_err}") # 出错也要继续,只是没有历史记录而已 # 组件准备完成,启动该论对话 self.should_continue = True @@ -125,10 +125,10 @@ class Conversation: async def start(self): """开始对话流程""" try: - logger.info("对话系统启动中...") + logger.info(f"[私聊][{self.private_name}]对话系统启动中...") asyncio.create_task(self._plan_and_action_loop()) except Exception as e: - logger.error(f"启动对话系统失败: {e}") + logger.error(f"[私聊][{self.private_name}]启动对话系统失败: {e}") raise async def _plan_and_action_loop(self): @@ -139,7 +139,7 @@ class Conversation: await asyncio.sleep(30) continue elif self.ignore_until_timestamp and time.time() >= self.ignore_until_timestamp: - logger.info(f"忽略时间已到 {self.stream_id},准备结束对话。") + logger.info(f"[私聊][{self.private_name}]忽略时间已到 {self.stream_id},准备结束对话。") self.ignore_until_timestamp = None self.should_continue = False continue @@ -149,7 +149,7 @@ class Conversation: if hasattr(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.") + logger.warning(f"[私聊][{self.private_name}]ObservationInfo missing 'new_messages_count' before planning.") # --- 调用 Action Planner --- # 传递 self.conversation_info.last_successful_reply_action @@ -162,11 +162,11 @@ class Conversation: 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(f"[私聊][{self.private_name}]ObservationInfo missing 'new_messages_count' after planning.") if current_new_message_count > initial_new_message_count + 2: logger.info( - f"规划期间发现新增消息 ({initial_new_message_count} -> {current_new_message_count}),跳过本次行动,重新规划" + f"[私聊][{self.private_name}]规划期间发现新增消息 ({initial_new_message_count} -> {current_new_message_count}),跳过本次行动,重新规划" ) # 如果规划期间有新消息,也应该重置上次回复状态,因为现在要响应新消息了 self.conversation_info.last_successful_reply_action = None @@ -176,12 +176,12 @@ class Conversation: # 包含 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"): - logger.debug(f"准备执行 {action},清理 {initial_new_message_count} 条规划时已知的新消息。") + logger.debug(f"[私聊][{self.private_name}]准备执行 {action},清理 {initial_new_message_count} 条规划时已知的新消息。") await self.observation_info.clear_unprocessed_messages() if hasattr(self.observation_info, "new_messages_count"): self.observation_info.new_messages_count = 0 else: - logger.error("无法清理未处理消息: ObservationInfo 缺少 clear_unprocessed_messages 方法!") + logger.error(f"[私聊][{self.private_name}]无法清理未处理消息: ObservationInfo 缺少 clear_unprocessed_messages 方法!") await self._handle_action(action, reason, self.observation_info, self.conversation_info) @@ -198,34 +198,34 @@ class Conversation: if goal_ended: self.should_continue = False - logger.info("检测到'结束对话'目标,停止循环。") + logger.info(f"[私聊][{self.private_name}]检测到'结束对话'目标,停止循环。") except Exception as loop_err: - logger.error(f"PFC主循环出错: {loop_err}") - logger.error(traceback.format_exc()) + logger.error(f"[私聊][{self.private_name}]PFC主循环出错: {loop_err}") + logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") await asyncio.sleep(1) if self.should_continue: await asyncio.sleep(0.1) - logger.info(f"PFC 循环结束 for stream_id: {self.stream_id}") + logger.info(f"[私聊][{self.private_name}]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' 属性,无法检查新消息。") + logger.warning(f"[私聊][{self.private_name}]ObservationInfo 未初始化或缺少 'new_messages_count' 属性,无法检查新消息。") return False # 或者根据需要抛出错误 if self.observation_info.new_messages_count > 2: logger.info( - f"生成/执行动作期间收到 {self.observation_info.new_messages_count} 条新消息,取消当前动作并重新规划" + f"[私聊][{self.private_name}]生成/执行动作期间收到 {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。") + logger.warning(f"[私聊][{self.private_name}]ConversationInfo 未初始化,无法重置 last_successful_reply_action。") return True return False @@ -254,7 +254,7 @@ class Conversation: detailed_plain_text=msg_dict.get("detailed_plain_text", ""), ) except Exception as e: - logger.warning(f"转换消息时出错: {e}") + logger.warning(f"[私聊][{self.private_name}]转换消息时出错: {e}") # 可以选择返回 None 或重新抛出异常,这里选择重新抛出以指示问题 raise ValueError(f"无法将字典转换为 Message 对象: {e}") from e @@ -263,7 +263,7 @@ class Conversation: ): """处理规划的行动""" - logger.debug(f"执行行动: {action}, 原因: {reason}") + logger.debug(f"[私聊][{self.private_name}]执行行动: {action}, 原因: {reason}") # 记录action历史 (逻辑不变) current_action_record = { @@ -294,14 +294,14 @@ 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"[私聊][{self.private_name}]尝试生成追问回复 (第 {reply_attempt_count}/{max_reply_attempts} 次)...") self.state = ConversationState.GENERATING # 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}") + logger.info(f"[私聊][{self.private_name}]第 {reply_attempt_count} 次生成的追问回复: {self.generated_reply}") # 2. 检查回复 (逻辑不变) self.state = ConversationState.CHECKING @@ -315,18 +315,18 @@ class Conversation: retry_count=reply_attempt_count - 1, ) logger.info( - f"第 {reply_attempt_count} 次追问检查结果: 合适={is_suitable}, 原因='{check_reason}', 需重新规划={need_replan}" + f"[私聊][{self.private_name}]第 {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}" + f"[私聊][{self.private_name}]第 {reply_attempt_count} 次追问检查建议重新规划,停止尝试。原因: {check_reason}" ) break except Exception as check_err: - logger.error(f"第 {reply_attempt_count} 次调用 ReplyChecker (追问) 时出错: {check_err}") + logger.error(f"[私聊][{self.private_name}]第 {reply_attempt_count} 次调用 ReplyChecker (追问) 时出错: {check_err}") check_reason = f"第 {reply_attempt_count} 次检查过程出错: {check_err}" break @@ -334,7 +334,7 @@ class Conversation: if is_suitable: # 检查是否有新消息 if self._check_new_messages_after_planning(): - logger.info("生成追问回复期间收到新消息,取消发送,重新规划行动") + logger.info(f"[私聊][{self.private_name}]生成追问回复期间收到新消息,取消发送,重新规划行动") conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"有新消息,取消发送追问: {final_reply_to_send}"} ) @@ -351,7 +351,7 @@ class Conversation: else: # 追问失败 - logger.warning(f"经过 {reply_attempt_count} 次尝试,未能生成合适的追问回复。最终原因: {check_reason}") + logger.warning(f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,未能生成合适的追问回复。最终原因: {check_reason}") conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"追问尝试{reply_attempt_count}次后失败: {check_reason}"} ) @@ -359,7 +359,7 @@ class Conversation: self.conversation_info.last_successful_reply_action = None # 执行 Wait 操作 - logger.info("由于无法生成合适追问回复,执行 'wait' 操作...") + logger.info(f"[私聊][{self.private_name}]由于无法生成合适追问回复,执行 'wait' 操作...") self.state = ConversationState.WAITING await self.waiter.wait(self.conversation_info) wait_action_record = { @@ -381,14 +381,14 @@ 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"[私聊][{self.private_name}]尝试生成首次回复 (第 {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}") + logger.info(f"[私聊][{self.private_name}]第 {reply_attempt_count} 次生成的首次回复: {self.generated_reply}") # 2. 检查回复 self.state = ConversationState.CHECKING @@ -402,18 +402,18 @@ class Conversation: retry_count=reply_attempt_count - 1, ) logger.info( - f"第 {reply_attempt_count} 次首次回复检查结果: 合适={is_suitable}, 原因='{check_reason}', 需重新规划={need_replan}" + f"[私聊][{self.private_name}]第 {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}" + f"[私聊][{self.private_name}]第 {reply_attempt_count} 次首次回复检查建议重新规划,停止尝试。原因: {check_reason}" ) break except Exception as check_err: - logger.error(f"第 {reply_attempt_count} 次调用 ReplyChecker (首次回复) 时出错: {check_err}") + logger.error(f"[私聊][{self.private_name}]第 {reply_attempt_count} 次调用 ReplyChecker (首次回复) 时出错: {check_err}") check_reason = f"第 {reply_attempt_count} 次检查过程出错: {check_err}" break @@ -421,7 +421,7 @@ class Conversation: if is_suitable: # 检查是否有新消息 if self._check_new_messages_after_planning(): - logger.info("生成首次回复期间收到新消息,取消发送,重新规划行动") + logger.info(f"[私聊][{self.private_name}]生成首次回复期间收到新消息,取消发送,重新规划行动") conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"有新消息,取消发送首次回复: {final_reply_to_send}"} ) @@ -438,7 +438,7 @@ class Conversation: else: # 首次回复失败 - logger.warning(f"经过 {reply_attempt_count} 次尝试,未能生成合适的首次回复。最终原因: {check_reason}") + logger.warning(f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,未能生成合适的首次回复。最终原因: {check_reason}") conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"首次回复尝试{reply_attempt_count}次后失败: {check_reason}"} ) @@ -446,7 +446,7 @@ class Conversation: self.conversation_info.last_successful_reply_action = None # 执行 Wait 操作 (保持原有逻辑) - logger.info("由于无法生成合适首次回复,执行 'wait' 操作...") + logger.info(f"[私聊][{self.private_name}]由于无法生成合适首次回复,执行 'wait' 操作...") self.state = ConversationState.WAITING await self.waiter.wait(self.conversation_info) wait_action_record = { @@ -464,11 +464,11 @@ class Conversation: try: # 检查 knowledge_fetcher 是否存在 if not hasattr(self, "knowledge_fetcher"): - logger.error("KnowledgeFetcher 未初始化,无法获取知识。") + logger.error(f"[私聊][{self.private_name}]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}") + logger.info(f"[私聊][{self.private_name}]获取到知识: {knowledge[:100]}..., 来源: {source}") if knowledge: # 确保 knowledge_list 存在 if not hasattr(conversation_info, "knowledge_list"): @@ -478,7 +478,7 @@ class Conversation: ) action_successful = True except Exception as fetch_err: - logger.error(f"获取知识时出错: {fetch_err}") + logger.error(f"[私聊][{self.private_name}]获取知识时出错: {fetch_err}") conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"获取知识失败: {fetch_err}"} ) @@ -489,12 +489,12 @@ class Conversation: try: # 检查 goal_analyzer 是否存在 if not hasattr(self, "goal_analyzer"): - logger.error("GoalAnalyzer 未初始化,无法重新思考目标。") + logger.error(f"[私聊][{self.private_name}]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}") + logger.error(f"[私聊][{self.private_name}]重新思考目标时出错: {rethink_err}") conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"重新思考目标失败: {rethink_err}"} ) @@ -502,16 +502,16 @@ class Conversation: elif action == "listening": self.state = ConversationState.LISTENING - logger.info("倾听对方发言...") + logger.info(f"[私聊][{self.private_name}]倾听对方发言...") try: # 检查 waiter 是否存在 if not hasattr(self, "waiter"): - logger.error("Waiter 未初始化,无法倾听。") + logger.error(f"[私聊][{self.private_name}]Waiter 未初始化,无法倾听。") raise AttributeError("Waiter not initialized") await self.waiter.wait_listening(conversation_info) action_successful = True # Listening 完成就算成功 except Exception as listen_err: - logger.error(f"倾听时出错: {listen_err}") + logger.error(f"[私聊][{self.private_name}]倾听时出错: {listen_err}") conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"倾听失败: {listen_err}"} ) @@ -519,29 +519,29 @@ class Conversation: elif action == "end_conversation": self.should_continue = False - logger.info("决定结束对话...") + logger.info(f"[私聊][{self.private_name}]决定结束对话...") action_successful = True # 标记动作成功 elif action == "block_and_ignore": - logger.info("不想再理你了...") + logger.info(f"[私聊][{self.private_name}]不想再理你了...") ignore_duration_seconds = 10 * 60 self.ignore_until_timestamp = time.time() + ignore_duration_seconds - logger.info(f"将忽略此对话直到: {datetime.datetime.fromtimestamp(self.ignore_until_timestamp)}") + logger.info(f"[私聊][{self.private_name}]将忽略此对话直到: {datetime.datetime.fromtimestamp(self.ignore_until_timestamp)}") self.state = ConversationState.IGNORED action_successful = True # 标记动作成功 else: # 对应 'wait' 动作 self.state = ConversationState.WAITING - logger.info("等待更多信息...") + logger.info(f"[私聊][{self.private_name}]等待更多信息...") try: # 检查 waiter 是否存在 if not hasattr(self, "waiter"): - logger.error("Waiter 未初始化,无法等待。") + logger.error(f"[私聊][{self.private_name}]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}") + logger.error(f"[私聊][{self.private_name}]等待时出错: {wait_err}") conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"等待失败: {wait_err}"} ) @@ -559,13 +559,13 @@ class Conversation: # 重置状态: 对于非回复类动作的成功,清除上次回复状态 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") + logger.debug(f"[私聊][{self.private_name}]动作 {action} 成功完成,重置 last_successful_reply_action") # 如果动作是 recall 状态,在各自的处理逻辑中已经更新了 done_action async def _send_reply(self): """发送回复""" if not self.generated_reply: - logger.warning("没有生成回复内容,无法发送。") + logger.warning(f"[私聊][{self.private_name}]没有生成回复内容,无法发送。") return try: @@ -574,10 +574,10 @@ class Conversation: # 发送消息 (确保 direct_sender 和 chat_stream 有效) if not hasattr(self, "direct_sender") or not self.direct_sender: - logger.error("DirectMessageSender 未初始化,无法发送回复。") + logger.error(f"[私聊][{self.private_name}]DirectMessageSender 未初始化,无法发送回复。") return if not self.chat_stream: - logger.error("ChatStream 未初始化,无法发送回复。") + logger.error(f"[私聊][{self.private_name}]ChatStream 未初始化,无法发送回复。") return await self.direct_sender.send_message(chat_stream=self.chat_stream, content=reply_content) @@ -587,13 +587,13 @@ class Conversation: # 暂时注释掉,观察是否影响 ObservationInfo 的更新 # self.chat_observer.trigger_update() # if not await self.chat_observer.wait_for_update(): - # logger.warning("等待 ChatObserver 更新完成超时") + # logger.warning(f"[私聊][{self.private_name}]等待 ChatObserver 更新完成超时") self.state = ConversationState.ANALYZING # 更新状态 except Exception as e: - logger.error(f"发送消息或更新状态时失败: {str(e)}") - logger.error(traceback.format_exc()) + logger.error(f"[私聊][{self.private_name}]发送消息或更新状态时失败: {str(e)}") + logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") self.state = ConversationState.ANALYZING async def _send_timeout_message(self): @@ -608,4 +608,4 @@ class Conversation: chat_stream=self.chat_stream, content="TODO:超时消息", reply_to_message=latest_message ) except Exception as e: - logger.error(f"发送超时消息失败: {str(e)}") + logger.error(f"[私聊][{self.private_name}]发送超时消息失败: {str(e)}") diff --git a/src/plugins/PFC/message_sender.py b/src/plugins/PFC/message_sender.py index 8a0f4176..06860af3 100644 --- a/src/plugins/PFC/message_sender.py +++ b/src/plugins/PFC/message_sender.py @@ -19,6 +19,7 @@ class DirectMessageSender: self, chat_stream: ChatStream, content: str, + private_name: str, reply_to_message: Optional[Message] = None, ) -> None: """发送消息到聊天流 @@ -43,8 +44,8 @@ class DirectMessageSender: message_set = MessageSet(chat_stream, message_sending.message_id) message_set.add_message(message_sending) message_manager.add_message(message_set) - logger.info(f"PFC消息已发送: {content}") + logger.info(f"[私聊][{private_name}]PFC消息已发送: {content}") except Exception as e: - logger.error(f"PFC消息发送失败: {str(e)}") + logger.error(f"[私聊][{private_name}]PFC消息发送失败: {str(e)}") raise diff --git a/src/plugins/PFC/observation_info.py b/src/plugins/PFC/observation_info.py index 050d839f..92b09cc0 100644 --- a/src/plugins/PFC/observation_info.py +++ b/src/plugins/PFC/observation_info.py @@ -15,13 +15,14 @@ logger = get_module_logger("observation_info") class ObservationInfoHandler(NotificationHandler): """ObservationInfo的通知处理器""" - def __init__(self, observation_info: "ObservationInfo"): + def __init__(self, observation_info: "ObservationInfo", private_name: str): """初始化处理器 Args: observation_info: 要更新的ObservationInfo实例 """ self.observation_info = observation_info + self.private_name = private_name async def handle_notification(self, notification): # 获取通知类型和数据 @@ -30,7 +31,7 @@ class ObservationInfoHandler(NotificationHandler): if notification_type == NotificationType.NEW_MESSAGE: # 处理新消息通知 - logger.debug(f"收到新消息通知data: {data}") + logger.debug(f"[私聊][{self.private_name}]收到新消息通知data: {data}") message_id = data.get("message_id") processed_plain_text = data.get("processed_plain_text") detailed_plain_text = data.get("detailed_plain_text") @@ -89,7 +90,7 @@ class ObservationInfoHandler(NotificationHandler): elif notification_type == NotificationType.ERROR: # 处理错误通知 error_msg = data.get("error", "") - logger.error(f"收到错误通知: {error_msg}") + logger.error(f"[私聊][{self.private_name}]收到错误通知: {error_msg}") @dataclass @@ -122,12 +123,13 @@ class ObservationInfo: # #spec # meta_plan_trigger: bool = False - def __post_init__(self): + def __post_init__(self, private_name: str = None): """初始化后创建handler""" self.chat_observer = None - self.handler = ObservationInfoHandler(self) + self.handler = ObservationInfoHandler(self, private_name) + self.private_name = private_name - def bind_to_chat_observer(self, chat_observer: ChatObserver): + def bind_to_chat_observer(self, chat_observer: ChatObserver, private_name: str): """绑定到指定的chat_observer Args: @@ -158,7 +160,7 @@ class ObservationInfo: Args: message: 消息数据 """ - # logger.debug(f"更新信息from_message: {message}") + # logger.debug(f"[私聊][{self.private_name}]更新信息from_message: {message}") self.last_message_time = message["time"] self.last_message_id = message["message_id"] diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index 7cb609e7..db10e79b 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -40,7 +40,7 @@ class GoalAnalyzer: self.name = global_config.BOT_NICKNAME self.nick_name = global_config.BOT_ALIAS_NAMES self.private_name = private_name - self.chat_observer = ChatObserver.get_instance(stream_id) + self.chat_observer = ChatObserver.get_instance(stream_id, private_name) # 多目标存储结构 self.goals = [] # 存储多个目标 @@ -135,27 +135,27 @@ class GoalAnalyzer: 输出格式示例: [ - {{ +{{ "goal": "回答用户关于Python编程的具体问题", "reasoning": "用户提出了关于Python的技术问题,需要专业且准确的解答" - }}, - {{ +}}, +{{ "goal": "回答用户关于python安装的具体问题", "reasoning": "用户提出了关于Python的技术问题,需要专业且准确的解答" - }} +}} ]""" - logger.debug(f"发送到LLM的提示词: {prompt}") + logger.debug(f"[私聊][{self.private_name}]发送到LLM的提示词: {prompt}") try: content, _ = await self.llm.generate_response_async(prompt) - logger.debug(f"LLM原始返回内容: {content}") + logger.debug(f"[私聊][{self.private_name}]LLM原始返回内容: {content}") except Exception as e: - logger.error(f"分析对话目标时出错: {str(e)}") + logger.error(f"[私聊][{self.private_name}]分析对话目标时出错: {str(e)}") content = "" # 使用改进后的get_items_from_json函数处理JSON数组 success, result = get_items_from_json( - content, "goal", "reasoning", required_types={"goal": str, "reasoning": str}, allow_array=True + content, self.private_name, "goal", "reasoning", required_types={"goal": str, "reasoning": str}, allow_array=True ) if success: @@ -287,11 +287,12 @@ class GoalAnalyzer: try: content, _ = await self.llm.generate_response_async(prompt) - logger.debug(f"LLM原始返回内容: {content}") + logger.debug(f"[私聊][{self.private_name}]LLM原始返回内容: {content}") # 尝试解析JSON success, result = get_items_from_json( content, + self.private_name, "goal_achieved", "stop_conversation", "reason", @@ -299,7 +300,7 @@ class GoalAnalyzer: ) if not success: - logger.error("无法解析对话分析结果JSON") + logger.error(f"[私聊][{self.private_name}]无法解析对话分析结果JSON") return False, False, "解析结果失败" goal_achieved = result["goal_achieved"] @@ -309,16 +310,17 @@ class GoalAnalyzer: return goal_achieved, stop_conversation, reason except Exception as e: - logger.error(f"分析对话状态时出错: {str(e)}") + logger.error(f"[私聊][{self.private_name}]分析对话状态时出错: {str(e)}") return False, False, f"分析出错: {str(e)}" class DirectMessageSender: """直接发送消息到平台的发送器""" - def __init__(self): + def __init__(self, private_name: str): self.logger = get_module_logger("direct_sender") self.storage = MessageStorage() + self.private_name = private_name async def send_via_ws(self, message: MessageSending) -> None: try: @@ -368,6 +370,6 @@ class DirectMessageSender: try: await self.send_via_ws(message) await self.storage.store_message(message, chat_stream) - logger.success(f"PFC消息已发送: {content}") + logger.success(f"[私聊][{self.private_name}]PFC消息已发送: {content}") except Exception as e: - logger.error(f"PFC消息发送失败: {str(e)}") + logger.error(f"[私聊][{self.private_name}]PFC消息发送失败: {str(e)}") diff --git a/src/plugins/PFC/pfc_KnowledgeFetcher.py b/src/plugins/PFC/pfc_KnowledgeFetcher.py index 958b05bf..099b4979 100644 --- a/src/plugins/PFC/pfc_KnowledgeFetcher.py +++ b/src/plugins/PFC/pfc_KnowledgeFetcher.py @@ -13,13 +13,14 @@ logger = get_module_logger("knowledge_fetcher") class KnowledgeFetcher: """知识调取器""" - def __init__(self): + def __init__(self, private_name: str): self.llm = LLMRequest( model=global_config.llm_normal, temperature=global_config.llm_normal["temp"], max_tokens=1000, request_type="knowledge_fetch", ) + self.private_name = private_name def _lpmm_get_knowledge(self, query: str) -> str: """获取相关知识 @@ -31,13 +32,13 @@ class KnowledgeFetcher: str: 构造好的,带相关度的知识 """ - logger.debug("正在从LPMM知识库中获取知识") + logger.debug(f"[私聊][{self.private_name}]正在从LPMM知识库中获取知识") try: knowledge_info = qa_manager.get_knowledge(query) - logger.debug(f"LPMM知识库查询结果: {knowledge_info:150}") + logger.debug(f"[私聊][{self.private_name}]LPMM知识库查询结果: {knowledge_info:150}") return knowledge_info except Exception as e: - logger.error(f"LPMM知识库搜索工具执行失败: {str(e)}") + logger.error(f"[私聊][{self.private_name}]LPMM知识库搜索工具执行失败: {str(e)}") return "未找到匹配的知识" async def fetch(self, query: str, chat_history: List[Message]) -> Tuple[str, str]: diff --git a/src/plugins/PFC/pfc_manager.py b/src/plugins/PFC/pfc_manager.py index 6a0c0070..621686a9 100644 --- a/src/plugins/PFC/pfc_manager.py +++ b/src/plugins/PFC/pfc_manager.py @@ -39,11 +39,11 @@ class PFCManager: """ # 检查是否已经有实例 if stream_id in self._initializing and self._initializing[stream_id]: - logger.debug(f"会话实例正在初始化中: {stream_id}") + logger.debug(f"[私聊][{private_name}]会话实例正在初始化中: {stream_id}") return None if stream_id in self._instances and self._instances[stream_id].should_continue: - logger.debug(f"使用现有会话实例: {stream_id}") + logger.debug(f"[私聊][{private_name}]使用现有会话实例: {stream_id}") return self._instances[stream_id] if stream_id in self._instances: instance = self._instances[stream_id] @@ -52,19 +52,19 @@ class PFCManager: and instance.ignore_until_timestamp and time.time() < instance.ignore_until_timestamp ): - logger.debug(f"会话实例当前处于忽略状态: {stream_id}") + logger.debug(f"[私聊][{private_name}]会话实例当前处于忽略状态: {stream_id}") # 返回 None 阻止交互。或者可以返回实例但标记它被忽略了喵? # 还是返回 None 吧喵。 return None # 检查 should_continue 状态 if instance.should_continue: - logger.debug(f"使用现有会话实例: {stream_id}") + logger.debug(f"[私聊][{private_name}]使用现有会话实例: {stream_id}") return instance # else: 实例存在但不应继续 try: # 创建新实例 - logger.info(f"创建新的对话实例: {stream_id}") + logger.info(f"[私聊][{private_name}]创建新的对话实例: {stream_id}") self._initializing[stream_id] = True # 创建实例 conversation_instance = Conversation(stream_id, private_name) @@ -73,7 +73,7 @@ class PFCManager: # 启动实例初始化 await self._initialize_conversation(conversation_instance) except Exception as e: - logger.error(f"创建会话实例失败: {stream_id}, 错误: {e}") + logger.error(f"[私聊][{private_name}]创建会话实例失败: {stream_id}, 错误: {e}") return None return conversation_instance @@ -85,20 +85,21 @@ class PFCManager: conversation: 要初始化的会话实例 """ stream_id = conversation.stream_id + private_name = conversation.private_name try: - logger.info(f"开始初始化会话实例: {stream_id}") + logger.info(f"[私聊][{private_name}]开始初始化会话实例: {stream_id}") # 启动初始化流程 await conversation._initialize() # 标记初始化完成 self._initializing[stream_id] = False - logger.info(f"会话实例 {stream_id} 初始化完成") + logger.info(f"[私聊][{private_name}]会话实例 {stream_id} 初始化完成") except Exception as e: - logger.error(f"管理器初始化会话实例失败: {stream_id}, 错误: {e}") - logger.error(traceback.format_exc()) + logger.error(f"[私聊][{private_name}]管理器初始化会话实例失败: {stream_id}, 错误: {e}") + logger.error(f"[私聊][{private_name}]{traceback.format_exc()}") # 清理失败的初始化 async def get_conversation(self, stream_id: str) -> Optional[Conversation]: diff --git a/src/plugins/PFC/pfc_utils.py b/src/plugins/PFC/pfc_utils.py index eae36e12..5e35d47b 100644 --- a/src/plugins/PFC/pfc_utils.py +++ b/src/plugins/PFC/pfc_utils.py @@ -8,6 +8,7 @@ logger = get_module_logger("pfc_utils") def get_items_from_json( content: str, + private_name: str, *items: str, default_values: Optional[Dict[str, Any]] = None, required_types: Optional[Dict[str, type]] = None, @@ -78,9 +79,9 @@ def get_items_from_json( if valid_items: return True, valid_items except json.JSONDecodeError: - logger.debug("JSON数组解析失败,尝试解析单个JSON对象") + logger.debug(f"[私聊][{private_name}]JSON数组解析失败,尝试解析单个JSON对象") except Exception as e: - logger.debug(f"尝试解析JSON数组时出错: {str(e)}") + logger.debug(f"[私聊][{private_name}]尝试解析JSON数组时出错: {str(e)}") # 尝试解析JSON对象 try: @@ -93,10 +94,10 @@ def get_items_from_json( try: json_data = json.loads(json_match.group()) except json.JSONDecodeError: - logger.error("提取的JSON内容解析失败") + logger.error(f"[私聊][{private_name}]提取的JSON内容解析失败") return False, result else: - logger.error("无法在返回内容中找到有效的JSON") + logger.error(f"[私聊][{private_name}]无法在返回内容中找到有效的JSON") return False, result # 提取字段 @@ -106,20 +107,20 @@ def get_items_from_json( # 验证必需字段 if not all(item in result for item in items): - logger.error(f"JSON缺少必要字段,实际内容: {json_data}") + logger.error(f"[私聊][{private_name}]JSON缺少必要字段,实际内容: {json_data}") return False, result # 验证字段类型 if required_types: for field, expected_type in required_types.items(): if field in result and not isinstance(result[field], expected_type): - logger.error(f"{field} 必须是 {expected_type.__name__} 类型") + logger.error(f"[私聊][{private_name}]{field} 必须是 {expected_type.__name__} 类型") return False, result # 验证字符串字段不为空 for field in items: if isinstance(result[field], str) and not result[field].strip(): - logger.error(f"{field} 不能为空") + logger.error(f"[私聊][{private_name}]{field} 不能为空") return False, result return True, result diff --git a/src/plugins/PFC/reply_checker.py b/src/plugins/PFC/reply_checker.py index 26b20875..590421aa 100644 --- a/src/plugins/PFC/reply_checker.py +++ b/src/plugins/PFC/reply_checker.py @@ -12,12 +12,13 @@ logger = get_module_logger("reply_checker") class ReplyChecker: """回复检查器""" - def __init__(self, stream_id: str): + def __init__(self, stream_id: str, private_name: str): self.llm = LLMRequest( model=global_config.llm_PFC_reply_checker, temperature=0.50, max_tokens=1000, request_type="reply_check" ) self.name = global_config.BOT_NICKNAME - self.chat_observer = ChatObserver.get_instance(stream_id) + self.private_name = private_name + self.chat_observer = ChatObserver.get_instance(stream_id, private_name) self.max_retries = 3 # 最大重试次数 async def check( @@ -49,7 +50,7 @@ class ReplyChecker: # 可以用简单比较,或者更复杂的相似度库 (如 difflib) # 简单比较:是否完全相同 if reply == bot_messages[0]: # 和最近一条完全一样 - logger.warning(f"ReplyChecker 检测到回复与上一条 Bot 消息完全相同: '{reply}'") + logger.warning(f"[私聊][{self.private_name}]ReplyChecker 检测到回复与上一条 Bot 消息完全相同: '{reply}'") return ( False, "回复内容与你上一条发言完全相同,请修改,可以选择深入话题或寻找其它话题或等待", @@ -60,13 +61,13 @@ class ReplyChecker: # 计算编辑距离相似度,ratio() 返回 0 到 1 之间的浮点数 similarity_ratio = difflib.SequenceMatcher(None, reply, bot_messages[0]).ratio() - logger.debug(f"ReplyChecker - 相似度: {similarity_ratio:.2f}") + logger.debug(f"[私聊][{self.private_name}]ReplyChecker - 相似度: {similarity_ratio:.2f}") # 设置一个相似度阈值 similarity_threshold = 0.9 if similarity_ratio > similarity_threshold: logger.warning( - f"ReplyChecker 检测到回复与上一条 Bot 消息高度相似 (相似度 {similarity_ratio:.2f}): '{reply}'" + f"[私聊][{self.private_name}]ReplyChecker 检测到回复与上一条 Bot 消息高度相似 (相似度 {similarity_ratio:.2f}): '{reply}'" ) return ( False, @@ -77,8 +78,8 @@ class ReplyChecker: except Exception as e: import traceback - logger.error(f"检查回复时出错: 类型={type(e)}, 值={e}") - logger.error(traceback.format_exc()) # 打印详细的回溯信息 + logger.error(f"[私聊][{self.private_name}]检查回复时出错: 类型={type(e)}, 值={e}") + logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") # 打印详细的回溯信息 prompt = f"""请检查以下回复或消息是否合适: @@ -118,7 +119,7 @@ class ReplyChecker: try: content, _ = await self.llm.generate_response_async(prompt) - logger.debug(f"检查回复的原始返回: {content}") + logger.debug(f"[私聊][{self.private_name}]检查回复的原始返回: {content}") # 清理内容,尝试提取JSON部分 content = content.strip() @@ -171,7 +172,7 @@ class ReplyChecker: return suitable, reason, need_replan except Exception as e: - logger.error(f"检查回复时出错: {e}") + logger.error(f"[私聊][{self.private_name}]检查回复时出错: {e}") # 如果出错且已达到最大重试次数,建议重新规划 if retry_count >= self.max_retries: return False, "多次检查失败,建议重新规划", True diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index a86051cf..fa4fdc42 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -72,8 +72,8 @@ class ReplyGenerator: self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2) self.name = global_config.BOT_NICKNAME self.private_name = private_name - self.chat_observer = ChatObserver.get_instance(stream_id) - self.reply_checker = ReplyChecker(stream_id) + self.chat_observer = ChatObserver.get_instance(stream_id, private_name) + self.reply_checker = ReplyChecker(stream_id, private_name) # 修改 generate 方法签名,增加 action_type 参数 async def generate( @@ -90,7 +90,7 @@ class ReplyGenerator: str: 生成的回复 """ # 构建提示词 - logger.debug(f"开始生成回复 (动作类型: {action_type}):当前目标: {conversation_info.goal_list}") + logger.debug(f"[私聊][{self.private_name}]开始生成回复 (动作类型: {action_type}):当前目标: {conversation_info.goal_list}") # --- 构建通用 Prompt 参数 --- # (这部分逻辑基本不变) @@ -146,10 +146,10 @@ class ReplyGenerator: # --- 选择 Prompt --- if action_type == "send_new_message": prompt_template = PROMPT_SEND_NEW_MESSAGE - logger.info("使用 PROMPT_SEND_NEW_MESSAGE (追问生成)") + logger.info(f"[私聊][{self.private_name}]使用 PROMPT_SEND_NEW_MESSAGE (追问生成)") else: # 默认使用 direct_reply 的 prompt prompt_template = PROMPT_DIRECT_REPLY - logger.info("使用 PROMPT_DIRECT_REPLY (首次/非连续回复生成)") + logger.info(f"[私聊][{self.private_name}]使用 PROMPT_DIRECT_REPLY (首次/非连续回复生成)") # --- 格式化最终的 Prompt --- prompt = prompt_template.format( @@ -157,15 +157,15 @@ class ReplyGenerator: ) # --- 调用 LLM 生成 --- - logger.debug(f"发送到LLM的生成提示词:\n------\n{prompt}\n------") + logger.debug(f"[私聊][{self.private_name}]发送到LLM的生成提示词:\n------\n{prompt}\n------") try: content, _ = await self.llm.generate_response_async(prompt) - logger.debug(f"生成的回复: {content}") + logger.debug(f"[私聊][{self.private_name}]生成的回复: {content}") # 移除旧的检查新消息逻辑,这应该由 conversation 控制流处理 return content except Exception as e: - logger.error(f"生成回复时出错: {e}") + logger.error(f"[私聊][{self.private_name}]生成回复时出错: {e}") return "抱歉,我现在有点混乱,让我重新思考一下..." # check_reply 方法保持不变 diff --git a/src/plugins/PFC/waiter.py b/src/plugins/PFC/waiter.py index 5d8fdecf..742c70a9 100644 --- a/src/plugins/PFC/waiter.py +++ b/src/plugins/PFC/waiter.py @@ -18,7 +18,7 @@ class Waiter: """等待处理类""" def __init__(self, stream_id: str, private_name: str): - self.chat_observer = ChatObserver.get_instance(stream_id) + self.chat_observer = ChatObserver.get_instance(stream_id, private_name) self.name = global_config.BOT_NICKNAME self.private_name = private_name # self.wait_accumulated_time = 0 # 不再需要累加计时 @@ -26,52 +26,52 @@ class Waiter: async def wait(self, conversation_info: ConversationInfo) -> bool: """等待用户新消息或超时""" wait_start_time = time.time() - logger.info(f"进入常规等待状态 (超时: {DESIRED_TIMEOUT_SECONDS} 秒)...") + logger.info(f"[私聊][{self.private_name}]进入常规等待状态 (超时: {DESIRED_TIMEOUT_SECONDS} 秒)...") while True: # 检查是否有新消息 if self.chat_observer.new_message_after(wait_start_time): - logger.info("等待结束,收到新消息") + logger.info(f"[私聊][{self.private_name}]等待结束,收到新消息") return False # 返回 False 表示不是超时 # 检查是否超时 elapsed_time = time.time() - wait_start_time if elapsed_time > DESIRED_TIMEOUT_SECONDS: - logger.info(f"等待超过 {DESIRED_TIMEOUT_SECONDS} 秒...添加思考目标。") + logger.info(f"[私聊][{self.private_name}]等待超过 {DESIRED_TIMEOUT_SECONDS} 秒...添加思考目标。") wait_goal = { "goal": f"你等待了{elapsed_time / 60:.1f}分钟,注意可能在对方看来聊天已经结束,思考接下来要做什么", "reasoning": "对方很久没有回复你的消息了", } conversation_info.goal_list.append(wait_goal) - logger.info(f"添加目标: {wait_goal}") + logger.info(f"[私聊][{self.private_name}]添加目标: {wait_goal}") return True # 返回 True 表示超时 await asyncio.sleep(5) # 每 5 秒检查一次 - logger.debug("等待中...") # 可以考虑把这个频繁日志注释掉,只在超时或收到消息时输出 + logger.debug(f"[私聊][{self.private_name}]等待中...") # 可以考虑把这个频繁日志注释掉,只在超时或收到消息时输出 async def wait_listening(self, conversation_info: ConversationInfo) -> bool: """倾听用户发言或超时""" wait_start_time = time.time() - logger.info(f"进入倾听等待状态 (超时: {DESIRED_TIMEOUT_SECONDS} 秒)...") + logger.info(f"[私聊][{self.private_name}]进入倾听等待状态 (超时: {DESIRED_TIMEOUT_SECONDS} 秒)...") while True: # 检查是否有新消息 if self.chat_observer.new_message_after(wait_start_time): - logger.info("倾听等待结束,收到新消息") + logger.info(f"[私聊][{self.private_name}]倾听等待结束,收到新消息") return False # 返回 False 表示不是超时 # 检查是否超时 elapsed_time = time.time() - wait_start_time if elapsed_time > DESIRED_TIMEOUT_SECONDS: - logger.info(f"倾听等待超过 {DESIRED_TIMEOUT_SECONDS} 秒...添加思考目标。") + logger.info(f"[私聊][{self.private_name}]倾听等待超过 {DESIRED_TIMEOUT_SECONDS} 秒...添加思考目标。") wait_goal = { # 保持 goal 文本一致 "goal": f"你等待了{elapsed_time / 60:.1f}分钟,对方似乎话说一半突然消失了,可能忙去了?也可能忘记了回复?要问问吗?还是结束对话?或继续等待?思考接下来要做什么", "reasoning": "对方话说一半消失了,很久没有回复", } conversation_info.goal_list.append(wait_goal) - logger.info(f"添加目标: {wait_goal}") + logger.info(f"[私聊][{self.private_name}]添加目标: {wait_goal}") return True # 返回 True 表示超时 await asyncio.sleep(5) # 每 5 秒检查一次 - logger.debug("倾听等待中...") # 同上,可以考虑注释掉 + logger.debug(f"[私聊][{self.private_name}]倾听等待中...") # 同上,可以考虑注释掉 From 93f2ceb9de96c9a20425c186f89234ebd4279508 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 28 Apr 2025 10:30:11 +0000 Subject: [PATCH 31/54] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 32 ++++++++++++---- src/plugins/PFC/chat_observer.py | 4 +- src/plugins/PFC/conversation.py | 60 ++++++++++++++++++++++-------- src/plugins/PFC/pfc.py | 7 +++- src/plugins/PFC/reply_checker.py | 4 +- src/plugins/PFC/reply_generator.py | 4 +- src/plugins/PFC/waiter.py | 4 +- 7 files changed, 87 insertions(+), 28 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 161bc0d0..3765a7d6 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -138,9 +138,13 @@ class ActionPlanner: ) break else: - logger.debug(f"[私聊][{self.private_name}]Observation info chat history is empty or not available for bot time check.") + logger.debug( + f"[私聊][{self.private_name}]Observation info chat history is empty or not available for bot time check." + ) except AttributeError: - logger.warning(f"[私聊][{self.private_name}]ObservationInfo object might not have chat_history attribute yet for bot time check.") + logger.warning( + f"[私聊][{self.private_name}]ObservationInfo object might not have chat_history attribute yet for bot time check." + ) except Exception as e: logger.warning(f"[私聊][{self.private_name}]获取 Bot 上次发言时间时出错: {e}") @@ -159,14 +163,20 @@ class ActionPlanner: except Exception: timeout_context = "重要提示:对方已经长时间没有回复你的消息了(这可能代表对方繁忙/不想回复/没注意到你的消息等情况,或在对方看来本次聊天已告一段落),请基于此情况规划下一步。\n" else: - logger.debug(f"[私聊][{self.private_name}]Conversation info goal_list is empty or not available for timeout check.") + logger.debug( + f"[私聊][{self.private_name}]Conversation info goal_list is empty or not available for timeout check." + ) except AttributeError: - logger.warning(f"[私聊][{self.private_name}]ConversationInfo object might not have goal_list attribute yet for timeout check.") + logger.warning( + f"[私聊][{self.private_name}]ConversationInfo object might not have goal_list attribute yet for timeout check." + ) except Exception as e: logger.warning(f"[私聊][{self.private_name}]检查超时目标时出错: {e}") # --- 构建通用 Prompt 参数 --- - logger.debug(f"[私聊][{self.private_name}]开始规划行动:当前目标: {getattr(conversation_info, 'goal_list', '不可用')}") + logger.debug( + f"[私聊][{self.private_name}]开始规划行动:当前目标: {getattr(conversation_info, 'goal_list', '不可用')}" + ) # 构建对话目标 (goals_str) goals_str = "" @@ -189,7 +199,9 @@ class ActionPlanner: else: goals_str = "- 目前没有明确对话目标,请考虑设定一个。\n" except AttributeError: - logger.warning(f"[私聊][{self.private_name}]ConversationInfo object might not have goal_list attribute yet.") + logger.warning( + f"[私聊][{self.private_name}]ConversationInfo object might not have goal_list attribute yet." + ) goals_str = "- 获取对话目标时出错。\n" except Exception as e: logger.error(f"[私聊][{self.private_name}]构建对话目标字符串时出错: {e}") @@ -223,7 +235,9 @@ class ActionPlanner: f"[私聊][{self.private_name}]ObservationInfo has new_messages_count > 0 but unprocessed_messages is empty or missing." ) except AttributeError: - logger.warning(f"[私聊][{self.private_name}]ObservationInfo object might be missing expected attributes for chat history.") + logger.warning( + f"[私聊][{self.private_name}]ObservationInfo object might be missing expected attributes for chat history." + ) chat_history_text = "获取聊天记录时出错。\n" except Exception as e: logger.error(f"[私聊][{self.private_name}]处理聊天记录时发生未知错误: {e}") @@ -257,7 +271,9 @@ class ActionPlanner: else: logger.debug(f"[私聊][{self.private_name}]Conversation info done_action is empty or not available.") except AttributeError: - logger.warning(f"[私聊][{self.private_name}]ConversationInfo object might not have done_action attribute yet.") + logger.warning( + f"[私聊][{self.private_name}]ConversationInfo object might not have done_action attribute yet." + ) except Exception as e: logger.error(f"[私聊][{self.private_name}]访问行动历史时出错: {e}") diff --git a/src/plugins/PFC/chat_observer.py b/src/plugins/PFC/chat_observer.py index 69aede96..102c9502 100644 --- a/src/plugins/PFC/chat_observer.py +++ b/src/plugins/PFC/chat_observer.py @@ -145,7 +145,9 @@ class ChatObserver: return False has_new = self.last_message_time > time_point - logger.debug(f"[私聊][{self.private_name}]判断是否在指定时间点后有新消息: {self.last_message_time} > {time_point} = {has_new}") + logger.debug( + f"[私聊][{self.private_name}]判断是否在指定时间点后有新消息: {self.last_message_time} > {time_point} = {has_new}" + ) return has_new def get_message_history( diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 756e01e5..238acfd2 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -149,7 +149,9 @@ class Conversation: if hasattr(self.observation_info, "new_messages_count"): initial_new_message_count = self.observation_info.new_messages_count + 1 # 算上麦麦自己发的那一条 else: - logger.warning(f"[私聊][{self.private_name}]ObservationInfo missing 'new_messages_count' before planning.") + logger.warning( + f"[私聊][{self.private_name}]ObservationInfo missing 'new_messages_count' before planning." + ) # --- 调用 Action Planner --- # 传递 self.conversation_info.last_successful_reply_action @@ -162,7 +164,9 @@ class Conversation: if hasattr(self.observation_info, "new_messages_count"): current_new_message_count = self.observation_info.new_messages_count else: - logger.warning(f"[私聊][{self.private_name}]ObservationInfo missing 'new_messages_count' after planning.") + logger.warning( + f"[私聊][{self.private_name}]ObservationInfo missing 'new_messages_count' after planning." + ) if current_new_message_count > initial_new_message_count + 2: logger.info( @@ -176,12 +180,16 @@ class Conversation: # 包含 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"): - logger.debug(f"[私聊][{self.private_name}]准备执行 {action},清理 {initial_new_message_count} 条规划时已知的新消息。") + logger.debug( + f"[私聊][{self.private_name}]准备执行 {action},清理 {initial_new_message_count} 条规划时已知的新消息。" + ) await self.observation_info.clear_unprocessed_messages() if hasattr(self.observation_info, "new_messages_count"): self.observation_info.new_messages_count = 0 else: - logger.error(f"[私聊][{self.private_name}]无法清理未处理消息: ObservationInfo 缺少 clear_unprocessed_messages 方法!") + logger.error( + f"[私聊][{self.private_name}]无法清理未处理消息: ObservationInfo 缺少 clear_unprocessed_messages 方法!" + ) await self._handle_action(action, reason, self.observation_info, self.conversation_info) @@ -214,7 +222,9 @@ class Conversation: """检查在规划后是否有新消息""" # 检查 ObservationInfo 是否已初始化并且有 new_messages_count 属性 if not hasattr(self, "observation_info") or not hasattr(self.observation_info, "new_messages_count"): - logger.warning(f"[私聊][{self.private_name}]ObservationInfo 未初始化或缺少 'new_messages_count' 属性,无法检查新消息。") + logger.warning( + f"[私聊][{self.private_name}]ObservationInfo 未初始化或缺少 'new_messages_count' 属性,无法检查新消息。" + ) return False # 或者根据需要抛出错误 if self.observation_info.new_messages_count > 2: @@ -225,7 +235,9 @@ class Conversation: if hasattr(self, "conversation_info"): # 确保 conversation_info 已初始化 self.conversation_info.last_successful_reply_action = None else: - logger.warning(f"[私聊][{self.private_name}]ConversationInfo 未初始化,无法重置 last_successful_reply_action。") + logger.warning( + f"[私聊][{self.private_name}]ConversationInfo 未初始化,无法重置 last_successful_reply_action。" + ) return True return False @@ -294,14 +306,18 @@ class Conversation: while reply_attempt_count < max_reply_attempts and not is_suitable: reply_attempt_count += 1 - logger.info(f"[私聊][{self.private_name}]尝试生成追问回复 (第 {reply_attempt_count}/{max_reply_attempts} 次)...") + logger.info( + f"[私聊][{self.private_name}]尝试生成追问回复 (第 {reply_attempt_count}/{max_reply_attempts} 次)..." + ) self.state = ConversationState.GENERATING # 1. 生成回复 (调用 generate 时传入 action_type) self.generated_reply = await self.reply_generator.generate( observation_info, conversation_info, action_type="send_new_message" ) - logger.info(f"[私聊][{self.private_name}]第 {reply_attempt_count} 次生成的追问回复: {self.generated_reply}") + logger.info( + f"[私聊][{self.private_name}]第 {reply_attempt_count} 次生成的追问回复: {self.generated_reply}" + ) # 2. 检查回复 (逻辑不变) self.state = ConversationState.CHECKING @@ -326,7 +342,9 @@ class Conversation: ) break except Exception as check_err: - logger.error(f"[私聊][{self.private_name}]第 {reply_attempt_count} 次调用 ReplyChecker (追问) 时出错: {check_err}") + logger.error( + f"[私聊][{self.private_name}]第 {reply_attempt_count} 次调用 ReplyChecker (追问) 时出错: {check_err}" + ) check_reason = f"第 {reply_attempt_count} 次检查过程出错: {check_err}" break @@ -351,7 +369,9 @@ class Conversation: else: # 追问失败 - logger.warning(f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,未能生成合适的追问回复。最终原因: {check_reason}") + logger.warning( + f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,未能生成合适的追问回复。最终原因: {check_reason}" + ) conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"追问尝试{reply_attempt_count}次后失败: {check_reason}"} ) @@ -381,14 +401,18 @@ class Conversation: while reply_attempt_count < max_reply_attempts and not is_suitable: reply_attempt_count += 1 - logger.info(f"[私聊][{self.private_name}]尝试生成首次回复 (第 {reply_attempt_count}/{max_reply_attempts} 次)...") + logger.info( + f"[私聊][{self.private_name}]尝试生成首次回复 (第 {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"[私聊][{self.private_name}]第 {reply_attempt_count} 次生成的首次回复: {self.generated_reply}") + logger.info( + f"[私聊][{self.private_name}]第 {reply_attempt_count} 次生成的首次回复: {self.generated_reply}" + ) # 2. 检查回复 self.state = ConversationState.CHECKING @@ -413,7 +437,9 @@ class Conversation: ) break except Exception as check_err: - logger.error(f"[私聊][{self.private_name}]第 {reply_attempt_count} 次调用 ReplyChecker (首次回复) 时出错: {check_err}") + logger.error( + f"[私聊][{self.private_name}]第 {reply_attempt_count} 次调用 ReplyChecker (首次回复) 时出错: {check_err}" + ) check_reason = f"第 {reply_attempt_count} 次检查过程出错: {check_err}" break @@ -438,7 +464,9 @@ class Conversation: else: # 首次回复失败 - logger.warning(f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,未能生成合适的首次回复。最终原因: {check_reason}") + logger.warning( + f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,未能生成合适的首次回复。最终原因: {check_reason}" + ) conversation_info.done_action[action_index].update( {"status": "recall", "final_reason": f"首次回复尝试{reply_attempt_count}次后失败: {check_reason}"} ) @@ -526,7 +554,9 @@ class Conversation: logger.info(f"[私聊][{self.private_name}]不想再理你了...") ignore_duration_seconds = 10 * 60 self.ignore_until_timestamp = time.time() + ignore_duration_seconds - logger.info(f"[私聊][{self.private_name}]将忽略此对话直到: {datetime.datetime.fromtimestamp(self.ignore_until_timestamp)}") + logger.info( + f"[私聊][{self.private_name}]将忽略此对话直到: {datetime.datetime.fromtimestamp(self.ignore_until_timestamp)}" + ) self.state = ConversationState.IGNORED action_successful = True # 标记动作成功 diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index db10e79b..b7570f30 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -155,7 +155,12 @@ class GoalAnalyzer: # 使用改进后的get_items_from_json函数处理JSON数组 success, result = get_items_from_json( - content, self.private_name, "goal", "reasoning", required_types={"goal": str, "reasoning": str}, allow_array=True + content, + self.private_name, + "goal", + "reasoning", + required_types={"goal": str, "reasoning": str}, + allow_array=True, ) if success: diff --git a/src/plugins/PFC/reply_checker.py b/src/plugins/PFC/reply_checker.py index 590421aa..17850325 100644 --- a/src/plugins/PFC/reply_checker.py +++ b/src/plugins/PFC/reply_checker.py @@ -50,7 +50,9 @@ class ReplyChecker: # 可以用简单比较,或者更复杂的相似度库 (如 difflib) # 简单比较:是否完全相同 if reply == bot_messages[0]: # 和最近一条完全一样 - logger.warning(f"[私聊][{self.private_name}]ReplyChecker 检测到回复与上一条 Bot 消息完全相同: '{reply}'") + logger.warning( + f"[私聊][{self.private_name}]ReplyChecker 检测到回复与上一条 Bot 消息完全相同: '{reply}'" + ) return ( False, "回复内容与你上一条发言完全相同,请修改,可以选择深入话题或寻找其它话题或等待", diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index fa4fdc42..dea42b04 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -90,7 +90,9 @@ class ReplyGenerator: str: 生成的回复 """ # 构建提示词 - logger.debug(f"[私聊][{self.private_name}]开始生成回复 (动作类型: {action_type}):当前目标: {conversation_info.goal_list}") + logger.debug( + f"[私聊][{self.private_name}]开始生成回复 (动作类型: {action_type}):当前目标: {conversation_info.goal_list}" + ) # --- 构建通用 Prompt 参数 --- # (这部分逻辑基本不变) diff --git a/src/plugins/PFC/waiter.py b/src/plugins/PFC/waiter.py index 742c70a9..0f5881fc 100644 --- a/src/plugins/PFC/waiter.py +++ b/src/plugins/PFC/waiter.py @@ -47,7 +47,9 @@ class Waiter: return True # 返回 True 表示超时 await asyncio.sleep(5) # 每 5 秒检查一次 - logger.debug(f"[私聊][{self.private_name}]等待中...") # 可以考虑把这个频繁日志注释掉,只在超时或收到消息时输出 + logger.debug( + f"[私聊][{self.private_name}]等待中..." + ) # 可以考虑把这个频繁日志注释掉,只在超时或收到消息时输出 async def wait_listening(self, conversation_info: ConversationInfo) -> bool: """倾听用户发言或超时""" From d07e2669c8fce76ab881eed34743c4fc767feb20 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Mon, 28 Apr 2025 18:46:29 +0800 Subject: [PATCH 32/54] =?UTF-8?q?ai=E5=93=A5=E8=AF=B4=E7=9A=84=E5=AF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/conversation.py | 2 +- src/plugins/PFC/observation_info.py | 367 ++++++++++++++++++---------- 2 files changed, 244 insertions(+), 125 deletions(-) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 756e01e5..d7ba00c8 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -70,7 +70,7 @@ class Conversation: self.chat_observer = ChatObserver.get_instance(self.stream_id, self.private_name) self.chat_observer.start() self.observation_info = ObservationInfo(self.private_name) - self.observation_info.bind_to_chat_observer(self.chat_observer, self.private_name) + self.observation_info.bind_to_chat_observer(self.chat_observer) # print(self.chat_observer.get_cached_messages(limit=) self.conversation_info = ConversationInfo() diff --git a/src/plugins/PFC/observation_info.py b/src/plugins/PFC/observation_info.py index 92b09cc0..29cc870f 100644 --- a/src/plugins/PFC/observation_info.py +++ b/src/plugins/PFC/observation_info.py @@ -1,13 +1,12 @@ -# Programmable Friendly Conversationalist -# Prefrontal cortex from typing import List, Optional, Dict, Any, Set from maim_message import UserInfo import time from dataclasses import dataclass, field from src.common.logger import get_module_logger from .chat_observer import ChatObserver -from .chat_states import NotificationHandler, NotificationType +from .chat_states import NotificationHandler, NotificationType, Notification from src.plugins.utils.chat_message_builder import build_readable_messages +import traceback # 导入 traceback 用于调试 logger = get_module_logger("observation_info") @@ -20,183 +19,283 @@ class ObservationInfoHandler(NotificationHandler): Args: observation_info: 要更新的ObservationInfo实例 + private_name: 私聊对象的名称,用于日志记录 """ self.observation_info = observation_info + # 将 private_name 存储在 handler 实例中 self.private_name = private_name - async def handle_notification(self, notification): + async def handle_notification(self, notification: Notification): # 添加类型提示 # 获取通知类型和数据 notification_type = notification.type data = notification.data - if notification_type == NotificationType.NEW_MESSAGE: - # 处理新消息通知 - logger.debug(f"[私聊][{self.private_name}]收到新消息通知data: {data}") - message_id = data.get("message_id") - processed_plain_text = data.get("processed_plain_text") - detailed_plain_text = data.get("detailed_plain_text") - user_info = data.get("user_info") - time_value = data.get("time") + try: # 添加错误处理块 + if notification_type == NotificationType.NEW_MESSAGE: + # 处理新消息通知 + # logger.debug(f"[私聊][{self.private_name}]收到新消息通知data: {data}") # 可以在需要时取消注释 + message_id = data.get("message_id") + processed_plain_text = data.get("processed_plain_text") + detailed_plain_text = data.get("detailed_plain_text") + user_info_dict = data.get("user_info") # 先获取字典 + time_value = data.get("time") - message = { - "message_id": message_id, - "processed_plain_text": processed_plain_text, - "detailed_plain_text": detailed_plain_text, - "user_info": user_info, - "time": time_value, - } + # 确保 user_info 是字典类型再创建 UserInfo 对象 + user_info = None + if isinstance(user_info_dict, dict): + try: + user_info = UserInfo.from_dict(user_info_dict) + except Exception as e: + logger.error(f"[私聊][{self.private_name}]从字典创建 UserInfo 时出错: {e}, 字典内容: {user_info_dict}") + # 可以选择在这里返回或记录错误,避免后续代码出错 + return + elif user_info_dict is not None: + logger.warning(f"[私聊][{self.private_name}]收到的 user_info 不是预期的字典类型: {type(user_info_dict)}") + # 根据需要处理非字典情况,这里暂时返回 + return - self.observation_info.update_from_message(message) - elif notification_type == NotificationType.COLD_CHAT: - # 处理冷场通知 - is_cold = data.get("is_cold", False) - self.observation_info.update_cold_chat_status(is_cold, time.time()) + message = { + "message_id": message_id, + "processed_plain_text": processed_plain_text, + "detailed_plain_text": detailed_plain_text, + "user_info": user_info_dict, # 存储原始字典或 UserInfo 对象,取决于你的 update_from_message 如何处理 + "time": time_value, + } + # 传递 UserInfo 对象(如果成功创建)或原始字典 + await self.observation_info.update_from_message(message, user_info) # 修改:传递 user_info 对象 - elif notification_type == NotificationType.ACTIVE_CHAT: - # 处理活跃通知 - is_active = data.get("is_active", False) - self.observation_info.is_cold = not is_active - elif notification_type == NotificationType.BOT_SPEAKING: - # 处理机器人说话通知 - self.observation_info.is_typing = False - self.observation_info.last_bot_speak_time = time.time() + elif notification_type == NotificationType.COLD_CHAT: + # 处理冷场通知 + is_cold = data.get("is_cold", False) + await self.observation_info.update_cold_chat_status(is_cold, time.time()) # 修改:改为 await 调用 - elif notification_type == NotificationType.USER_SPEAKING: - # 处理用户说话通知 - self.observation_info.is_typing = False - self.observation_info.last_user_speak_time = time.time() + elif notification_type == NotificationType.ACTIVE_CHAT: + # 处理活跃通知 (通常由 COLD_CHAT 的反向状态处理) + is_active = data.get("is_active", False) + self.observation_info.is_cold = not is_active - elif notification_type == NotificationType.MESSAGE_DELETED: - # 处理消息删除通知 - message_id = data.get("message_id") - self.observation_info.unprocessed_messages = [ - msg for msg in self.observation_info.unprocessed_messages if msg.get("message_id") != message_id - ] + elif notification_type == NotificationType.BOT_SPEAKING: + # 处理机器人说话通知 (按需实现) + self.observation_info.is_typing = False + self.observation_info.last_bot_speak_time = time.time() - elif notification_type == NotificationType.USER_JOINED: - # 处理用户加入通知 - user_id = data.get("user_id") - if user_id: - self.observation_info.active_users.add(user_id) + elif notification_type == NotificationType.USER_SPEAKING: + # 处理用户说话通知 + self.observation_info.is_typing = False + self.observation_info.last_user_speak_time = time.time() - elif notification_type == NotificationType.USER_LEFT: - # 处理用户离开通知 - user_id = data.get("user_id") - if user_id: - self.observation_info.active_users.discard(user_id) + elif notification_type == NotificationType.MESSAGE_DELETED: + # 处理消息删除通知 + message_id = data.get("message_id") + # 从 unprocessed_messages 中移除被删除的消息 + original_count = len(self.observation_info.unprocessed_messages) + self.observation_info.unprocessed_messages = [ + msg for msg in self.observation_info.unprocessed_messages if msg.get("message_id") != message_id + ] + if len(self.observation_info.unprocessed_messages) < original_count: + logger.info(f"[私聊][{self.private_name}]移除了未处理的消息 (ID: {message_id})") - elif notification_type == NotificationType.ERROR: - # 处理错误通知 - error_msg = data.get("error", "") - logger.error(f"[私聊][{self.private_name}]收到错误通知: {error_msg}") + + elif notification_type == NotificationType.USER_JOINED: + # 处理用户加入通知 (如果适用私聊场景) + user_id = data.get("user_id") + if user_id: + self.observation_info.active_users.add(str(user_id)) # 确保是字符串 + + elif notification_type == NotificationType.USER_LEFT: + # 处理用户离开通知 (如果适用私聊场景) + user_id = data.get("user_id") + if user_id: + self.observation_info.active_users.discard(str(user_id)) # 确保是字符串 + + elif notification_type == NotificationType.ERROR: + # 处理错误通知 + error_msg = data.get("error", "未提供错误信息") + logger.error(f"[私聊][{self.private_name}]收到错误通知: {error_msg}") + + except Exception as e: + logger.error(f"[私聊][{self.private_name}]处理通知时发生错误: {e}") + logger.error(traceback.format_exc()) # 打印详细堆栈信息 @dataclass class ObservationInfo: """决策信息类,用于收集和管理来自chat_observer的通知信息""" + # --- 修改:添加 private_name 字段 --- + private_name: str = field(init=True) # 让 dataclass 的 __init__ 接收 private_name + # data_list - chat_history: List[str] = field(default_factory=list) + chat_history: List[Dict[str, Any]] = field(default_factory=list) # 修改:明确类型为 Dict chat_history_str: str = "" - unprocessed_messages: List[Dict[str, Any]] = field(default_factory=list) + unprocessed_messages: List[Dict[str, Any]] = field(default_factory=list) # 修改:明确类型为 Dict active_users: Set[str] = field(default_factory=set) # data last_bot_speak_time: Optional[float] = None last_user_speak_time: Optional[float] = None last_message_time: Optional[float] = None + # 添加 last_message_id + last_message_id: Optional[str] = None last_message_content: str = "" last_message_sender: Optional[str] = None bot_id: Optional[str] = None chat_history_count: int = 0 new_messages_count: int = 0 - cold_chat_duration: float = 0.0 + cold_chat_start_time: Optional[float] = None # 用于计算冷场持续时间 + cold_chat_duration: float = 0.0 # 缓存计算结果 # state - is_typing: bool = False - has_unread_messages: bool = False + is_typing: bool = False # 可能表示对方正在输入 + # has_unread_messages: bool = False # 这个状态可以通过 new_messages_count > 0 判断 is_cold_chat: bool = False - changed: bool = False + changed: bool = False # 用于标记状态是否有变化,以便外部模块决定是否重新规划 - # #spec + # #spec (暂时注释掉,如果不需要) # meta_plan_trigger: bool = False - def __post_init__(self, private_name: str = None): - """初始化后创建handler""" - self.chat_observer = None - self.handler = ObservationInfoHandler(self, private_name) - self.private_name = private_name + # --- 修改:移除 __post_init__ 的参数 --- + def __post_init__(self): + """初始化后创建handler并进行必要的设置""" + self.chat_observer: Optional[ChatObserver] = None # 添加类型提示 + self.handler = ObservationInfoHandler(self, self.private_name) - def bind_to_chat_observer(self, chat_observer: ChatObserver, private_name: str): + def bind_to_chat_observer(self, chat_observer: ChatObserver): """绑定到指定的chat_observer Args: - stream_id: 聊天流ID + chat_observer: 要绑定的 ChatObserver 实例 """ + if self.chat_observer: + logger.warning(f"[私聊][{self.private_name}]尝试重复绑定 ChatObserver") + return + self.chat_observer = chat_observer - self.chat_observer.notification_manager.register_handler( - target="observation_info", notification_type=NotificationType.NEW_MESSAGE, handler=self.handler - ) - self.chat_observer.notification_manager.register_handler( - target="observation_info", notification_type=NotificationType.COLD_CHAT, handler=self.handler - ) + try: + # 注册关心的通知类型 + self.chat_observer.notification_manager.register_handler( + target="observation_info", notification_type=NotificationType.NEW_MESSAGE, handler=self.handler + ) + self.chat_observer.notification_manager.register_handler( + target="observation_info", notification_type=NotificationType.COLD_CHAT, handler=self.handler + ) + # 可以根据需要注册更多通知类型 + # self.chat_observer.notification_manager.register_handler( + # target="observation_info", notification_type=NotificationType.MESSAGE_DELETED, handler=self.handler + # ) + logger.info(f"[私聊][{self.private_name}]成功绑定到 ChatObserver") + except Exception as e: + logger.error(f"[私聊][{self.private_name}]绑定到 ChatObserver 时出错: {e}") + self.chat_observer = None # 绑定失败,重置 def unbind_from_chat_observer(self): """解除与chat_observer的绑定""" - if self.chat_observer: - self.chat_observer.notification_manager.unregister_handler( - target="observation_info", notification_type=NotificationType.NEW_MESSAGE, handler=self.handler - ) - self.chat_observer.notification_manager.unregister_handler( - target="observation_info", notification_type=NotificationType.COLD_CHAT, handler=self.handler - ) - self.chat_observer = None + if self.chat_observer and hasattr(self.chat_observer, 'notification_manager'): # 增加检查 + try: + self.chat_observer.notification_manager.unregister_handler( + target="observation_info", notification_type=NotificationType.NEW_MESSAGE, handler=self.handler + ) + self.chat_observer.notification_manager.unregister_handler( + target="observation_info", notification_type=NotificationType.COLD_CHAT, handler=self.handler + ) + # 如果注册了其他类型,也要在这里注销 + # self.chat_observer.notification_manager.unregister_handler( + # target="observation_info", notification_type=NotificationType.MESSAGE_DELETED, handler=self.handler + # ) + logger.info(f"[私聊][{self.private_name}]成功从 ChatObserver 解绑") + except Exception as e: + logger.error(f"[私聊][{self.private_name}]从 ChatObserver 解绑时出错: {e}") + finally: # 确保 chat_observer 被重置 + self.chat_observer = None + else: + logger.warning(f"[私聊][{self.private_name}]尝试解绑时 ChatObserver 不存在或无效") - def update_from_message(self, message: Dict[str, Any]): + + # 修改:update_from_message 接收 UserInfo 对象 + async def update_from_message(self, message: Dict[str, Any], user_info: Optional[UserInfo]): """从消息更新信息 Args: - message: 消息数据 + message: 消息数据字典 + user_info: 解析后的 UserInfo 对象 (可能为 None) """ - # logger.debug(f"[私聊][{self.private_name}]更新信息from_message: {message}") - self.last_message_time = message["time"] - self.last_message_id = message["message_id"] + message_time = message.get("time") + message_id = message.get("message_id") + processed_text = message.get("processed_plain_text", "") - self.last_message_content = message.get("processed_plain_text", "") + # 只有在新消息到达时才更新 last_message 相关信息 + if message_time and message_time > (self.last_message_time or 0): + self.last_message_time = message_time + self.last_message_id = message_id + self.last_message_content = processed_text + # 重置冷场计时器 + self.is_cold_chat = False + self.cold_chat_start_time = None + self.cold_chat_duration = 0.0 - user_info = UserInfo.from_dict(message.get("user_info", {})) - self.last_message_sender = user_info.user_id - if user_info.user_id == self.bot_id: - self.last_bot_speak_time = message["time"] + if user_info: + sender_id = str(user_info.user_id) # 确保是字符串 + self.last_message_sender = sender_id + # 更新发言时间 + if sender_id == self.bot_id: + self.last_bot_speak_time = message_time + else: + self.last_user_speak_time = message_time + self.active_users.add(sender_id) # 用户发言则认为其活跃 + else: + logger.warning(f"[私聊][{self.private_name}]处理消息更新时缺少有效的 UserInfo 对象, message_id: {message_id}") + self.last_message_sender = None # 发送者未知 + + + # 将原始消息字典添加到未处理列表 + self.unprocessed_messages.append(message) + self.new_messages_count = len(self.unprocessed_messages) # 直接用列表长度 + + # logger.debug(f"[私聊][{self.private_name}]消息更新: last_time={self.last_message_time}, new_count={self.new_messages_count}") + self.update_changed() # 标记状态已改变 else: - self.last_user_speak_time = message["time"] - self.active_users.add(user_info.user_id) + # 如果消息时间戳不是最新的,可能不需要处理,或者记录一个警告 + pass + # logger.warning(f"[私聊][{self.private_name}]收到过时或无效时间戳的消息: ID={message_id}, time={message_time}") - self.new_messages_count += 1 - self.unprocessed_messages.append(message) - - self.update_changed() def update_changed(self): - """更新changed状态""" + """标记状态已改变,并重置标记""" + # logger.debug(f"[私聊][{self.private_name}]状态标记为已改变 (changed=True)") self.changed = True - def update_cold_chat_status(self, is_cold: bool, current_time: float): + + async def update_cold_chat_status(self, is_cold: bool, current_time: float): """更新冷场状态 Args: - is_cold: 是否冷场 - current_time: 当前时间 + is_cold: 是否处于冷场状态 + current_time: 当前时间戳 """ - self.is_cold_chat = is_cold - if is_cold and self.last_message_time: - self.cold_chat_duration = current_time - self.last_message_time + if is_cold != self.is_cold_chat: # 仅在状态变化时更新 + self.is_cold_chat = is_cold + if is_cold: + # 进入冷场状态 + self.cold_chat_start_time = self.last_message_time or current_time # 从最后消息时间开始算,或从当前时间开始 + logger.info(f"[私聊][{self.private_name}]进入冷场状态,开始时间: {self.cold_chat_start_time}") + else: + # 结束冷场状态 + if self.cold_chat_start_time: + self.cold_chat_duration = current_time - self.cold_chat_start_time + logger.info(f"[私聊][{self.private_name}]结束冷场状态,持续时间: {self.cold_chat_duration:.2f} 秒") + self.cold_chat_start_time = None # 重置开始时间 + self.update_changed() # 状态变化,标记改变 + + # 即使状态没变,如果是冷场状态,也更新持续时间 + if self.is_cold_chat and self.cold_chat_start_time: + self.cold_chat_duration = current_time - self.cold_chat_start_time + def get_active_duration(self) -> float: - """获取当前活跃时长 + """获取当前活跃时长 (距离最后一条消息的时间) Returns: float: 最后一条消息到现在的时长(秒) @@ -206,7 +305,7 @@ class ObservationInfo: return time.time() - self.last_message_time def get_user_response_time(self) -> Optional[float]: - """获取用户响应时间 + """获取用户最后响应时间 (距离用户最后发言的时间) Returns: Optional[float]: 用户最后发言到现在的时长(秒),如果没有用户发言则返回None @@ -216,7 +315,7 @@ class ObservationInfo: return time.time() - self.last_user_speak_time def get_bot_response_time(self) -> Optional[float]: - """获取机器人响应时间 + """获取机器人最后响应时间 (距离机器人最后发言的时间) Returns: Optional[float]: 机器人最后发言到现在的时长(秒),如果没有机器人发言则返回None @@ -226,19 +325,39 @@ class ObservationInfo: return time.time() - self.last_bot_speak_time async def clear_unprocessed_messages(self): - """清空未处理消息列表""" - # 将未处理消息添加到历史记录中 - for message in self.unprocessed_messages: - self.chat_history.append(message) - self.chat_history_str = await build_readable_messages( - self.chat_history[-20:] if len(self.chat_history) > 20 else self.chat_history, - replace_bot_name=True, - merge_messages=False, - timestamp_mode="relative", - read_mark=0.0, - ) - # 清空未处理消息列表 - self.has_unread_messages = False + """将未处理消息移入历史记录,并更新相关状态""" + if not self.unprocessed_messages: + return # 没有未处理消息,直接返回 + + # logger.debug(f"[私聊][{self.private_name}]处理 {len(self.unprocessed_messages)} 条未处理消息...") + # 将未处理消息添加到历史记录中 (确保历史记录有长度限制,避免无限增长) + max_history_len = 100 # 示例:最多保留100条历史记录 + self.chat_history.extend(self.unprocessed_messages) + if len(self.chat_history) > max_history_len: + self.chat_history = self.chat_history[-max_history_len:] + + # 更新历史记录字符串 (只使用最近一部分生成,例如20条) + history_slice_for_str = self.chat_history[-20:] + try: + self.chat_history_str = await build_readable_messages( + history_slice_for_str, + replace_bot_name=True, + merge_messages=False, + timestamp_mode="relative", + read_mark=0.0, # read_mark 可能需要根据逻辑调整 + ) + except Exception as e: + logger.error(f"[私聊][{self.private_name}]构建聊天记录字符串时出错: {e}") + self.chat_history_str = "[构建聊天记录出错]" # 提供错误提示 + + + # 清空未处理消息列表和计数 + cleared_count = len(self.unprocessed_messages) self.unprocessed_messages.clear() - self.chat_history_count = len(self.chat_history) self.new_messages_count = 0 + # self.has_unread_messages = False # 这个状态可以通过 new_messages_count 判断 + + self.chat_history_count = len(self.chat_history) # 更新历史记录总数 + # logger.debug(f"[私聊][{self.private_name}]已处理 {cleared_count} 条消息,当前历史记录 {self.chat_history_count} 条。") + + self.update_changed() # 状态改变 \ No newline at end of file From 44549a43f83c5ebd8d780bfd6634659d08951738 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Mon, 28 Apr 2025 18:55:14 +0800 Subject: [PATCH 33/54] ruff --- src/plugins/PFC/observation_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/PFC/observation_info.py b/src/plugins/PFC/observation_info.py index 29cc870f..3584cbae 100644 --- a/src/plugins/PFC/observation_info.py +++ b/src/plugins/PFC/observation_info.py @@ -352,7 +352,7 @@ class ObservationInfo: # 清空未处理消息列表和计数 - cleared_count = len(self.unprocessed_messages) + # cleared_count = len(self.unprocessed_messages) self.unprocessed_messages.clear() self.new_messages_count = 0 # self.has_unread_messages = False # 这个状态可以通过 new_messages_count 判断 From 79a70e0202e5aadd4772f9e5516adb9e6d25eef0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 28 Apr 2025 10:55:43 +0000 Subject: [PATCH 34/54] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/observation_info.py | 96 ++++++++++++++--------------- 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/src/plugins/PFC/observation_info.py b/src/plugins/PFC/observation_info.py index 3584cbae..35f39301 100644 --- a/src/plugins/PFC/observation_info.py +++ b/src/plugins/PFC/observation_info.py @@ -6,7 +6,7 @@ from src.common.logger import get_module_logger from .chat_observer import ChatObserver from .chat_states import NotificationHandler, NotificationType, Notification from src.plugins.utils.chat_message_builder import build_readable_messages -import traceback # 导入 traceback 用于调试 +import traceback # 导入 traceback 用于调试 logger = get_module_logger("observation_info") @@ -25,19 +25,19 @@ class ObservationInfoHandler(NotificationHandler): # 将 private_name 存储在 handler 实例中 self.private_name = private_name - async def handle_notification(self, notification: Notification): # 添加类型提示 + async def handle_notification(self, notification: Notification): # 添加类型提示 # 获取通知类型和数据 notification_type = notification.type data = notification.data - try: # 添加错误处理块 + try: # 添加错误处理块 if notification_type == NotificationType.NEW_MESSAGE: # 处理新消息通知 # logger.debug(f"[私聊][{self.private_name}]收到新消息通知data: {data}") # 可以在需要时取消注释 message_id = data.get("message_id") processed_plain_text = data.get("processed_plain_text") detailed_plain_text = data.get("detailed_plain_text") - user_info_dict = data.get("user_info") # 先获取字典 + user_info_dict = data.get("user_info") # 先获取字典 time_value = data.get("time") # 确保 user_info 是字典类型再创建 UserInfo 对象 @@ -46,30 +46,32 @@ class ObservationInfoHandler(NotificationHandler): try: user_info = UserInfo.from_dict(user_info_dict) except Exception as e: - logger.error(f"[私聊][{self.private_name}]从字典创建 UserInfo 时出错: {e}, 字典内容: {user_info_dict}") + logger.error( + f"[私聊][{self.private_name}]从字典创建 UserInfo 时出错: {e}, 字典内容: {user_info_dict}" + ) # 可以选择在这里返回或记录错误,避免后续代码出错 return elif user_info_dict is not None: - logger.warning(f"[私聊][{self.private_name}]收到的 user_info 不是预期的字典类型: {type(user_info_dict)}") + logger.warning( + f"[私聊][{self.private_name}]收到的 user_info 不是预期的字典类型: {type(user_info_dict)}" + ) # 根据需要处理非字典情况,这里暂时返回 return - message = { "message_id": message_id, "processed_plain_text": processed_plain_text, "detailed_plain_text": detailed_plain_text, - "user_info": user_info_dict, # 存储原始字典或 UserInfo 对象,取决于你的 update_from_message 如何处理 + "user_info": user_info_dict, # 存储原始字典或 UserInfo 对象,取决于你的 update_from_message 如何处理 "time": time_value, } # 传递 UserInfo 对象(如果成功创建)或原始字典 - await self.observation_info.update_from_message(message, user_info) # 修改:传递 user_info 对象 - + await self.observation_info.update_from_message(message, user_info) # 修改:传递 user_info 对象 elif notification_type == NotificationType.COLD_CHAT: # 处理冷场通知 is_cold = data.get("is_cold", False) - await self.observation_info.update_cold_chat_status(is_cold, time.time()) # 修改:改为 await 调用 + await self.observation_info.update_cold_chat_status(is_cold, time.time()) # 修改:改为 await 调用 elif notification_type == NotificationType.ACTIVE_CHAT: # 处理活跃通知 (通常由 COLD_CHAT 的反向状态处理) @@ -97,18 +99,17 @@ class ObservationInfoHandler(NotificationHandler): if len(self.observation_info.unprocessed_messages) < original_count: logger.info(f"[私聊][{self.private_name}]移除了未处理的消息 (ID: {message_id})") - elif notification_type == NotificationType.USER_JOINED: # 处理用户加入通知 (如果适用私聊场景) user_id = data.get("user_id") if user_id: - self.observation_info.active_users.add(str(user_id)) # 确保是字符串 + self.observation_info.active_users.add(str(user_id)) # 确保是字符串 elif notification_type == NotificationType.USER_LEFT: # 处理用户离开通知 (如果适用私聊场景) user_id = data.get("user_id") if user_id: - self.observation_info.active_users.discard(str(user_id)) # 确保是字符串 + self.observation_info.active_users.discard(str(user_id)) # 确保是字符串 elif notification_type == NotificationType.ERROR: # 处理错误通知 @@ -117,7 +118,7 @@ class ObservationInfoHandler(NotificationHandler): except Exception as e: logger.error(f"[私聊][{self.private_name}]处理通知时发生错误: {e}") - logger.error(traceback.format_exc()) # 打印详细堆栈信息 + logger.error(traceback.format_exc()) # 打印详细堆栈信息 @dataclass @@ -125,12 +126,12 @@ class ObservationInfo: """决策信息类,用于收集和管理来自chat_observer的通知信息""" # --- 修改:添加 private_name 字段 --- - private_name: str = field(init=True) # 让 dataclass 的 __init__ 接收 private_name + private_name: str = field(init=True) # 让 dataclass 的 __init__ 接收 private_name # data_list - chat_history: List[Dict[str, Any]] = field(default_factory=list) # 修改:明确类型为 Dict + chat_history: List[Dict[str, Any]] = field(default_factory=list) # 修改:明确类型为 Dict chat_history_str: str = "" - unprocessed_messages: List[Dict[str, Any]] = field(default_factory=list) # 修改:明确类型为 Dict + unprocessed_messages: List[Dict[str, Any]] = field(default_factory=list) # 修改:明确类型为 Dict active_users: Set[str] = field(default_factory=set) # data @@ -144,14 +145,14 @@ class ObservationInfo: bot_id: Optional[str] = None chat_history_count: int = 0 new_messages_count: int = 0 - cold_chat_start_time: Optional[float] = None # 用于计算冷场持续时间 - cold_chat_duration: float = 0.0 # 缓存计算结果 + cold_chat_start_time: Optional[float] = None # 用于计算冷场持续时间 + cold_chat_duration: float = 0.0 # 缓存计算结果 # state - is_typing: bool = False # 可能表示对方正在输入 + is_typing: bool = False # 可能表示对方正在输入 # has_unread_messages: bool = False # 这个状态可以通过 new_messages_count > 0 判断 is_cold_chat: bool = False - changed: bool = False # 用于标记状态是否有变化,以便外部模块决定是否重新规划 + changed: bool = False # 用于标记状态是否有变化,以便外部模块决定是否重新规划 # #spec (暂时注释掉,如果不需要) # meta_plan_trigger: bool = False @@ -159,7 +160,7 @@ class ObservationInfo: # --- 修改:移除 __post_init__ 的参数 --- def __post_init__(self): """初始化后创建handler并进行必要的设置""" - self.chat_observer: Optional[ChatObserver] = None # 添加类型提示 + self.chat_observer: Optional[ChatObserver] = None # 添加类型提示 self.handler = ObservationInfoHandler(self, self.private_name) def bind_to_chat_observer(self, chat_observer: ChatObserver): @@ -188,11 +189,11 @@ class ObservationInfo: logger.info(f"[私聊][{self.private_name}]成功绑定到 ChatObserver") except Exception as e: logger.error(f"[私聊][{self.private_name}]绑定到 ChatObserver 时出错: {e}") - self.chat_observer = None # 绑定失败,重置 + self.chat_observer = None # 绑定失败,重置 def unbind_from_chat_observer(self): """解除与chat_observer的绑定""" - if self.chat_observer and hasattr(self.chat_observer, 'notification_manager'): # 增加检查 + if self.chat_observer and hasattr(self.chat_observer, "notification_manager"): # 增加检查 try: self.chat_observer.notification_manager.unregister_handler( target="observation_info", notification_type=NotificationType.NEW_MESSAGE, handler=self.handler @@ -207,12 +208,11 @@ class ObservationInfo: logger.info(f"[私聊][{self.private_name}]成功从 ChatObserver 解绑") except Exception as e: logger.error(f"[私聊][{self.private_name}]从 ChatObserver 解绑时出错: {e}") - finally: # 确保 chat_observer 被重置 + finally: # 确保 chat_observer 被重置 self.chat_observer = None else: logger.warning(f"[私聊][{self.private_name}]尝试解绑时 ChatObserver 不存在或无效") - # 修改:update_from_message 接收 UserInfo 对象 async def update_from_message(self, message: Dict[str, Any], user_info: Optional[UserInfo]): """从消息更新信息 @@ -235,39 +235,37 @@ class ObservationInfo: self.cold_chat_start_time = None self.cold_chat_duration = 0.0 - if user_info: - sender_id = str(user_info.user_id) # 确保是字符串 + sender_id = str(user_info.user_id) # 确保是字符串 self.last_message_sender = sender_id # 更新发言时间 if sender_id == self.bot_id: self.last_bot_speak_time = message_time else: self.last_user_speak_time = message_time - self.active_users.add(sender_id) # 用户发言则认为其活跃 + self.active_users.add(sender_id) # 用户发言则认为其活跃 else: - logger.warning(f"[私聊][{self.private_name}]处理消息更新时缺少有效的 UserInfo 对象, message_id: {message_id}") - self.last_message_sender = None # 发送者未知 - + logger.warning( + f"[私聊][{self.private_name}]处理消息更新时缺少有效的 UserInfo 对象, message_id: {message_id}" + ) + self.last_message_sender = None # 发送者未知 # 将原始消息字典添加到未处理列表 self.unprocessed_messages.append(message) - self.new_messages_count = len(self.unprocessed_messages) # 直接用列表长度 + self.new_messages_count = len(self.unprocessed_messages) # 直接用列表长度 # logger.debug(f"[私聊][{self.private_name}]消息更新: last_time={self.last_message_time}, new_count={self.new_messages_count}") - self.update_changed() # 标记状态已改变 + self.update_changed() # 标记状态已改变 else: # 如果消息时间戳不是最新的,可能不需要处理,或者记录一个警告 pass # logger.warning(f"[私聊][{self.private_name}]收到过时或无效时间戳的消息: ID={message_id}, time={message_time}") - def update_changed(self): """标记状态已改变,并重置标记""" # logger.debug(f"[私聊][{self.private_name}]状态标记为已改变 (changed=True)") self.changed = True - async def update_cold_chat_status(self, is_cold: bool, current_time: float): """更新冷场状态 @@ -275,25 +273,26 @@ class ObservationInfo: is_cold: 是否处于冷场状态 current_time: 当前时间戳 """ - if is_cold != self.is_cold_chat: # 仅在状态变化时更新 + if is_cold != self.is_cold_chat: # 仅在状态变化时更新 self.is_cold_chat = is_cold if is_cold: # 进入冷场状态 - self.cold_chat_start_time = self.last_message_time or current_time # 从最后消息时间开始算,或从当前时间开始 + self.cold_chat_start_time = ( + self.last_message_time or current_time + ) # 从最后消息时间开始算,或从当前时间开始 logger.info(f"[私聊][{self.private_name}]进入冷场状态,开始时间: {self.cold_chat_start_time}") else: # 结束冷场状态 if self.cold_chat_start_time: self.cold_chat_duration = current_time - self.cold_chat_start_time logger.info(f"[私聊][{self.private_name}]结束冷场状态,持续时间: {self.cold_chat_duration:.2f} 秒") - self.cold_chat_start_time = None # 重置开始时间 - self.update_changed() # 状态变化,标记改变 + self.cold_chat_start_time = None # 重置开始时间 + self.update_changed() # 状态变化,标记改变 # 即使状态没变,如果是冷场状态,也更新持续时间 if self.is_cold_chat and self.cold_chat_start_time: self.cold_chat_duration = current_time - self.cold_chat_start_time - def get_active_duration(self) -> float: """获取当前活跃时长 (距离最后一条消息的时间) @@ -327,11 +326,11 @@ class ObservationInfo: async def clear_unprocessed_messages(self): """将未处理消息移入历史记录,并更新相关状态""" if not self.unprocessed_messages: - return # 没有未处理消息,直接返回 + return # 没有未处理消息,直接返回 # logger.debug(f"[私聊][{self.private_name}]处理 {len(self.unprocessed_messages)} 条未处理消息...") # 将未处理消息添加到历史记录中 (确保历史记录有长度限制,避免无限增长) - max_history_len = 100 # 示例:最多保留100条历史记录 + max_history_len = 100 # 示例:最多保留100条历史记录 self.chat_history.extend(self.unprocessed_messages) if len(self.chat_history) > max_history_len: self.chat_history = self.chat_history[-max_history_len:] @@ -344,12 +343,11 @@ class ObservationInfo: replace_bot_name=True, merge_messages=False, timestamp_mode="relative", - read_mark=0.0, # read_mark 可能需要根据逻辑调整 + read_mark=0.0, # read_mark 可能需要根据逻辑调整 ) except Exception as e: logger.error(f"[私聊][{self.private_name}]构建聊天记录字符串时出错: {e}") - self.chat_history_str = "[构建聊天记录出错]" # 提供错误提示 - + self.chat_history_str = "[构建聊天记录出错]" # 提供错误提示 # 清空未处理消息列表和计数 # cleared_count = len(self.unprocessed_messages) @@ -357,7 +355,7 @@ class ObservationInfo: self.new_messages_count = 0 # self.has_unread_messages = False # 这个状态可以通过 new_messages_count 判断 - self.chat_history_count = len(self.chat_history) # 更新历史记录总数 + self.chat_history_count = len(self.chat_history) # 更新历史记录总数 # logger.debug(f"[私聊][{self.private_name}]已处理 {cleared_count} 条消息,当前历史记录 {self.chat_history_count} 条。") - self.update_changed() # 状态改变 \ No newline at end of file + self.update_changed() # 状态改变 From f83e151d400e3ea71a05901b0b5ebc0fe6439d50 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Mon, 28 Apr 2025 19:31:00 +0800 Subject: [PATCH 35/54] =?UTF-8?q?fix=EF=BC=9A=E4=BC=98=E5=8C=96=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/heart_flow/sub_mind.py | 70 ++++----- src/plugins/heartFC_chat/heartFC_chat.py | 152 +++++++++--------- src/plugins/models/utils_model.py | 10 +- src/plugins/utils/json_utils.py | 189 +++++------------------ 4 files changed, 159 insertions(+), 262 deletions(-) diff --git a/src/heart_flow/sub_mind.py b/src/heart_flow/sub_mind.py index bccead7d..50bb889e 100644 --- a/src/heart_flow/sub_mind.py +++ b/src/heart_flow/sub_mind.py @@ -66,6 +66,9 @@ class SubMind: self.current_mind = "" self.past_mind = [] self.structured_info = {} + + name = chat_manager.get_stream_name(self.subheartflow_id) + self.log_prefix = f"[{name}] " async def do_thinking_before_reply(self, history_cycle: list[CycleInfo] = None): """ @@ -77,6 +80,8 @@ class SubMind: # 更新活跃时间 self.last_active_time = time.time() + + # ---------- 1. 准备基础数据 ---------- # 获取现有想法和情绪状态 current_thinking_info = self.current_mind @@ -85,7 +90,7 @@ class SubMind: # 获取观察对象 observation = self.observations[0] if not observation: - logger.error(f"[{self.subheartflow_id}] 无法获取观察对象") + logger.error(f"{self.log_prefix} 无法获取观察对象") self.update_current_mind("(我没看到任何聊天内容...)") return self.current_mind, self.past_mind @@ -223,57 +228,48 @@ class SubMind: try: # 调用LLM生成响应 - response = await self.llm_model.generate_response_tool_async(prompt=prompt, tools=tools) + response, _reasoning_content, tool_calls = await self.llm_model.generate_response_tool_async(prompt=prompt, tools=tools) - # 标准化响应格式 - success, normalized_response, error_msg = normalize_llm_response( - response, log_prefix=f"[{self.subheartflow_id}] " - ) + logger.debug(f"{self.log_prefix} 子心流输出的原始LLM响应: {response}") + + # 直接使用LLM返回的文本响应作为 content + content = response if response else "" - if not success: - # 处理标准化失败情况 - logger.warning(f"[{self.subheartflow_id}] {error_msg}") - content = "LLM响应格式无法处理" - else: - # 从标准化响应中提取内容 - if len(normalized_response) >= 2: - content = normalized_response[0] - _reasoning_content = normalized_response[1] if len(normalized_response) > 1 else "" - - # 处理可能的工具调用 - if len(normalized_response) == 3: - # 提取并验证工具调用 - success, valid_tool_calls, error_msg = process_llm_tool_calls( - normalized_response, log_prefix=f"[{self.subheartflow_id}] " + if tool_calls: + # 直接将 tool_calls 传递给处理函数 + success, valid_tool_calls, error_msg = process_llm_tool_calls( + tool_calls, log_prefix=f"{self.log_prefix} " + ) + + if success and valid_tool_calls: + # 记录工具调用信息 + tool_calls_str = ", ".join( + [call.get("function", {}).get("name", "未知工具") for call in valid_tool_calls] + ) + logger.info( + f"{self.log_prefix} 模型请求调用{len(valid_tool_calls)}个工具: {tool_calls_str}" ) - if success and valid_tool_calls: - # 记录工具调用信息 - tool_calls_str = ", ".join( - [call.get("function", {}).get("name", "未知工具") for call in valid_tool_calls] - ) - logger.info( - f"[{self.subheartflow_id}] 模型请求调用{len(valid_tool_calls)}个工具: {tool_calls_str}" - ) + # 收集工具执行结果 + await self._execute_tool_calls(valid_tool_calls, tool_instance) + elif not success: + logger.warning(f"{self.log_prefix} 处理工具调用时出错: {error_msg}") + else: + logger.info(f"{self.log_prefix} 心流未使用工具") # 修改日志信息,明确是未使用工具而不是未处理 - # 收集工具执行结果 - await self._execute_tool_calls(valid_tool_calls, tool_instance) - elif not success: - logger.warning(f"[{self.subheartflow_id}] {error_msg}") except Exception as e: # 处理总体异常 - logger.error(f"[{self.subheartflow_id}] 执行LLM请求或处理响应时出错: {e}") + logger.error(f"{self.log_prefix} 执行LLM请求或处理响应时出错: {e}") logger.error(traceback.format_exc()) content = "思考过程中出现错误" # 记录最终思考结果 - name = chat_manager.get_stream_name(self.subheartflow_id) - logger.debug(f"[{name}] \nPrompt:\n{prompt}\n\n心流思考结果:\n{content}\n") + logger.debug(f"{self.log_prefix} \nPrompt:\n{prompt}\n\n心流思考结果:\n{content}\n") # 处理空响应情况 if not content: content = "(不知道该想些什么...)" - logger.warning(f"[{self.subheartflow_id}] LLM返回空结果,思考失败。") + logger.warning(f"{self.log_prefix} LLM返回空结果,思考失败。") # ---------- 6. 更新思考状态并返回结果 ---------- # 更新当前思考内容 diff --git a/src/plugins/heartFC_chat/heartFC_chat.py b/src/plugins/heartFC_chat/heartFC_chat.py index 1237378a..83c43534 100644 --- a/src/plugins/heartFC_chat/heartFC_chat.py +++ b/src/plugins/heartFC_chat/heartFC_chat.py @@ -17,7 +17,7 @@ from src.plugins.utils.timer_calculator import Timer # <--- Import Timer from src.plugins.heartFC_chat.heartFC_generator import HeartFCGenerator from src.do_tool.tool_use import ToolUser from src.plugins.emoji_system.emoji_manager import emoji_manager -from src.plugins.utils.json_utils import process_llm_tool_response # 导入新的JSON工具 +from src.plugins.utils.json_utils import process_llm_tool_calls, extract_tool_call_arguments from src.heart_flow.sub_mind import SubMind from src.heart_flow.observation import Observation from src.plugins.heartFC_chat.heartflow_prompt_builder import global_prompt_manager @@ -401,20 +401,24 @@ class HeartFChatting: with Timer("决策", cycle_timers): planner_result = await self._planner(current_mind, cycle_timers) - action = planner_result.get("action", "error") - reasoning = planner_result.get("reasoning", "未提供理由") + + # 效果不太好,还没处理replan导致观察时间点改变的问题 + + # action = planner_result.get("action", "error") + # reasoning = planner_result.get("reasoning", "未提供理由") - self._current_cycle.set_action_info(action, reasoning, False) + # self._current_cycle.set_action_info(action, reasoning, False) # 在获取规划结果后检查新消息 - if await self._check_new_messages(planner_start_db_time): - if random.random() < 0.2: - logger.info(f"{self.log_prefix} 看到了新消息,麦麦决定重新观察和规划...") - # 重新规划 - with Timer("重新决策", cycle_timers): - self._current_cycle.replanned = True - planner_result = await self._planner(current_mind, cycle_timers, is_re_planned=True) - logger.info(f"{self.log_prefix} 重新规划完成.") + + # if await self._check_new_messages(planner_start_db_time): + # if random.random() < 0.2: + # logger.info(f"{self.log_prefix} 看到了新消息,麦麦决定重新观察和规划...") + # # 重新规划 + # with Timer("重新决策", cycle_timers): + # self._current_cycle.replanned = True + # planner_result = await self._planner(current_mind, cycle_timers, is_re_planned=True) + # logger.info(f"{self.log_prefix} 重新规划完成.") # 解析规划结果 action = planner_result.get("action", "error") @@ -736,94 +740,104 @@ class HeartFChatting: observed_messages_str = observation.talking_message_str # --- 使用 LLM 进行决策 --- # - action = "no_reply" # 默认动作 - emoji_query = "" # 默认表情查询 reasoning = "默认决策或获取决策失败" llm_error = False # LLM错误标志 + arguments = None # 初始化参数变量 + emoji_query = "" # <--- 在这里初始化 emoji_query try: - # 构建提示词 - + # --- 构建提示词 --- + replan_prompt_str = "" if is_re_planned: - replan_prompt = await self._build_replan_prompt( + replan_prompt_str = await self._build_replan_prompt( self._current_cycle.action_type, self._current_cycle.reasoning ) - prompt = replan_prompt - else: - replan_prompt = "" prompt = await self._build_planner_prompt( - observed_messages_str, current_mind, self.sub_mind.structured_info, replan_prompt + observed_messages_str, current_mind, self.sub_mind.structured_info, replan_prompt_str ) - payload = { - "model": global_config.llm_plan["name"], - "messages": [{"role": "user", "content": prompt}], - "tools": self.action_manager.get_planner_tool_definition(), - "tool_choice": {"type": "function", "function": {"name": "decide_reply_action"}}, - } - - # 执行LLM请求 + # --- 调用 LLM --- try: - print("prompt") - print("prompt") - print("prompt") - print(payload) - print(prompt) - response = await self.planner_llm._execute_request( - endpoint="/chat/completions", payload=payload, prompt=prompt + planner_tools = self.action_manager.get_planner_tool_definition() + _response_text, _reasoning_content, tool_calls = await self.planner_llm.generate_response_tool_async( + prompt=prompt, + tools=planner_tools, ) - print(response) + logger.debug(f"{self.log_prefix}[Planner] 原始人 LLM响应: {_response_text}") except Exception as req_e: logger.error(f"{self.log_prefix}[Planner] LLM请求执行失败: {req_e}") + action = "error" + reasoning = f"LLM请求失败: {req_e}" + llm_error = True + # 直接返回错误结果 return { - "action": "error", - "reasoning": f"LLM请求执行失败: {req_e}", + "action": action, + "reasoning": reasoning, "emoji_query": "", "current_mind": current_mind, "observed_messages": observed_messages, - "llm_error": True, + "llm_error": llm_error, } - # 处理LLM响应 - with Timer("使用工具", cycle_timers): - # 使用辅助函数处理工具调用响应 - print(1111122222222222) - print(response) + # 默认错误状态 + action = "error" + reasoning = "处理工具调用时出错" + llm_error = True - success, arguments, error_msg = process_llm_tool_response( - response, expected_tool_name="decide_reply_action", log_prefix=f"{self.log_prefix}[Planner] " - ) + # 1. 验证工具调用 + success, valid_tool_calls, error_msg = process_llm_tool_calls( + tool_calls, log_prefix=f"{self.log_prefix}[Planner] " + ) - if success: - # 提取决策参数 - action = arguments.get("action", "no_reply") - # 验证动作是否在可用动作集中 - if action not in self.action_manager.get_available_actions(): + if success and valid_tool_calls: + # 2. 提取第一个调用并获取参数 + first_tool_call = valid_tool_calls[0] + tool_name = first_tool_call.get("function", {}).get("name") + arguments = extract_tool_call_arguments(first_tool_call, None) + + # 3. 检查名称和参数 + expected_tool_name = "decide_reply_action" + if tool_name == expected_tool_name and arguments is not None: + # 4. 成功,提取决策 + extracted_action = arguments.get("action", "no_reply") + # 验证动作 + if extracted_action not in self.action_manager.get_available_actions(): logger.warning( - f"{self.log_prefix}[Planner] LLM返回了未授权的动作: {action},使用默认动作no_reply" + f"{self.log_prefix}[Planner] LLM返回了未授权的动作: {extracted_action},使用默认动作no_reply" ) action = "no_reply" - reasoning = f"LLM返回了未授权的动作: {action}" + reasoning = f"LLM返回了未授权的动作: {extracted_action}" + emoji_query = "" + llm_error = False # 视为非LLM错误,只是逻辑修正 else: + # 动作有效,使用提取的值 + action = extracted_action reasoning = arguments.get("reasoning", "未提供理由") emoji_query = arguments.get("emoji_query", "") - - # 记录决策结果 - logger.debug( - f"{self.log_prefix}[要做什么]\nPrompt:\n{prompt}\n\n决策结果: {action}, 理由: {reasoning}, 表情查询: '{emoji_query}'" - ) - else: - # 处理工具调用失败 - logger.warning(f"{self.log_prefix}[Planner] {error_msg}") - action = "error" - reasoning = error_msg - llm_error = True + llm_error = False # 成功处理 + # 记录决策结果 + logger.debug( + f"{self.log_prefix}[要做什么]\nPrompt:\n{prompt}\n\n决策结果: {action}, 理由: {reasoning}, 表情查询: '{emoji_query}'" + ) + elif tool_name != expected_tool_name: + reasoning = f"LLM返回了非预期的工具: {tool_name}" + logger.warning(f"{self.log_prefix}[Planner] {reasoning}") + else: # arguments is None + reasoning = f"无法提取工具 {tool_name} 的参数" + logger.warning(f"{self.log_prefix}[Planner] {reasoning}") + elif not success: + reasoning = f"验证工具调用失败: {error_msg}" + logger.warning(f"{self.log_prefix}[Planner] {reasoning}") + else: # not valid_tool_calls + reasoning = "LLM未返回有效的工具调用" + logger.warning(f"{self.log_prefix}[Planner] {reasoning}") + # 如果 llm_error 仍然是 True,说明在处理过程中有错误发生 except Exception as llm_e: - logger.error(f"{self.log_prefix}[Planner] Planner LLM处理过程中出错: {llm_e}") - logger.error(traceback.format_exc()) # 记录完整堆栈以便调试 + logger.error(f"{self.log_prefix}[Planner] Planner LLM处理过程中发生意外错误: {llm_e}") + logger.error(traceback.format_exc()) action = "error" - reasoning = f"LLM处理失败: {llm_e}" + reasoning = f"Planner内部处理错误: {llm_e}" llm_error = True # --- 结束 LLM 决策 --- # diff --git a/src/plugins/models/utils_model.py b/src/plugins/models/utils_model.py index 7c87cf94..2ff4c394 100644 --- a/src/plugins/models/utils_model.py +++ b/src/plugins/models/utils_model.py @@ -739,7 +739,7 @@ class LLMRequest: return response - async def generate_response_tool_async(self, prompt: str, tools: list, **kwargs) -> Union[str, Tuple]: + async def generate_response_tool_async(self, prompt: str, tools: list, **kwargs) -> tuple[str, str, list]: """异步方式根据输入的提示生成模型的响应""" # 构建请求体,不硬编码max_tokens data = { @@ -750,16 +750,18 @@ class LLMRequest: "tools": tools, } - logger.debug(f"向模型 {self.model_name} 发送工具调用请求,包含 {len(tools)} 个工具") + response = await self._execute_request(endpoint="/chat/completions", payload=data, prompt=prompt) + logger.debug(f"向模型 {self.model_name} 发送工具调用请求,包含 {len(tools)} 个工具,返回结果: {response}") # 检查响应是否包含工具调用 - if isinstance(response, tuple) and len(response) == 3: + if len(response) == 3: content, reasoning_content, tool_calls = response logger.debug(f"收到工具调用响应,包含 {len(tool_calls) if tool_calls else 0} 个工具调用") return content, reasoning_content, tool_calls else: + content, reasoning_content = response logger.debug("收到普通响应,无工具调用") - return response + return content, reasoning_content, None async def get_embedding(self, text: str) -> Union[list, None]: """异步方法:获取文本的embedding向量 diff --git a/src/plugins/utils/json_utils.py b/src/plugins/utils/json_utils.py index a76c83e3..63e39198 100644 --- a/src/plugins/utils/json_utils.py +++ b/src/plugins/utils/json_utils.py @@ -70,55 +70,6 @@ def extract_tool_call_arguments(tool_call: Dict[str, Any], default_value: Dict[s return default_result -def get_json_value( - json_obj: Dict[str, Any], key_path: str, default_value: T = None, transform_func: Callable[[Any], T] = None -) -> Union[Any, T]: - """ - 从JSON对象中按照路径提取值,支持点表示法路径,如"data.items.0.name" - - 参数: - json_obj: JSON对象(已解析的字典) - key_path: 键路径,使用点表示法,如"data.items.0.name" - default_value: 获取失败时返回的默认值 - transform_func: 可选的转换函数,用于对获取的值进行转换 - - 返回: - 路径指向的值,或在获取失败时返回default_value - """ - if not json_obj or not key_path: - return default_value - - try: - # 分割路径 - keys = key_path.split(".") - current = json_obj - - # 遍历路径 - for key in keys: - # 处理数组索引 - if key.isdigit() and isinstance(current, list): - index = int(key) - if 0 <= index < len(current): - current = current[index] - else: - return default_value - # 处理字典键 - elif isinstance(current, dict): - if key in current: - current = current[key] - else: - return default_value - else: - return default_value - - # 应用转换函数(如果提供) - if transform_func and current is not None: - return transform_func(current) - return current - except Exception as e: - logger.error(f"从JSON获取值时出错: {e}, 路径: {key_path}") - return default_value - def safe_json_dumps(obj: Any, default_value: str = "{}", ensure_ascii: bool = False, pretty: bool = False) -> str: """ @@ -144,21 +95,6 @@ def safe_json_dumps(obj: Any, default_value: str = "{}", ensure_ascii: bool = Fa return default_value -def merge_json_objects(*objects: Dict[str, Any]) -> Dict[str, Any]: - """ - 合并多个JSON对象(字典) - - 参数: - *objects: 要合并的JSON对象(字典) - - 返回: - 合并后的字典,后面的对象会覆盖前面对象的相同键 - """ - result = {} - for obj in objects: - if obj and isinstance(obj, dict): - result.update(obj) - return result def normalize_llm_response(response: Any, log_prefix: str = "") -> Tuple[bool, List[Any], str]: @@ -172,6 +108,9 @@ def normalize_llm_response(response: Any, log_prefix: str = "") -> Tuple[bool, L 返回: 元组 (成功标志, 标准化后的响应列表, 错误消息) """ + + logger.debug(f"{log_prefix}原始人 LLM响应: {response}") + # 检查是否为None if response is None: return False, [], "LLM响应为None" @@ -201,114 +140,60 @@ def normalize_llm_response(response: Any, log_prefix: str = "") -> Tuple[bool, L return True, response, "" -def process_llm_tool_calls(response: List[Any], log_prefix: str = "") -> Tuple[bool, List[Dict[str, Any]], str]: +def process_llm_tool_calls(tool_calls: List[Dict[str, Any]], log_prefix: str = "") -> Tuple[bool, List[Dict[str, Any]], str]: """ - 处理并提取LLM响应中的工具调用列表 + 处理并验证LLM响应中的工具调用列表 参数: - response: 标准化后的LLM响应列表 + tool_calls: 从LLM响应中直接获取的工具调用列表 log_prefix: 日志前缀 返回: - 元组 (成功标志, 工具调用列表, 错误消息) + 元组 (成功标志, 验证后的工具调用列表, 错误消息) """ - # 确保响应格式正确 - print(response) - print(11111111111111111) - if len(response) != 3: - return False, [], f"LLM响应元素数量不正确: 预期3个元素,实际{len(response)}个" + # 如果列表为空,表示没有工具调用,这不是错误 + if not tool_calls: + return True, [], "工具调用列表为空" - # 提取工具调用部分 - tool_calls = response[2] - - # 检查工具调用是否有效 - if tool_calls is None: - return False, [], "工具调用部分为None" - - if not isinstance(tool_calls, list): - return False, [], f"工具调用部分不是列表: {type(tool_calls).__name__}" - - if len(tool_calls) == 0: - return False, [], "工具调用列表为空" - - # 检查工具调用是否格式正确 + # 验证每个工具调用的格式 valid_tool_calls = [] for i, tool_call in enumerate(tool_calls): if not isinstance(tool_call, dict): - logger.warning(f"{log_prefix}工具调用[{i}]不是字典: {type(tool_call).__name__}") + logger.warning(f"{log_prefix}工具调用[{i}]不是字典: {type(tool_call).__name__}, 内容: {tool_call}") continue + # 检查基本结构 if tool_call.get("type") != "function": - logger.warning(f"{log_prefix}工具调用[{i}]不是函数类型: {tool_call.get('type', '未知')}") + logger.warning(f"{log_prefix}工具调用[{i}]不是function类型: type={tool_call.get('type', '未定义')}, 内容: {tool_call}") continue - if "function" not in tool_call or not isinstance(tool_call["function"], dict): - logger.warning(f"{log_prefix}工具调用[{i}]缺少function字段或格式不正确") + if "function" not in tool_call or not isinstance(tool_call.get("function"), dict): + logger.warning(f"{log_prefix}工具调用[{i}]缺少'function'字段或其类型不正确: {tool_call}") + continue + + func_details = tool_call["function"] + if "name" not in func_details or not isinstance(func_details.get("name"), str): + logger.warning(f"{log_prefix}工具调用[{i}]的'function'字段缺少'name'或类型不正确: {func_details}") + continue + if "arguments" not in func_details or not isinstance(func_details.get("arguments"), str): # 参数是字符串形式的JSON + logger.warning(f"{log_prefix}工具调用[{i}]的'function'字段缺少'arguments'或类型不正确: {func_details}") + continue + + # 可选:尝试解析参数JSON,确保其有效 + args_str = func_details["arguments"] + try: + json.loads(args_str) # 尝试解析,但不存储结果 + except json.JSONDecodeError as e: + logger.warning(f"{log_prefix}工具调用[{i}]的'arguments'不是有效的JSON字符串: {e}, 内容: {args_str[:100]}...") + continue + except Exception as e: + logger.warning(f"{log_prefix}解析工具调用[{i}]的'arguments'时发生意外错误: {e}, 内容: {args_str[:100]}...") continue valid_tool_calls.append(tool_call) - # 检查是否有有效的工具调用 - if not valid_tool_calls: - return False, [], "没有找到有效的工具调用" + if not valid_tool_calls and tool_calls: # 如果原始列表不为空,但验证后为空 + return False, [], "所有工具调用格式均无效" return True, valid_tool_calls, "" - - -def process_llm_tool_response( - response: Any, expected_tool_name: str = None, log_prefix: str = "" -) -> Tuple[bool, Dict[str, Any], str]: - """ - 处理LLM返回的工具调用响应,进行常见错误检查并提取参数 - - 参数: - response: LLM的响应,预期是[content, reasoning, tool_calls]格式的列表或元组 - expected_tool_name: 预期的工具名称,如不指定则不检查 - log_prefix: 日志前缀,用于标识日志来源 - - 返回: - 三元组(成功标志, 参数字典, 错误描述) - - 如果成功解析,返回(True, 参数字典, "") - - 如果解析失败,返回(False, {}, 错误描述) - """ - # 使用新的标准化函数 - success, normalized_response, error_msg = normalize_llm_response(response, log_prefix) - if not success: - return False, {}, error_msg - - # 新增检查:确保响应包含预期的工具调用部分 - if len(normalized_response) != 3: - # 如果长度不为3,说明LLM响应不包含工具调用部分,这在期望工具调用的上下文中是错误的 - error_msg = ( - f"LLM响应未包含预期的工具调用部分: 元素数量{len(normalized_response)},响应内容:{normalized_response}" - ) - logger.warning(f"{log_prefix}{error_msg}") - return False, {}, error_msg - - # 使用新的工具调用处理函数 - # 此时已知 normalized_response 长度必定为 3 - success, valid_tool_calls, error_msg = process_llm_tool_calls(normalized_response, log_prefix) - if not success: - return False, {}, error_msg - - # 检查是否有工具调用 - if not valid_tool_calls: - return False, {}, "没有有效的工具调用" - - # 获取第一个工具调用 - tool_call = valid_tool_calls[0] - - # 检查工具名称(如果提供了预期名称) - if expected_tool_name: - actual_name = tool_call.get("function", {}).get("name") - if actual_name != expected_tool_name: - return False, {}, f"工具名称不匹配: 预期'{expected_tool_name}',实际'{actual_name}'" - - # 提取并解析参数 - try: - arguments = extract_tool_call_arguments(tool_call, {}) - return True, arguments, "" - except Exception as e: - logger.error(f"{log_prefix}解析工具参数时出错: {e}") - return False, {}, f"解析参数失败: {str(e)}" From fdcb82b25e9e99414b876e50d21418c90f8671f5 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Mon, 28 Apr 2025 20:44:56 +0800 Subject: [PATCH 36/54] =?UTF-8?q?=E5=90=AF=E7=94=A8message=5Fsender.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/conversation.py | 3 +- src/plugins/PFC/message_sender.py | 54 +++++++++++----- src/plugins/PFC/pfc.py | 104 +++++++++++++++--------------- 3 files changed, 92 insertions(+), 69 deletions(-) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 215a6ed2..37966a76 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -9,7 +9,8 @@ from src.plugins.utils.chat_message_builder import build_readable_messages, get_ from typing import Dict, Any, Optional from ..chat.message import Message from .pfc_types import ConversationState -from .pfc import ChatObserver, GoalAnalyzer, DirectMessageSender +from .pfc import ChatObserver, GoalAnalyzer +from .message_sender import DirectMessageSender from src.common.logger_manager import get_logger from .action_planner import ActionPlanner from .observation_info import ObservationInfo diff --git a/src/plugins/PFC/message_sender.py b/src/plugins/PFC/message_sender.py index 06860af3..453cb214 100644 --- a/src/plugins/PFC/message_sender.py +++ b/src/plugins/PFC/message_sender.py @@ -1,10 +1,14 @@ +import time from typing import Optional from src.common.logger import get_module_logger from ..chat.chat_stream import ChatStream from ..chat.message import Message -from maim_message import Seg +from maim_message import UserInfo, Seg from src.plugins.chat.message import MessageSending, MessageSet from src.plugins.chat.message_sender import message_manager +from ..storage.storage import MessageStorage +from ...config.config import global_config + logger = get_module_logger("message_sender") @@ -12,14 +16,14 @@ logger = get_module_logger("message_sender") class DirectMessageSender: """直接消息发送器""" - def __init__(self): - pass + def __init__(self, private_name: str): + self.private_name = private_name + self.storage = MessageStorage() async def send_message( self, chat_stream: ChatStream, content: str, - private_name: str, reply_to_message: Optional[Message] = None, ) -> None: """发送消息到聊天流 @@ -31,21 +35,39 @@ class DirectMessageSender: """ try: # 创建消息内容 - segments = [Seg(type="text", data={"text": content})] + segments = Seg(type="seglist", data=[Seg(type="text", data=content)]) + bot_user_info = UserInfo( + user_id=global_config.BOT_QQ, + user_nickname=global_config.BOT_NICKNAME, + platform=chat_stream.platform, + ) - # 检查是否需要引用回复 - if reply_to_message: - reply_id = reply_to_message.message_id - message_sending = MessageSending(segments=segments, reply_to_id=reply_id) - else: - message_sending = MessageSending(segments=segments) + message_id=f"dm{round(time.time(), 2)}" + + message = MessageSending( + message_id=message_id, + chat_stream=chat_stream, + bot_user_info=bot_user_info, + sender_info=reply_to_message.message_info.user_info if reply_to_message else None, + message_segment=segments, + reply=reply_to_message, + is_head=True, + is_emoji=False, + thinking_start_time=time.time(), + ) + + # 处理消息 + await message.process() + + _message_json = message.to_dict() # 发送消息 - message_set = MessageSet(chat_stream, message_sending.message_id) - message_set.add_message(message_sending) - message_manager.add_message(message_set) - logger.info(f"[私聊][{private_name}]PFC消息已发送: {content}") + message_set = MessageSet(chat_stream, message_id) + message_set.add_message(message) + await message_manager.add_message(message_set) + await self.storage.store_message(message, chat_stream) + logger.info(f"[私聊][{self.private_name}]PFC消息已发送: {content}") except Exception as e: - logger.error(f"[私聊][{private_name}]PFC消息发送失败: {str(e)}") + logger.error(f"[私聊][{self.private_name}]PFC消息发送失败: {str(e)}") raise diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index b7570f30..04330886 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -318,63 +318,63 @@ class GoalAnalyzer: logger.error(f"[私聊][{self.private_name}]分析对话状态时出错: {str(e)}") return False, False, f"分析出错: {str(e)}" +# 先注释掉,万一以后出问题了还能开回来((( +# class DirectMessageSender: +# """直接发送消息到平台的发送器""" -class DirectMessageSender: - """直接发送消息到平台的发送器""" +# def __init__(self, private_name: str): +# self.logger = get_module_logger("direct_sender") +# self.storage = MessageStorage() +# self.private_name = private_name - def __init__(self, private_name: str): - self.logger = get_module_logger("direct_sender") - self.storage = MessageStorage() - self.private_name = private_name +# async def send_via_ws(self, message: MessageSending) -> None: +# try: +# await global_api.send_message(message) +# except Exception as e: +# raise ValueError(f"未找到平台:{message.message_info.platform} 的url配置,请检查配置文件") from e - async def send_via_ws(self, message: MessageSending) -> None: - try: - await global_api.send_message(message) - except Exception as e: - raise ValueError(f"未找到平台:{message.message_info.platform} 的url配置,请检查配置文件") from e +# async def send_message( +# self, +# chat_stream: ChatStream, +# content: str, +# reply_to_message: Optional[Message] = None, +# ) -> None: +# """直接发送消息到平台 - async def send_message( - self, - chat_stream: ChatStream, - content: str, - reply_to_message: Optional[Message] = None, - ) -> None: - """直接发送消息到平台 +# Args: +# chat_stream: 聊天流 +# content: 消息内容 +# reply_to_message: 要回复的消息 +# """ +# # 构建消息对象 +# message_segment = Seg(type="text", data=content) +# bot_user_info = UserInfo( +# user_id=global_config.BOT_QQ, +# user_nickname=global_config.BOT_NICKNAME, +# platform=chat_stream.platform, +# ) - Args: - chat_stream: 聊天流 - content: 消息内容 - reply_to_message: 要回复的消息 - """ - # 构建消息对象 - message_segment = Seg(type="text", data=content) - bot_user_info = UserInfo( - user_id=global_config.BOT_QQ, - user_nickname=global_config.BOT_NICKNAME, - platform=chat_stream.platform, - ) +# message = MessageSending( +# message_id=f"dm{round(time.time(), 2)}", +# chat_stream=chat_stream, +# bot_user_info=bot_user_info, +# sender_info=reply_to_message.message_info.user_info if reply_to_message else None, +# message_segment=message_segment, +# reply=reply_to_message, +# is_head=True, +# is_emoji=False, +# thinking_start_time=time.time(), +# ) - message = MessageSending( - message_id=f"dm{round(time.time(), 2)}", - chat_stream=chat_stream, - bot_user_info=bot_user_info, - sender_info=reply_to_message.message_info.user_info if reply_to_message else None, - message_segment=message_segment, - reply=reply_to_message, - is_head=True, - is_emoji=False, - thinking_start_time=time.time(), - ) +# # 处理消息 +# await message.process() - # 处理消息 - await message.process() +# _message_json = message.to_dict() - _message_json = message.to_dict() - - # 发送消息 - try: - await self.send_via_ws(message) - await self.storage.store_message(message, chat_stream) - logger.success(f"[私聊][{self.private_name}]PFC消息已发送: {content}") - except Exception as e: - logger.error(f"[私聊][{self.private_name}]PFC消息发送失败: {str(e)}") +# # 发送消息 +# try: +# await self.send_via_ws(message) +# await self.storage.store_message(message, chat_stream) +# logger.success(f"[私聊][{self.private_name}]PFC消息已发送: {content}") +# except Exception as e: +# logger.error(f"[私聊][{self.private_name}]PFC消息发送失败: {str(e)}") From 52c81a2d511b2c615f5df57c60d049791bb03de8 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Mon, 28 Apr 2025 20:51:53 +0800 Subject: [PATCH 37/54] =?UTF-8?q?=E8=A1=A5=E4=BA=86=E7=82=B9=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/message_sender.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/PFC/message_sender.py b/src/plugins/PFC/message_sender.py index 453cb214..b7493e3c 100644 --- a/src/plugins/PFC/message_sender.py +++ b/src/plugins/PFC/message_sender.py @@ -36,14 +36,18 @@ class DirectMessageSender: try: # 创建消息内容 segments = Seg(type="seglist", data=[Seg(type="text", data=content)]) + + # 获取麦麦的信息 bot_user_info = UserInfo( user_id=global_config.BOT_QQ, user_nickname=global_config.BOT_NICKNAME, platform=chat_stream.platform, ) + # 用当前时间作为message_id,和之前那套sender一样 message_id=f"dm{round(time.time(), 2)}" + # 构建消息对象 message = MessageSending( message_id=message_id, chat_stream=chat_stream, @@ -59,6 +63,7 @@ class DirectMessageSender: # 处理消息 await message.process() + # 不知道有什么用,先留下来了,和之前那套sender一样 _message_json = message.to_dict() # 发送消息 From 4c5690e0b16f91caa696f65865a910e9e96bf0c4 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Mon, 28 Apr 2025 20:58:22 +0800 Subject: [PATCH 38/54] rufff --- src/plugins/PFC/pfc.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index 04330886..29fce657 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -1,24 +1,12 @@ -# Programmable Friendly Conversationalist -# Prefrontal cortex -# import datetime - -# import asyncio from typing import List, Optional, Tuple, TYPE_CHECKING from src.common.logger import get_module_logger -from ..chat.chat_stream import ChatStream -from maim_message import UserInfo, Seg -from ..chat.message import Message from ..models.utils_model import LLMRequest from ...config.config import global_config -from src.plugins.chat.message import MessageSending -from ..message.api import global_api -from ..storage.storage import MessageStorage from .chat_observer import ChatObserver from .pfc_utils import get_items_from_json from src.individuality.individuality import Individuality from .conversation_info import ConversationInfo from .observation_info import ObservationInfo -import time from src.plugins.utils.chat_message_builder import build_readable_messages if TYPE_CHECKING: From 1a19c3296f0cb5b88472f5df266585db5199050d Mon Sep 17 00:00:00 2001 From: Bakadax Date: Mon, 28 Apr 2025 20:59:23 +0800 Subject: [PATCH 39/54] ruff --- 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 29fce657..a81d7852 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Tuple, TYPE_CHECKING +from typing import List, Tuple, TYPE_CHECKING from src.common.logger import get_module_logger from ..models.utils_model import LLMRequest from ...config.config import global_config From 8ff87397b60d793c946dd60ae7c72b3a5bceb709 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 28 Apr 2025 12:59:46 +0000 Subject: [PATCH 40/54] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/message_sender.py | 2 +- src/plugins/PFC/pfc.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/PFC/message_sender.py b/src/plugins/PFC/message_sender.py index b7493e3c..53c20374 100644 --- a/src/plugins/PFC/message_sender.py +++ b/src/plugins/PFC/message_sender.py @@ -45,7 +45,7 @@ class DirectMessageSender: ) # 用当前时间作为message_id,和之前那套sender一样 - message_id=f"dm{round(time.time(), 2)}" + message_id = f"dm{round(time.time(), 2)}" # 构建消息对象 message = MessageSending( diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index a81d7852..32755c29 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -306,6 +306,7 @@ class GoalAnalyzer: logger.error(f"[私聊][{self.private_name}]分析对话状态时出错: {str(e)}") return False, False, f"分析出错: {str(e)}" + # 先注释掉,万一以后出问题了还能开回来((( # class DirectMessageSender: # """直接发送消息到平台的发送器""" From c3e0d6651cfc59530bc19e41396909aceb1d3834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Mon, 28 Apr 2025 22:21:37 +0800 Subject: [PATCH 41/54] =?UTF-8?q?=E5=85=88=E6=9D=80=E4=B8=80=E9=83=A8?= =?UTF-8?q?=E5=88=86=E6=A0=B9=E7=9B=AE=E5=BD=95=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- CONTRIBUTE.md => depends-data/CONTRIBUTE.md | 0 interest_monitor_gui.py => scripts/interest_monitor_gui.py | 0 tool_call_benchmark.py => scripts/tool_call_benchmark.py | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename CONTRIBUTE.md => depends-data/CONTRIBUTE.md (100%) rename interest_monitor_gui.py => scripts/interest_monitor_gui.py (100%) rename tool_call_benchmark.py => scripts/tool_call_benchmark.py (100%) diff --git a/README.md b/README.md index 65e04a23..820d7d62 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ graph TD ## ✍️如何给本项目报告BUG/提交建议/做贡献 -MaiCore是一个开源项目,我们非常欢迎你的参与。你的贡献,无论是提交bug报告、功能需求还是代码pr,都对项目非常宝贵。我们非常感谢你的支持!🎉 但无序的讨论会降低沟通效率,进而影响问题的解决速度,因此在提交任何贡献前,请务必先阅读本项目的[贡献指南](CONTRIBUTE.md)(待补完) +MaiCore是一个开源项目,我们非常欢迎你的参与。你的贡献,无论是提交bug报告、功能需求还是代码pr,都对项目非常宝贵。我们非常感谢你的支持!🎉 但无序的讨论会降低沟通效率,进而影响问题的解决速度,因此在提交任何贡献前,请务必先阅读本项目的[贡献指南](depends-data/CONTRIBUTE.md)(待补完) diff --git a/CONTRIBUTE.md b/depends-data/CONTRIBUTE.md similarity index 100% rename from CONTRIBUTE.md rename to depends-data/CONTRIBUTE.md diff --git a/interest_monitor_gui.py b/scripts/interest_monitor_gui.py similarity index 100% rename from interest_monitor_gui.py rename to scripts/interest_monitor_gui.py diff --git a/tool_call_benchmark.py b/scripts/tool_call_benchmark.py similarity index 100% rename from tool_call_benchmark.py rename to scripts/tool_call_benchmark.py From 93b4a431c9a08ffd2366fe64b57341967a358923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Mon, 28 Apr 2025 22:29:11 +0800 Subject: [PATCH 42/54] =?UTF-8?q?=E6=9D=80=E5=8F=A6=E4=B8=80=E9=83=A8?= =?UTF-8?q?=E5=88=86=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ![新版麦麦开始学习.bat | 6 +++--- import_openie.py => scripts/import_openie.py | 9 ++++++--- info_extraction.py => scripts/info_extraction.py | 2 ++ .../raw_data_preprocessor.py | 3 +++ scripts/tempCodeRunnerFile.py | 1 + scripts/tool_call_benchmark.py | 12 ++++++------ 6 files changed, 21 insertions(+), 12 deletions(-) rename import_openie.py => scripts/import_openie.py (98%) rename info_extraction.py => scripts/info_extraction.py (98%) rename raw_data_preprocessor.py => scripts/raw_data_preprocessor.py (95%) create mode 100644 scripts/tempCodeRunnerFile.py diff --git a/![新版麦麦开始学习.bat b/![新版麦麦开始学习.bat index ca38689c..41fc9836 100644 --- a/![新版麦麦开始学习.bat +++ b/![新版麦麦开始学习.bat @@ -19,7 +19,7 @@ if %ERRORLEVEL% neq 0 ( ) REM 运行预处理脚本 -python "%~dp0raw_data_preprocessor.py" +python "%~dp0scripts\raw_data_preprocessor.py" if %ERRORLEVEL% neq 0 ( echo 错误: raw_data_preprocessor.py 执行失败 pause @@ -27,7 +27,7 @@ if %ERRORLEVEL% neq 0 ( ) REM 运行信息提取脚本 -python "%~dp0info_extraction.py" +python "%~dp0scripts\info_extraction.py" if %ERRORLEVEL% neq 0 ( echo 错误: info_extraction.py 执行失败 pause @@ -35,7 +35,7 @@ if %ERRORLEVEL% neq 0 ( ) REM 运行OpenIE导入脚本 -python "%~dp0import_openie.py" +python "%~dp0scripts\import_openie.py" if %ERRORLEVEL% neq 0 ( echo 错误: import_openie.py 执行失败 pause diff --git a/import_openie.py b/scripts/import_openie.py similarity index 98% rename from import_openie.py rename to scripts/import_openie.py index 43fcd21f..b669bc6b 100644 --- a/import_openie.py +++ b/scripts/import_openie.py @@ -4,7 +4,9 @@ # print("未找到quick_algo库,无法使用quick_algo算法") # print("请安装quick_algo库 - 在lib.quick_algo中,执行命令:python setup.py build_ext --inplace") - +import sys +import os +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from typing import Dict, List from src.plugins.knowledge.src.lpmmconfig import PG_NAMESPACE, global_config @@ -15,8 +17,9 @@ from src.plugins.knowledge.src.kg_manager import KGManager from src.common.logger import get_module_logger from src.plugins.knowledge.src.utils.hash import get_sha256 -# 添加在现有导入之后 -import sys + +# 添加项目根目录到 sys.path + logger = get_module_logger("LPMM知识库-OpenIE导入") diff --git a/info_extraction.py b/scripts/info_extraction.py similarity index 98% rename from info_extraction.py rename to scripts/info_extraction.py index b6ad8a9c..e1274ec6 100644 --- a/info_extraction.py +++ b/scripts/info_extraction.py @@ -4,6 +4,8 @@ import signal from concurrent.futures import ThreadPoolExecutor, as_completed from threading import Lock, Event import sys +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +# 添加项目根目录到 sys.path import tqdm diff --git a/raw_data_preprocessor.py b/scripts/raw_data_preprocessor.py similarity index 95% rename from raw_data_preprocessor.py rename to scripts/raw_data_preprocessor.py index 7b8d400c..89ee3ee1 100644 --- a/raw_data_preprocessor.py +++ b/scripts/raw_data_preprocessor.py @@ -2,10 +2,13 @@ import json import os from pathlib import Path import sys # 新增系统模块导入 +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from src.common.logger import get_module_logger logger = get_module_logger("LPMM数据库-原始数据处理") +# 添加项目根目录到 sys.path + def check_and_create_dirs(): """检查并创建必要的目录""" diff --git a/scripts/tempCodeRunnerFile.py b/scripts/tempCodeRunnerFile.py new file mode 100644 index 00000000..026931f7 --- /dev/null +++ b/scripts/tempCodeRunnerFile.py @@ -0,0 +1 @@ +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) \ No newline at end of file diff --git a/scripts/tool_call_benchmark.py b/scripts/tool_call_benchmark.py index a3e28273..1ab8bdd1 100644 --- a/scripts/tool_call_benchmark.py +++ b/scripts/tool_call_benchmark.py @@ -1,3 +1,9 @@ +import sys +import os + +# 添加项目根目录到 sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + import asyncio import time from src.plugins.models.utils_model import LLMRequest @@ -64,12 +70,6 @@ async def test_with_tool_calls(): prompt = "请分析当前天气情况,并查询今日历史上的重要事件。并且3.9和3.11谁比较大?请使用适当的工具来获取这些信息。" prompt = """ 你的名字是麦麦,你包容开放,情绪敏感,有时候有些搞怪幽默, 是一个学习心理学和脑科学的女大学生,现在在读大二,你会刷贴吧,有时候会喜欢说一些奇怪的话,喜欢刷小红书 -刚刚你的内心想法是:漂移菌提到罐罐被吃完了,可以顺着这个梗继续玩一下,比如假装委屈"那今晚的加班费是不是也要被吃掉了"或者"猫娘罢工警告"。不过薯薯和薯宝之前已经接了不少梗,漂移菌刚刚也参与了,可能话题热度还在,可以再互动一下。如果没人接话,或许可以问问大家有没有遇到过类似"代码写完但奖励被吃掉"的搞笑职场经历,换个轻松的话题方向。 - -暂时不需要使用工具。 ------------------------------------ -现在是2025-04-25 17:38:37,你正在上网,和qq群里的网友们聊天,以下是正在进行的聊天内容: -2025-04-25 17:34:08麦麦(你) 说:[表达了:顽皮、嬉戏。]; 2025-04-25 17:34:39漂移菌 说:@麦麦。(id:3936257206) 你是一只猫娘; 2025-04-25 17:34:42薯宝 说:🤣; 2025-04-25 17:34:43麦麦(你) 说:行啊 工资分我一半; From adfc8bef7d7f35ad69a92377f84b9551c4e41b4b Mon Sep 17 00:00:00 2001 From: Maple127667 <98679702+Maple127667@users.noreply.github.com> Date: Mon, 28 Apr 2025 22:30:24 +0800 Subject: [PATCH 43/54] =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=EF=BC=8C=E5=A2=9E=E5=8A=A0=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.py | 2 +- src/plugins/chat/bot.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main.py b/src/main.py index 4b5d1bd8..4e9c2abc 100644 --- a/src/main.py +++ b/src/main.py @@ -83,7 +83,7 @@ class MainSystem: ) asyncio.create_task(bot_schedule.mai_schedule_start()) - # 启动FastAPI服务器 + # 将bot.py中的chat_bot.message_process消息处理函数注册到api.py的消息处理基类中 self.app.register_message_handler(chat_bot.message_process) # 初始化个体特征 diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index ae63d121..2efb60a7 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -48,6 +48,7 @@ class ChatBot: async def message_process(self, message_data: str) -> None: """处理转化后的统一格式消息 + 这个函数本质是预处理一些数据,根据配置信息和消息内容,预处理消息,并分发到合适的消息处理器中 heart_flow模式:使用思维流系统进行回复 - 包含思维流状态管理 - 在回复前进行观察和状态更新 @@ -75,14 +76,17 @@ class ChatBot: groupinfo = message.message_info.group_info userinfo = message.message_info.user_info + #用户黑名单拦截 if userinfo.user_id in global_config.ban_user_id: logger.debug(f"用户{userinfo.user_id}被禁止回复") return + #群聊黑名单拦截 if groupinfo != None and groupinfo.group_id not in global_config.talk_allowed_groups: logger.trace(f"群{groupinfo.group_id}被禁止回复") return - + + #确认从接口发来的message是否有自定义的prompt模板信息 if message.message_info.template_info and not message.message_info.template_info.template_default: template_group_name = message.message_info.template_info.template_name template_items = message.message_info.template_info.template_items @@ -95,8 +99,11 @@ class ChatBot: template_group_name = None async def preprocess(): + #如果在私聊中 if groupinfo is None: + #是否在配置信息中开启私聊模式 if global_config.enable_friend_chat: + #是否进入PFC if global_config.enable_pfc_chatting: userinfo = message.message_info.user_info messageinfo = message.message_info @@ -109,8 +116,10 @@ class ChatBot: message.update_chat_stream(chat) await self.only_process_chat.process_message(message) await self._create_pfc_chat(message) + #禁止PFC,进入普通的心流消息处理逻辑 else: await self.heartflow_processor.process_message(message_data) + #群聊默认进入心流消息处理逻辑 else: await self.heartflow_processor.process_message(message_data) From 7b6bb27bb7e7eb3bbcfe2d8701d6ba804967f5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Mon, 28 Apr 2025 22:30:26 +0800 Subject: [PATCH 44/54] fix: Ruff --- scripts/tempCodeRunnerFile.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 scripts/tempCodeRunnerFile.py diff --git a/scripts/tempCodeRunnerFile.py b/scripts/tempCodeRunnerFile.py deleted file mode 100644 index 026931f7..00000000 --- a/scripts/tempCodeRunnerFile.py +++ /dev/null @@ -1 +0,0 @@ -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) \ No newline at end of file From b6ce33f469c6d3a877ead4d7f2ff9faf2d580ca5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 28 Apr 2025 14:30:40 +0000 Subject: [PATCH 45/54] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/import_openie.py | 1 + scripts/info_extraction.py | 1 + scripts/raw_data_preprocessor.py | 1 + 3 files changed, 3 insertions(+) diff --git a/scripts/import_openie.py b/scripts/import_openie.py index b669bc6b..26cbd8ce 100644 --- a/scripts/import_openie.py +++ b/scripts/import_openie.py @@ -6,6 +6,7 @@ import sys import os + sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from typing import Dict, List diff --git a/scripts/info_extraction.py b/scripts/info_extraction.py index e1274ec6..fdb44528 100644 --- a/scripts/info_extraction.py +++ b/scripts/info_extraction.py @@ -4,6 +4,7 @@ import signal from concurrent.futures import ThreadPoolExecutor, as_completed from threading import Lock, Event import sys + sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) # 添加项目根目录到 sys.path diff --git a/scripts/raw_data_preprocessor.py b/scripts/raw_data_preprocessor.py index 89ee3ee1..2fc30352 100644 --- a/scripts/raw_data_preprocessor.py +++ b/scripts/raw_data_preprocessor.py @@ -2,6 +2,7 @@ import json import os from pathlib import Path import sys # 新增系统模块导入 + sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) from src.common.logger import get_module_logger From e47e9f6b2d4eebe01ef93e726ef92301efddf30c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 28 Apr 2025 14:33:59 +0000 Subject: [PATCH 46/54] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/chat/bot.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index 2efb60a7..f89b3898 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -76,17 +76,17 @@ class ChatBot: groupinfo = message.message_info.group_info userinfo = message.message_info.user_info - #用户黑名单拦截 + # 用户黑名单拦截 if userinfo.user_id in global_config.ban_user_id: logger.debug(f"用户{userinfo.user_id}被禁止回复") return - #群聊黑名单拦截 + # 群聊黑名单拦截 if groupinfo != None and groupinfo.group_id not in global_config.talk_allowed_groups: logger.trace(f"群{groupinfo.group_id}被禁止回复") return - - #确认从接口发来的message是否有自定义的prompt模板信息 + + # 确认从接口发来的message是否有自定义的prompt模板信息 if message.message_info.template_info and not message.message_info.template_info.template_default: template_group_name = message.message_info.template_info.template_name template_items = message.message_info.template_info.template_items @@ -99,11 +99,11 @@ class ChatBot: template_group_name = None async def preprocess(): - #如果在私聊中 + # 如果在私聊中 if groupinfo is None: - #是否在配置信息中开启私聊模式 + # 是否在配置信息中开启私聊模式 if global_config.enable_friend_chat: - #是否进入PFC + # 是否进入PFC if global_config.enable_pfc_chatting: userinfo = message.message_info.user_info messageinfo = message.message_info @@ -116,10 +116,10 @@ class ChatBot: message.update_chat_stream(chat) await self.only_process_chat.process_message(message) await self._create_pfc_chat(message) - #禁止PFC,进入普通的心流消息处理逻辑 + # 禁止PFC,进入普通的心流消息处理逻辑 else: await self.heartflow_processor.process_message(message_data) - #群聊默认进入心流消息处理逻辑 + # 群聊默认进入心流消息处理逻辑 else: await self.heartflow_processor.process_message(message_data) From 286fef79c4845980125404d91f5456786e2fc33c Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 29 Apr 2025 01:49:51 +0800 Subject: [PATCH 47/54] =?UTF-8?q?dev=EF=BC=9A=E4=BF=AE=E5=A4=8D=E8=AE=B0?= =?UTF-8?q?=E5=BF=86=E6=9E=84=E5=BB=BA=E6=96=87=E6=9C=AC=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- llm_tool_benchmark_results.json | 145 +++++++++++++++ src/heart_flow/mai_state_manager.py | 4 +- src/heart_flow/sub_mind.py | 43 ++--- src/individuality/identity.py | 22 --- src/individuality/individuality.py | 168 ++++++++++++++++-- src/individuality/personality.py | 26 --- src/plugins/heartFC_chat/heartFC_chat.py | 116 ++++++++---- src/plugins/heartFC_chat/heartFC_generator.py | 107 ----------- .../heartFC_chat/heartflow_prompt_builder.py | 24 +-- src/plugins/memory_system/Hippocampus.py | 164 +++++++---------- src/plugins/models/utils_model.py | 1 - src/plugins/person_info/person_info.py | 4 +- src/plugins/utils/json_utils.py | 29 +-- template/bot_config_template.toml | 8 +- 14 files changed, 494 insertions(+), 367 deletions(-) create mode 100644 llm_tool_benchmark_results.json delete mode 100644 src/plugins/heartFC_chat/heartFC_generator.py diff --git a/llm_tool_benchmark_results.json b/llm_tool_benchmark_results.json new file mode 100644 index 00000000..6caa7c31 --- /dev/null +++ b/llm_tool_benchmark_results.json @@ -0,0 +1,145 @@ +{ + "测试时间": "2025-04-28 14:12:36", + "测试迭代次数": 10, + "不使用工具调用": { + "平均耗时": 4.596814393997192, + "最短耗时": 2.957131862640381, + "最长耗时": 10.121938705444336, + "标准差": 2.1705468730949593, + "所有耗时": [ + 3.18, + 4.65, + 10.12, + 3.5, + 4.46, + 4.24, + 3.23, + 6.2, + 2.96, + 3.42 + ] + }, + "不使用工具调用_详细响应": [ + { + "内容摘要": "```json\n{\n \"action\": \"text_reply\",\n \"content\": \"怎么啦?\",\n \"emoji_query\": \"友好地询问\"\n}\n```", + "推理内容摘要": "" + }, + { + "内容摘要": "decide_reply_action(\n action=\"text_reply\",\n reasoning=\"千石连续两次叫了我的名字,显然是想引起我的注意或有事要说,作为礼貌应当回应\",\n emoji_query=\"友善的回应\"\n)", + "推理内容摘要": "" + }, + { + "内容摘要": "```json\n{\n \"action\": \"text_reply\",\n \"content\": \"怎么啦?我在呢~\",\n \"emoji_query\": \"友好的询问\",\n \"reasoning\": \"由于对方连续两次提到我的名字,显然是想与我交流,应当及时给予友好回应避免冷场\"\n}\n```", + "推理内容摘要": "" + }, + { + "内容摘要": "```json\n{\n \"action\": \"text_reply\",\n \"content\": \"在呢在呢~怎么啦?\",\n \"emoji_query\": \"好奇的回应\"\n}\n```", + "推理内容摘要": "" + }, + { + "内容摘要": "decide_reply_action(\n action=\"text_reply\",\n reasoning=\"千石连续两次提到我的名字,显然需要我回应。文字回复更正式且能明确表示我在关注他的信息。\",\n emoji_query=\"友好的回应\"\n)", + "推理内容摘要": "" + }, + { + "内容摘要": "```json\n{\n \"action\": \"text_reply\",\n \"reasoning\": \"千石连续两次提到我的名字,显然是需要我的回应。作为日常交流,应该给予友善简短的答复。\",\n \"emoji_query\": \"疑惑的歪头\"\n}\n```", + "推理内容摘要": "" + }, + { + "内容摘要": "```json\n{\n \"action\": \"text_reply\",\n \"content\": \"咋啦千石~\",\n \"emoji_query\": \"好奇的询问\"\n}\n```", + "推理内容摘要": "" + }, + { + "内容摘要": "decide_reply_action\n```json\n{\n \"action\": \"text_reply\",\n \"content\": \"我在呢~怎么啦?\",\n \"emoji_query\": \"友好的关心\",\n \"reasoning\": \"千石连续两次呼唤我的名字,显然是有事情要找我或想引起我的注意。根据回复原则2(有人提到你但未回应),应该用友善的文字进行回应,并附上表达关心的表情符号来延...", + "推理内容摘要": "" + }, + { + "内容摘要": "```json\n{\n \"action\": \"text_reply\",\n \"content\": \"千石怎么啦~\",\n \"emoji_query\": \"好奇的探询\"\n}\n```", + "推理内容摘要": "" + }, + { + "内容摘要": "```json\n{\n \"action\": \"text_reply\",\n \"content\": \"怎么啦?突然叫我两次\",\n \"emoji_query\": \"好奇的疑问\"\n}\n```", + "推理内容摘要": "" + } + ], + "使用工具调用": { + "平均耗时": 8.139546775817871, + "最短耗时": 4.9980738162994385, + "最长耗时": 18.803313732147217, + "标准差": 4.008772720760647, + "所有耗时": [ + 5.81, + 18.8, + 6.06, + 8.06, + 10.07, + 6.34, + 7.9, + 6.66, + 5.0, + 6.69 + ] + }, + "使用工具调用_详细响应": [ + { + "内容摘要": "", + "推理内容摘要": "", + "工具调用数量": 0, + "工具调用详情": [] + }, + { + "内容摘要": "", + "推理内容摘要": "", + "工具调用数量": 0, + "工具调用详情": [] + }, + { + "内容摘要": "", + "推理内容摘要": "", + "工具调用数量": 0, + "工具调用详情": [] + }, + { + "内容摘要": "", + "推理内容摘要": "", + "工具调用数量": 0, + "工具调用详情": [] + }, + { + "内容摘要": "", + "推理内容摘要": "", + "工具调用数量": 0, + "工具调用详情": [] + }, + { + "内容摘要": "", + "推理内容摘要": "", + "工具调用数量": 0, + "工具调用详情": [] + }, + { + "内容摘要": "", + "推理内容摘要": "", + "工具调用数量": 0, + "工具调用详情": [] + }, + { + "内容摘要": "", + "推理内容摘要": "", + "工具调用数量": 0, + "工具调用详情": [] + }, + { + "内容摘要": "", + "推理内容摘要": "", + "工具调用数量": 0, + "工具调用详情": [] + }, + { + "内容摘要": "", + "推理内容摘要": "", + "工具调用数量": 0, + "工具调用详情": [] + } + ], + "差异百分比": 77.07 +} \ No newline at end of file diff --git a/src/heart_flow/mai_state_manager.py b/src/heart_flow/mai_state_manager.py index 0888ae1f..1743df16 100644 --- a/src/heart_flow/mai_state_manager.py +++ b/src/heart_flow/mai_state_manager.py @@ -8,8 +8,8 @@ from src.plugins.moods.moods import MoodManager logger = get_logger("mai_state") -# enable_unlimited_hfc_chat = True -enable_unlimited_hfc_chat = False +enable_unlimited_hfc_chat = True +# enable_unlimited_hfc_chat = False class MaiState(enum.Enum): diff --git a/src/heart_flow/sub_mind.py b/src/heart_flow/sub_mind.py index 50bb889e..861bf598 100644 --- a/src/heart_flow/sub_mind.py +++ b/src/heart_flow/sub_mind.py @@ -8,7 +8,7 @@ from src.individuality.individuality import Individuality import random from ..plugins.utils.prompt_builder import Prompt, global_prompt_manager from src.do_tool.tool_use import ToolUser -from src.plugins.utils.json_utils import safe_json_dumps, normalize_llm_response, process_llm_tool_calls +from src.plugins.utils.json_utils import safe_json_dumps, process_llm_tool_calls from src.heart_flow.chat_state_info import ChatStateInfo from src.plugins.chat.chat_stream import chat_manager from src.plugins.heartFC_chat.heartFC_Cycleinfo import CycleInfo @@ -20,14 +20,12 @@ logger = get_logger("sub_heartflow") def init_prompt(): prompt = "" prompt += "{extra_info}\n" - prompt += "{prompt_personality}\n" + prompt += "你的名字是{bot_name},{prompt_personality}\n" prompt += "{last_loop_prompt}\n" prompt += "{cycle_info_block}\n" prompt += "现在是{time_now},你正在上网,和qq群里的网友们聊天,以下是正在进行的聊天内容:\n{chat_observe_info}\n" prompt += "\n你现在{mood_info}\n" - prompt += ( - "请仔细阅读当前群聊内容,分析讨论话题和群成员关系,分析你刚刚发言和别人对你的发言的反应,思考你要不要回复。" - ) + prompt += "请仔细阅读当前群聊内容,分析讨论话题和群成员关系,分析你刚刚发言和别人对你的发言的反应,思考你要不要回复。然后思考你是否需要使用函数工具。" prompt += "思考并输出你的内心想法\n" prompt += "输出要求:\n" prompt += "1. 根据聊天内容生成你的想法,{hf_do_next}\n" @@ -66,7 +64,7 @@ class SubMind: self.current_mind = "" self.past_mind = [] self.structured_info = {} - + name = chat_manager.get_stream_name(self.subheartflow_id) self.log_prefix = f"[{name}] " @@ -80,8 +78,6 @@ class SubMind: # 更新活跃时间 self.last_active_time = time.time() - - # ---------- 1. 准备基础数据 ---------- # 获取现有想法和情绪状态 current_thinking_info = self.current_mind @@ -106,18 +102,7 @@ class SubMind: individuality = Individuality.get_instance() # 构建个性部分 - prompt_personality = f"你正在扮演名为{individuality.personality.bot_nickname}的人类,你" - prompt_personality += individuality.personality.personality_core - - # 随机添加个性侧面 - if individuality.personality.personality_sides: - random_side = random.choice(individuality.personality.personality_sides) - prompt_personality += f",{random_side}" - - # 随机添加身份细节 - if individuality.identity.identity_detail: - random_detail = random.choice(individuality.identity.identity_detail) - prompt_personality += f",{random_detail}" + prompt_personality = individuality.get_prompt(x_person=2, level=2) # 获取当前时间 time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) @@ -211,7 +196,7 @@ class SubMind: prompt = (await global_prompt_manager.get_prompt_async("sub_heartflow_prompt_before")).format( extra_info="", # 可以在这里添加额外信息 prompt_personality=prompt_personality, - bot_name=individuality.personality.bot_nickname, + bot_name=individuality.name, time_now=time_now, chat_observe_info=chat_observe_info, mood_info=mood_info, @@ -228,34 +213,34 @@ class SubMind: try: # 调用LLM生成响应 - response, _reasoning_content, tool_calls = await self.llm_model.generate_response_tool_async(prompt=prompt, tools=tools) + response, _reasoning_content, tool_calls = await self.llm_model.generate_response_tool_async( + prompt=prompt, tools=tools + ) logger.debug(f"{self.log_prefix} 子心流输出的原始LLM响应: {response}") - + # 直接使用LLM返回的文本响应作为 content content = response if response else "" if tool_calls: # 直接将 tool_calls 传递给处理函数 success, valid_tool_calls, error_msg = process_llm_tool_calls( - tool_calls, log_prefix=f"{self.log_prefix} " + tool_calls, log_prefix=f"{self.log_prefix} " ) - + if success and valid_tool_calls: # 记录工具调用信息 tool_calls_str = ", ".join( [call.get("function", {}).get("name", "未知工具") for call in valid_tool_calls] ) - logger.info( - f"{self.log_prefix} 模型请求调用{len(valid_tool_calls)}个工具: {tool_calls_str}" - ) + logger.info(f"{self.log_prefix} 模型请求调用{len(valid_tool_calls)}个工具: {tool_calls_str}") # 收集工具执行结果 await self._execute_tool_calls(valid_tool_calls, tool_instance) elif not success: logger.warning(f"{self.log_prefix} 处理工具调用时出错: {error_msg}") else: - logger.info(f"{self.log_prefix} 心流未使用工具") # 修改日志信息,明确是未使用工具而不是未处理 + logger.info(f"{self.log_prefix} 心流未使用工具") # 修改日志信息,明确是未使用工具而不是未处理 except Exception as e: # 处理总体异常 diff --git a/src/individuality/identity.py b/src/individuality/identity.py index 6abf0a5e..fd0d70f3 100644 --- a/src/individuality/identity.py +++ b/src/individuality/identity.py @@ -1,6 +1,5 @@ from dataclasses import dataclass from typing import List -import random @dataclass @@ -86,27 +85,6 @@ class Identity: instance.appearance = appearance return instance - def get_prompt(self, x_person, level): - """ - 获取身份特征的prompt - """ - if x_person == 2: - prompt_identity = "你" - elif x_person == 1: - prompt_identity = "我" - else: - prompt_identity = "他" - - if level == 1: - identity_detail = self.identity_detail - random.shuffle(identity_detail) - prompt_identity += identity_detail[0] - elif level == 2: - for detail in self.identity_detail: - prompt_identity += f",{detail}" - prompt_identity += "。" - return prompt_identity - def to_dict(self) -> dict: """将身份特征转换为字典格式""" return { diff --git a/src/individuality/individuality.py b/src/individuality/individuality.py index 2a489338..65e9937c 100644 --- a/src/individuality/individuality.py +++ b/src/individuality/individuality.py @@ -1,6 +1,7 @@ from typing import Optional from .personality import Personality from .identity import Identity +import random class Individuality: @@ -8,15 +9,16 @@ class Individuality: _instance = None - def __new__(cls, *args, **kwargs): - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - def __init__(self): + if Individuality._instance is not None: + raise RuntimeError("Individuality 类是单例,请使用 get_instance() 方法获取实例。") + + # 正常初始化实例属性 self.personality: Optional[Personality] = None self.identity: Optional[Identity] = None + self.name = "" + @classmethod def get_instance(cls) -> "Individuality": """获取Individuality单例实例 @@ -25,7 +27,13 @@ class Individuality: Individuality: 单例实例 """ if cls._instance is None: - cls._instance = cls() + # 实例不存在,调用 cls() 创建新实例 + # cls() 会调用 __init__ + # 因为此时 cls._instance 仍然是 None,__init__ 会正常执行初始化 + new_instance = cls() + # 将新创建的实例赋值给类变量 _instance + cls._instance = new_instance + # 返回(新创建的或已存在的)单例实例 return cls._instance def initialize( @@ -63,6 +71,8 @@ class Individuality: identity_detail=identity_detail, height=height, weight=weight, age=age, gender=gender, appearance=appearance ) + self.name = bot_nickname + def to_dict(self) -> dict: """将个体特征转换为字典格式""" return { @@ -80,16 +90,148 @@ class Individuality: instance.identity = Identity.from_dict(data["identity"]) return instance - def get_prompt(self, type, x_person, level): + def get_personality_prompt(self, level: int, x_person: int = 2) -> str: """ - 获取个体特征的prompt + 获取人格特征的prompt + + Args: + level (int): 详细程度 (1: 核心, 2: 核心+随机侧面, 3: 核心+所有侧面) + x_person (int, optional): 人称代词 (0: 无人称, 1: 我, 2: 你). 默认为 2. + + Returns: + str: 生成的人格prompt字符串 """ - if type == "personality": - return self.personality.get_prompt(x_person, level) - elif type == "identity": - return self.identity.get_prompt(x_person, level) + if x_person not in [0, 1, 2]: + return "无效的人称代词,请使用 0 (无人称), 1 (我) 或 2 (你)。" + if not self.personality: + return "人格特征尚未初始化。" + + if x_person == 2: + p_pronoun = "你" + prompt_personality = f"{p_pronoun}{self.personality.personality_core}" + elif x_person == 1: + p_pronoun = "我" + prompt_personality = f"{p_pronoun}{self.personality.personality_core}" + else: # x_person == 0 + p_pronoun = "" # 无人称 + # 对于无人称,直接描述核心特征 + prompt_personality = f"{self.personality.personality_core}" + + # 根据level添加人格侧面 + if level >= 2 and self.personality.personality_sides: + personality_sides = list(self.personality.personality_sides) + random.shuffle(personality_sides) + if level == 2: + prompt_personality += f",有时也会{personality_sides[0]}" + elif level == 3: + sides_str = "、".join(personality_sides) + prompt_personality += f",有时也会{sides_str}" + prompt_personality += "。" + return prompt_personality + + def get_identity_prompt(self, level: int, x_person: int = 2) -> str: + """ + 获取身份特征的prompt + + Args: + level (int): 详细程度 (1: 随机细节, 2: 所有细节+外貌年龄性别, 3: 同2) + x_person (int, optional): 人称代词 (0: 无人称, 1: 我, 2: 你). 默认为 2. + + Returns: + str: 生成的身份prompt字符串 + """ + if x_person not in [0, 1, 2]: + return "无效的人称代词,请使用 0 (无人称), 1 (我) 或 2 (你)。" + if not self.identity: + return "身份特征尚未初始化。" + + if x_person == 2: + i_pronoun = "你" + elif x_person == 1: + i_pronoun = "我" + else: # x_person == 0 + i_pronoun = "" # 无人称 + + identity_parts = [] + + # 根据level添加身份细节 + if level >= 1 and self.identity.identity_detail: + identity_detail = list(self.identity.identity_detail) + random.shuffle(identity_detail) + if level == 1: + identity_parts.append(f"身份是{identity_detail[0]}") + elif level >= 2: + details_str = "、".join(identity_detail) + identity_parts.append(f"身份是{details_str}") + + # 根据level添加其他身份信息 + if level >= 3: + if self.identity.appearance: + identity_parts.append(f"{self.identity.appearance}") + if self.identity.age > 0: + identity_parts.append(f"年龄大约{self.identity.age}岁") + if self.identity.gender: + identity_parts.append(f"性别是{self.identity.gender}") + + if identity_parts: + details_str = ",".join(identity_parts) + if x_person in [1, 2]: + return f"{i_pronoun},{details_str}。" + else: # x_person == 0 + # 无人称时,直接返回细节,不加代词和开头的逗号 + return f"{details_str}。" else: - return "" + if x_person in [1, 2]: + return f"{i_pronoun}的身份信息不完整。" + else: # x_person == 0 + return "身份信息不完整。" + + def get_prompt(self, level: int, x_person: int = 2) -> str: + """ + 获取合并的个体特征prompt + + Args: + level (int): 详细程度 (1: 核心/随机细节, 2: 核心+侧面/细节+其他, 3: 全部) + x_person (int, optional): 人称代词 (0: 无人称, 1: 我, 2: 你). 默认为 2. + + Returns: + str: 生成的合并prompt字符串 + """ + if x_person not in [0, 1, 2]: + return "无效的人称代词,请使用 0 (无人称), 1 (我) 或 2 (你)。" + + if not self.personality or not self.identity: + return "个体特征尚未完全初始化。" + + # 调用新的独立方法 + prompt_personality = self.get_personality_prompt(level, x_person) + prompt_identity = self.get_identity_prompt(level, x_person) + + # 移除可能存在的错误信息,只合并有效的 prompt + valid_prompts = [] + if "尚未初始化" not in prompt_personality and "无效的人称" not in prompt_personality: + valid_prompts.append(prompt_personality) + if ( + "尚未初始化" not in prompt_identity + and "无效的人称" not in prompt_identity + and "信息不完整" not in prompt_identity + ): + # 从身份 prompt 中移除代词和句号,以便更好地合并 + identity_content = prompt_identity + if x_person == 2 and identity_content.startswith("你,"): + identity_content = identity_content[2:] + elif x_person == 1 and identity_content.startswith("我,"): + identity_content = identity_content[2:] + # 对于 x_person == 0,身份提示不带前缀,无需移除 + + if identity_content.endswith("。"): + identity_content = identity_content[:-1] + valid_prompts.append(identity_content) + + # --- 合并 Prompt --- + final_prompt = " ".join(valid_prompts) + + return final_prompt.strip() def get_traits(self, factor): """ diff --git a/src/individuality/personality.py b/src/individuality/personality.py index 1b05f2d9..0ee46a3d 100644 --- a/src/individuality/personality.py +++ b/src/individuality/personality.py @@ -2,7 +2,6 @@ from dataclasses import dataclass from typing import Dict, List import json from pathlib import Path -import random @dataclass @@ -119,28 +118,3 @@ class Personality: for key, value in data.items(): setattr(instance, key, value) return instance - - def get_prompt(self, x_person, level): - # 开始构建prompt - if x_person == 2: - prompt_personality = "你" - elif x_person == 1: - prompt_personality = "我" - else: - prompt_personality = "他" - # person - - prompt_personality += self.personality_core - - if level == 2: - personality_sides = self.personality_sides - random.shuffle(personality_sides) - prompt_personality += f",{personality_sides[0]}" - elif level == 3: - personality_sides = self.personality_sides - for side in personality_sides: - prompt_personality += f",{side}" - - prompt_personality += "。" - - return prompt_personality diff --git a/src/plugins/heartFC_chat/heartFC_chat.py b/src/plugins/heartFC_chat/heartFC_chat.py index 83c43534..84a9f621 100644 --- a/src/plugins/heartFC_chat/heartFC_chat.py +++ b/src/plugins/heartFC_chat/heartFC_chat.py @@ -1,7 +1,6 @@ import asyncio import time import traceback -import random # <-- 添加导入 from typing import List, Optional, Dict, Any, Deque, Callable, Coroutine from collections import deque from src.plugins.chat.message import MessageRecv, BaseMessageInfo, MessageThinking, MessageSending @@ -14,17 +13,20 @@ from src.plugins.models.utils_model import LLMRequest from src.config.config import global_config from src.plugins.chat.utils_image import image_path_to_base64 # Local import needed after move from src.plugins.utils.timer_calculator import Timer # <--- Import Timer -from src.plugins.heartFC_chat.heartFC_generator import HeartFCGenerator from src.do_tool.tool_use import ToolUser from src.plugins.emoji_system.emoji_manager import emoji_manager from src.plugins.utils.json_utils import process_llm_tool_calls, extract_tool_call_arguments from src.heart_flow.sub_mind import SubMind from src.heart_flow.observation import Observation -from src.plugins.heartFC_chat.heartflow_prompt_builder import global_prompt_manager +from src.plugins.heartFC_chat.heartflow_prompt_builder import global_prompt_manager, prompt_builder import contextlib from src.plugins.utils.chat_message_builder import num_new_messages_since from src.plugins.heartFC_chat.heartFC_Cycleinfo import CycleInfo from .heartFC_sender import HeartFCSender +from src.plugins.chat.utils import process_llm_response +from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager +from src.plugins.moods.moods import MoodManager +from src.individuality.individuality import Individuality INITIAL_DURATION = 60.0 @@ -181,12 +183,18 @@ class HeartFChatting: self.action_manager = ActionManager() # 初始化状态控制 - self._initialized = False # 是否已初始化标志 - self._processing_lock = asyncio.Lock() # 处理锁(确保单次Plan-Replier-Sender周期) + self._initialized = False + self._processing_lock = asyncio.Lock() - # 依赖注入存储 - self.gpt_instance = HeartFCGenerator() # 文本回复生成器 - self.tool_user = ToolUser() # 工具使用实例 + # --- 移除 gpt_instance, 直接初始化 LLM 模型 --- + # self.gpt_instance = HeartFCGenerator() # <-- 移除 + self.model_normal = LLMRequest( # <-- 新增 LLM 初始化 + model=global_config.llm_normal, + temperature=global_config.llm_normal["temp"], + max_tokens=256, + request_type="response_heartflow", + ) + self.tool_user = ToolUser() self.heart_fc_sender = HeartFCSender() # LLM规划器配置 @@ -401,16 +409,15 @@ class HeartFChatting: with Timer("决策", cycle_timers): planner_result = await self._planner(current_mind, cycle_timers) - # 效果不太好,还没处理replan导致观察时间点改变的问题 - + # action = planner_result.get("action", "error") # reasoning = planner_result.get("reasoning", "未提供理由") # self._current_cycle.set_action_info(action, reasoning, False) # 在获取规划结果后检查新消息 - + # if await self._check_new_messages(planner_start_db_time): # if random.random() < 0.2: # logger.info(f"{self.log_prefix} 看到了新消息,麦麦决定重新观察和规划...") @@ -742,8 +749,8 @@ class HeartFChatting: # --- 使用 LLM 进行决策 --- # reasoning = "默认决策或获取决策失败" llm_error = False # LLM错误标志 - arguments = None # 初始化参数变量 - emoji_query = "" # <--- 在这里初始化 emoji_query + arguments = None # 初始化参数变量 + emoji_query = "" # <--- 在这里初始化 emoji_query try: # --- 构建提示词 --- @@ -756,7 +763,7 @@ class HeartFChatting: observed_messages_str, current_mind, self.sub_mind.structured_info, replan_prompt_str ) - # --- 调用 LLM --- + # --- 调用 LLM --- try: planner_tools = self.action_manager.get_planner_tool_definition() _response_text, _reasoning_content, tool_calls = await self.planner_llm.generate_response_tool_async( @@ -794,7 +801,7 @@ class HeartFChatting: first_tool_call = valid_tool_calls[0] tool_name = first_tool_call.get("function", {}).get("name") arguments = extract_tool_call_arguments(first_tool_call, None) - + # 3. 检查名称和参数 expected_tool_name = "decide_reply_action" if tool_name == expected_tool_name and arguments is not None: @@ -808,13 +815,13 @@ class HeartFChatting: action = "no_reply" reasoning = f"LLM返回了未授权的动作: {extracted_action}" emoji_query = "" - llm_error = False # 视为非LLM错误,只是逻辑修正 + llm_error = False # 视为非LLM错误,只是逻辑修正 else: # 动作有效,使用提取的值 action = extracted_action reasoning = arguments.get("reasoning", "未提供理由") emoji_query = arguments.get("emoji_query", "") - llm_error = False # 成功处理 + llm_error = False # 成功处理 # 记录决策结果 logger.debug( f"{self.log_prefix}[要做什么]\nPrompt:\n{prompt}\n\n决策结果: {action}, 理由: {reasoning}, 表情查询: '{emoji_query}'" @@ -822,13 +829,13 @@ class HeartFChatting: elif tool_name != expected_tool_name: reasoning = f"LLM返回了非预期的工具: {tool_name}" logger.warning(f"{self.log_prefix}[Planner] {reasoning}") - else: # arguments is None + else: # arguments is None reasoning = f"无法提取工具 {tool_name} 的参数" logger.warning(f"{self.log_prefix}[Planner] {reasoning}") elif not success: reasoning = f"验证工具调用失败: {error_msg}" logger.warning(f"{self.log_prefix}[Planner] {reasoning}") - else: # not valid_tool_calls + else: # not valid_tool_calls reasoning = "LLM未返回有效的工具调用" logger.warning(f"{self.log_prefix}[Planner] {reasoning}") # 如果 llm_error 仍然是 True,说明在处理过程中有错误发生 @@ -1058,9 +1065,13 @@ class HeartFChatting: # 如果最近的活动循环不是文本回复,或者没有活动循环 cycle_info_block = "\n【近期回复历史】\n(最近没有连续文本回复)\n" + individuality = Individuality.get_instance() + prompt_personality = individuality.get_prompt(x_person=2, level=2) + # 获取提示词模板并填充数据 prompt = (await global_prompt_manager.get_prompt_async("planner_prompt")).format( bot_name=global_config.BOT_NICKNAME, + prompt_personality=prompt_personality, structured_info_block=structured_info_block, chat_content_block=chat_content_block, current_mind_block=current_mind_block, @@ -1083,27 +1094,66 @@ class HeartFChatting: thinking_id: str, ) -> Optional[List[str]]: """ - 回复器 (Replier): 核心逻辑用于生成回复。 + 回复器 (Replier): 核心逻辑,负责生成回复文本。 + (已整合原 HeartFCGenerator 的功能) """ - response_set: Optional[List[str]] = None try: - response_set = await self.gpt_instance.generate_response( - structured_info=self.sub_mind.structured_info, - current_mind_info=self.sub_mind.current_mind, - reason=reason, - message=anchor_message, # Pass anchor_message positionally (matches 'message' parameter) - thinking_id=thinking_id, # Pass thinking_id positionally - ) + # 1. 获取情绪影响因子并调整模型温度 + arousal_multiplier = MoodManager.get_instance().get_arousal_multiplier() + current_temp = global_config.llm_normal["temp"] * arousal_multiplier + self.model_normal.temperature = current_temp # 动态调整温度 - if not response_set: - logger.warning(f"{self.log_prefix}[Replier-{thinking_id}] LLM生成了一个空回复集。") + # 2. 获取信息捕捉器 + info_catcher = info_catcher_manager.get_info_catcher(thinking_id) + + # 3. 构建 Prompt + with Timer("构建Prompt", {}): # 内部计时器,可选保留 + prompt = await prompt_builder.build_prompt( + build_mode="focus", + reason=reason, + current_mind_info=self.sub_mind.current_mind, + structured_info=self.sub_mind.structured_info, + message_txt="", # 似乎是固定的空字符串 + sender_name="", # 似乎是固定的空字符串 + chat_stream=anchor_message.chat_stream, + ) + + # 4. 调用 LLM 生成回复 + content = None + reasoning_content = None + model_name = "unknown_model" + try: + with Timer("LLM生成", {}): # 内部计时器,可选保留 + content, reasoning_content, model_name = await self.model_normal.generate_response(prompt) + logger.info(f"{self.log_prefix}[Replier-{thinking_id}]\\nPrompt:\\n{prompt}\\n生成回复: {content}\\n") + # 捕捉 LLM 输出信息 + info_catcher.catch_after_llm_generated( + prompt=prompt, response=content, reasoning_content=reasoning_content, model_name=model_name + ) + + except Exception as llm_e: + # 精简报错信息 + logger.error(f"{self.log_prefix}[Replier-{thinking_id}] LLM 生成失败: {llm_e}") + return None # LLM 调用失败则无法生成回复 + + # 5. 处理 LLM 响应 + if not content: + logger.warning(f"{self.log_prefix}[Replier-{thinking_id}] LLM 生成了空内容。") return None - return response_set + with Timer("处理响应", {}): # 内部计时器,可选保留 + processed_response = process_llm_response(content) + + if not processed_response: + logger.warning(f"{self.log_prefix}[Replier-{thinking_id}] 处理后的回复为空。") + return None + + return processed_response except Exception as e: - logger.error(f"{self.log_prefix}[Replier-{thinking_id}] Unexpected error in replier_work: {e}") - logger.error(traceback.format_exc()) + # 更通用的错误处理,精简信息 + logger.error(f"{self.log_prefix}[Replier-{thinking_id}] 回复生成意外失败: {e}") + # logger.error(traceback.format_exc()) # 可以取消注释这行以在调试时查看完整堆栈 return None # --- Methods moved from HeartFCController start --- diff --git a/src/plugins/heartFC_chat/heartFC_generator.py b/src/plugins/heartFC_chat/heartFC_generator.py deleted file mode 100644 index 9ad5f90c..00000000 --- a/src/plugins/heartFC_chat/heartFC_generator.py +++ /dev/null @@ -1,107 +0,0 @@ -from typing import List, Optional - - -from ..models.utils_model import LLMRequest -from ...config.config import global_config -from ..chat.message import MessageRecv -from .heartflow_prompt_builder import prompt_builder -from ..chat.utils import process_llm_response -from src.common.logger_manager import get_logger -from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager -from ..utils.timer_calculator import Timer - -from src.plugins.moods.moods import MoodManager - - -logger = get_logger("llm") - - -class HeartFCGenerator: - def __init__(self): - self.model_normal = LLMRequest( - model=global_config.llm_normal, - temperature=global_config.llm_normal["temp"], - max_tokens=256, - request_type="response_heartflow", - ) - - self.model_sum = LLMRequest( - model=global_config.llm_summary_by_topic, temperature=0.6, max_tokens=2000, request_type="relation" - ) - self.current_model_type = "r1" # 默认使用 R1 - self.current_model_name = "unknown model" - - async def generate_response( - self, - structured_info: str, - current_mind_info: str, - reason: str, - message: MessageRecv, - thinking_id: str, - ) -> Optional[List[str]]: - """根据当前模型类型选择对应的生成函数""" - - arousal_multiplier = MoodManager.get_instance().get_arousal_multiplier() - - current_model = self.model_normal - current_model.temperature = global_config.llm_normal["temp"] * arousal_multiplier # 激活度越高,温度越高 - model_response = await self._generate_response_with_model( - structured_info, current_mind_info, reason, message, current_model, thinking_id - ) - - if model_response: - model_processed_response = await self._process_response(model_response) - - return model_processed_response - else: - logger.info(f"{self.current_model_type}思考,失败") - return None - - async def _generate_response_with_model( - self, - structured_info: str, - current_mind_info: str, - reason: str, - message: MessageRecv, - model: LLMRequest, - thinking_id: str, - ) -> str: - info_catcher = info_catcher_manager.get_info_catcher(thinking_id) - - with Timer() as _build_prompt: - prompt = await prompt_builder.build_prompt( - build_mode="focus", - reason=reason, - current_mind_info=current_mind_info, - structured_info=structured_info, - message_txt="", - sender_name="", - chat_stream=message.chat_stream, - ) - # logger.info(f"构建prompt时间: {t_build_prompt.human_readable}") - - try: - content, reasoning_content, self.current_model_name = await model.generate_response(prompt) - - logger.info(f"\nprompt:{prompt}\n生成回复{content}\n") - - info_catcher.catch_after_llm_generated( - prompt=prompt, response=content, reasoning_content=reasoning_content, model_name=self.current_model_name - ) - - except Exception: - logger.exception("生成回复时出错") - return None - - return content - - async def _process_response(self, content: str) -> List[str]: - """处理响应内容,返回处理后的内容和情感标签""" - if not content: - return None - - processed_response = process_llm_response(content) - - # print(f"得到了处理后的llm返回{processed_response}") - - return processed_response diff --git a/src/plugins/heartFC_chat/heartflow_prompt_builder.py b/src/plugins/heartFC_chat/heartflow_prompt_builder.py index b77c737d..a0fb6d88 100644 --- a/src/plugins/heartFC_chat/heartflow_prompt_builder.py +++ b/src/plugins/heartFC_chat/heartflow_prompt_builder.py @@ -25,7 +25,7 @@ def init_prompt(): {chat_target} {chat_talking_prompt} 现在你想要在群里发言或者回复。\n -你需要扮演一位网名叫{bot_name}的人进行回复,这个人的特点是:"{prompt_personality} {prompt_identity}"。 +你需要扮演一位网名叫{bot_name}的人进行回复,这个人的特点是:"{prompt_personality}"。 你正在{chat_target_2},现在请你读读之前的聊天记录,然后给出日常且口语化的回复,平淡一些,你可以参考贴吧,知乎或者微博的回复风格。 看到以上聊天记录,你刚刚在想: @@ -49,7 +49,7 @@ def init_prompt(): # Planner提示词 - 优化版 Prompt( - """你的名字是{bot_name},一名大二女生,正在QQ群聊中。需要基于以下信息决定如何参与对话: + """你的名字是{bot_name},{prompt_personality},你现在正在一个群聊中。需要基于以下信息决定如何参与对话: {structured_info_block} {chat_content_block} 你的内心想法: @@ -164,9 +164,7 @@ class PromptBuilder: async def _build_prompt_focus(self, reason, current_mind_info, structured_info, chat_stream) -> tuple[str, str]: individuality = Individuality.get_instance() - prompt_personality = individuality.get_prompt(type="personality", x_person=2, level=1) - prompt_identity = individuality.get_prompt(type="identity", x_person=2, level=1) - + prompt_personality = individuality.get_prompt(x_person=0, level=2) # 日程构建 # schedule_prompt = f'''你现在正在做的事情是:{bot_schedule.get_current_num_task(num = 1,time_info = False)}''' @@ -214,7 +212,6 @@ class PromptBuilder: chat_talking_prompt=chat_talking_prompt, bot_name=global_config.BOT_NICKNAME, prompt_personality=prompt_personality, - prompt_identity=prompt_identity, chat_target_2=await global_prompt_manager.get_prompt_async("chat_target_group2") if chat_in_group else await global_prompt_manager.get_prompt_async("chat_target_private2"), @@ -230,21 +227,8 @@ class PromptBuilder: return prompt async def _build_prompt_normal(self, chat_stream, message_txt: str, sender_name: str = "某人") -> tuple[str, str]: - # 开始构建prompt - prompt_personality = "你" - # person individuality = Individuality.get_instance() - - personality_core = individuality.personality.personality_core - prompt_personality += personality_core - - personality_sides = individuality.personality.personality_sides - random.shuffle(personality_sides) - prompt_personality += f",{personality_sides[0]}" - - identity_detail = individuality.identity.identity_detail - random.shuffle(identity_detail) - prompt_personality += f",{identity_detail[0]}" + prompt_personality = individuality.get_prompt(x_person=2, level=2) # 关系 who_chat_in_group = [ diff --git a/src/plugins/memory_system/Hippocampus.py b/src/plugins/memory_system/Hippocampus.py index 10677317..710fc142 100644 --- a/src/plugins/memory_system/Hippocampus.py +++ b/src/plugins/memory_system/Hippocampus.py @@ -14,51 +14,14 @@ from ...common.database import db from ...plugins.models.utils_model import LLMRequest from src.common.logger_manager import get_logger from src.plugins.memory_system.sample_distribution import MemoryBuildScheduler # 分布生成器 +from ..utils.chat_message_builder import ( + get_raw_msg_by_timestamp, + build_readable_messages, +) # 导入 build_readable_messages +from ..chat.utils import translate_timestamp_to_human_readable from .memory_config import MemoryConfig -def get_closest_chat_from_db(length: int, timestamp: str): - # print(f"获取最接近指定时间戳的聊天记录,长度: {length}, 时间戳: {timestamp}") - # print(f"当前时间: {timestamp},转换后时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(timestamp))}") - chat_records = [] - closest_record = db.messages.find_one({"time": {"$lte": timestamp}}, sort=[("time", -1)]) - # print(f"最接近的记录: {closest_record}") - if closest_record: - closest_time = closest_record["time"] - chat_id = closest_record["chat_id"] # 获取chat_id - # 获取该时间戳之后的length条消息,保持相同的chat_id - chat_records = list( - db.messages.find( - { - "time": {"$gt": closest_time}, - "chat_id": chat_id, # 添加chat_id过滤 - } - ) - .sort("time", 1) - .limit(length) - ) - # print(f"获取到的记录: {chat_records}") - length = len(chat_records) - # print(f"获取到的记录长度: {length}") - # 转换记录格式 - formatted_records = [] - for record in chat_records: - # 兼容行为,前向兼容老数据 - formatted_records.append( - { - "_id": record["_id"], - "time": record["time"], - "chat_id": record["chat_id"], - "detailed_plain_text": record.get("detailed_plain_text", ""), # 添加文本内容 - "memorized_times": record.get("memorized_times", 0), # 添加记忆次数 - } - ) - - return formatted_records - - return [] - - def calculate_information_content(text): """计算文本的信息量(熵)""" char_count = Counter(text) @@ -263,16 +226,17 @@ class Hippocampus: @staticmethod def find_topic_llm(text, topic_num): prompt = ( - f"这是一段文字:{text}。请你从这段话中总结出最多{topic_num}个关键的概念,可以是名词,动词,或者特定人物,帮我列出来," + f"这是一段文字:\n{text}\n\n请你从这段话中总结出最多{topic_num}个关键的概念,可以是名词,动词,或者特定人物,帮我列出来," f"将主题用逗号隔开,并加上<>,例如<主题1>,<主题2>......尽可能精简。只需要列举最多{topic_num}个话题就好,不要有序号,不要告诉我其他内容。" f"如果确定找不出主题或者没有明显主题,返回。" ) return prompt @staticmethod - def topic_what(text, topic, time_info): + def topic_what(text, topic): + # 不再需要 time_info 参数 prompt = ( - f'这是一段文字,{time_info}:{text}。我想让你基于这段文字来概括"{topic}"这个概念,帮我总结成一句自然的话,' + f'这是一段文字:\n{text}\n\n我想让你基于这段文字来概括"{topic}"这个概念,帮我总结成一句自然的话,' f"可以包含时间和人物,以及具体的观点。只输出这句话就好" ) return prompt @@ -845,9 +809,12 @@ class EntorhinalCortex: ) timestamps = sample_scheduler.get_timestamp_array() - logger.info(f"回忆往事: {[time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(ts)) for ts in timestamps]}") + # 使用 translate_timestamp_to_human_readable 并指定 mode="normal" + readable_timestamps = [translate_timestamp_to_human_readable(ts, mode="normal") for ts in timestamps] + logger.info(f"回忆往事: {readable_timestamps}") chat_samples = [] for timestamp in timestamps: + # 调用修改后的 random_get_msg_snippet messages = self.random_get_msg_snippet( timestamp, self.config.build_memory_sample_length, max_memorized_time_per_msg ) @@ -862,22 +829,45 @@ class EntorhinalCortex: @staticmethod def random_get_msg_snippet(target_timestamp: float, chat_size: int, max_memorized_time_per_msg: int) -> list: - """从数据库中随机获取指定时间戳附近的消息片段""" + """从数据库中随机获取指定时间戳附近的消息片段 (使用 chat_message_builder)""" try_count = 0 + time_window_seconds = random.randint(300, 1800) # 随机时间窗口,5到30分钟 + while try_count < 3: - messages = get_closest_chat_from_db(length=chat_size, timestamp=target_timestamp) + # 定义时间范围:从目标时间戳开始,向后推移 time_window_seconds + timestamp_start = target_timestamp + timestamp_end = target_timestamp + time_window_seconds + + # 使用 chat_message_builder 的函数获取消息 + # limit_mode='earliest' 获取这个时间窗口内最早的 chat_size 条消息 + messages = get_raw_msg_by_timestamp( + timestamp_start=timestamp_start, timestamp_end=timestamp_end, limit=chat_size, limit_mode="earliest" + ) + if messages: + # 检查获取到的所有消息是否都未达到最大记忆次数 + all_valid = True for message in messages: - if message["memorized_times"] >= max_memorized_time_per_msg: - messages = None + if message.get("memorized_times", 0) >= max_memorized_time_per_msg: + all_valid = False break - if messages: + + # 如果所有消息都有效 + if all_valid: + # 更新数据库中的记忆次数 for message in messages: + # 确保在更新前获取最新的 memorized_times,以防万一 + current_memorized_times = message.get("memorized_times", 0) db.messages.update_one( - {"_id": message["_id"]}, {"$set": {"memorized_times": message["memorized_times"] + 1}} + {"_id": message["_id"]}, {"$set": {"memorized_times": current_memorized_times + 1}} ) - return messages + return messages # 直接返回原始的消息列表 + + # 如果获取失败或消息无效,增加尝试次数 try_count += 1 + target_timestamp -= 120 # 如果第一次尝试失败,稍微向前调整时间戳再试 + + # 三次尝试都失败,返回 None return None async def sync_memory_to_db(self): @@ -1113,86 +1103,70 @@ class ParahippocampalGyrus: """压缩和总结消息内容,生成记忆主题和摘要。 Args: - messages (list): 消息列表,每个消息是一个字典,包含以下字段: - - time: float, 消息的时间戳 - - detailed_plain_text: str, 消息的详细文本内容 + messages (list): 消息列表,每个消息是一个字典,包含数据库消息结构。 compress_rate (float, optional): 压缩率,用于控制生成的主题数量。默认为0.1。 Returns: tuple: (compressed_memory, similar_topics_dict) - compressed_memory: set, 压缩后的记忆集合,每个元素是一个元组 (topic, summary) - - topic: str, 记忆主题 - - summary: str, 主题的摘要描述 - - similar_topics_dict: dict, 相似主题字典,key为主题,value为相似主题列表 - 每个相似主题是一个元组 (similar_topic, similarity) - - similar_topic: str, 相似的主题 - - similarity: float, 相似度分数(0-1之间) + - similar_topics_dict: dict, 相似主题字典 Process: - 1. 合并消息文本并生成时间信息 - 2. 使用LLM提取关键主题 - 3. 过滤掉包含禁用关键词的主题 - 4. 为每个主题生成摘要 - 5. 查找与现有记忆中的相似主题 + 1. 使用 build_readable_messages 生成包含时间、人物信息的格式化文本。 + 2. 使用LLM提取关键主题。 + 3. 过滤掉包含禁用关键词的主题。 + 4. 为每个主题生成摘要。 + 5. 查找与现有记忆中的相似主题。 """ if not messages: return set(), {} - # 合并消息文本,同时保留时间信息 - input_text = "" - time_info = "" - # 计算最早和最晚时间 - earliest_time = min(msg["time"] for msg in messages) - latest_time = max(msg["time"] for msg in messages) + # 1. 使用 build_readable_messages 生成格式化文本 + # build_readable_messages 只返回一个字符串,不需要解包 + input_text = await build_readable_messages( + messages, + merge_messages=True, # 合并连续消息 + timestamp_mode="normal", # 使用 'YYYY-MM-DD HH:MM:SS' 格式 + replace_bot_name=False, # 保留原始用户名 + ) - earliest_dt = datetime.datetime.fromtimestamp(earliest_time) - latest_dt = datetime.datetime.fromtimestamp(latest_time) + # 如果生成的可读文本为空(例如所有消息都无效),则直接返回 + if not input_text: + logger.warning("无法从提供的消息生成可读文本,跳过记忆压缩。") + return set(), {} - # 如果是同一年 - if earliest_dt.year == latest_dt.year: - earliest_str = earliest_dt.strftime("%m-%d %H:%M:%S") - latest_str = latest_dt.strftime("%m-%d %H:%M:%S") - time_info += f"是在{earliest_dt.year}年,{earliest_str} 到 {latest_str} 的对话:\n" - else: - earliest_str = earliest_dt.strftime("%Y-%m-%d %H:%M:%S") - latest_str = latest_dt.strftime("%Y-%m-%d %H:%M:%S") - time_info += f"是从 {earliest_str} 到 {latest_str} 的对话:\n" - - for msg in messages: - input_text += f"{msg['detailed_plain_text']}\n" - - logger.debug(input_text) + logger.debug(f"用于压缩的格式化文本:\n{input_text}") + # 2. 使用LLM提取关键主题 topic_num = self.hippocampus.calculate_topic_num(input_text, compress_rate) topics_response = await self.hippocampus.llm_topic_judge.generate_response( self.hippocampus.find_topic_llm(input_text, topic_num) ) - # 使用正则表达式提取<>中的内容 + # 提取<>中的内容 topics = re.findall(r"<([^>]+)>", topics_response[0]) - # 如果没有找到<>包裹的内容,返回['none'] if not topics: topics = ["none"] else: - # 处理提取出的话题 topics = [ topic.strip() for topic in ",".join(topics).replace(",", ",").replace("、", ",").replace(" ", ",").split(",") if topic.strip() ] - # 过滤掉包含禁用关键词的topic + # 3. 过滤掉包含禁用关键词的topic filtered_topics = [ topic for topic in topics if not any(keyword in topic for keyword in self.config.memory_ban_words) ] logger.debug(f"过滤后话题: {filtered_topics}") - # 创建所有话题的请求任务 + # 4. 创建所有话题的摘要生成任务 tasks = [] for topic in filtered_topics: - topic_what_prompt = self.hippocampus.topic_what(input_text, topic, time_info) + # 调用修改后的 topic_what,不再需要 time_info + topic_what_prompt = self.hippocampus.topic_what(input_text, topic) try: task = self.hippocampus.llm_summary_by_topic.generate_response_async(topic_what_prompt) tasks.append((topic.strip(), task)) diff --git a/src/plugins/models/utils_model.py b/src/plugins/models/utils_model.py index 2ff4c394..e8ccf1ca 100644 --- a/src/plugins/models/utils_model.py +++ b/src/plugins/models/utils_model.py @@ -750,7 +750,6 @@ class LLMRequest: "tools": tools, } - response = await self._execute_request(endpoint="/chat/completions", payload=data, prompt=prompt) logger.debug(f"向模型 {self.model_name} 发送工具调用请求,包含 {len(tools)} 个工具,返回结果: {response}") # 检查响应是否包含工具调用 diff --git a/src/plugins/person_info/person_info.py b/src/plugins/person_info/person_info.py index d7300063..2c9fb72b 100644 --- a/src/plugins/person_info/person_info.py +++ b/src/plugins/person_info/person_info.py @@ -180,10 +180,10 @@ class PersonInfoManager: existing_names = "" while current_try < max_retries: individuality = Individuality.get_instance() - prompt_personality = individuality.get_prompt(type="personality", x_person=2, level=1) + prompt_personality = individuality.get_prompt(x_person=2, level=1) bot_name = individuality.personality.bot_nickname - qv_name_prompt = f"你是{bot_name},你{prompt_personality}" + qv_name_prompt = f"你是{bot_name},{prompt_personality}" qv_name_prompt += f"现在你想给一个用户取一个昵称,用户是的qq昵称是{user_nickname}," qv_name_prompt += f"用户的qq群昵称名是{user_cardname}," if user_avatar: diff --git a/src/plugins/utils/json_utils.py b/src/plugins/utils/json_utils.py index 63e39198..ddcbca43 100644 --- a/src/plugins/utils/json_utils.py +++ b/src/plugins/utils/json_utils.py @@ -1,6 +1,6 @@ import json import logging -from typing import Any, Dict, TypeVar, List, Union, Callable, Tuple +from typing import Any, Dict, TypeVar, List, Union, Tuple # 定义类型变量用于泛型类型提示 T = TypeVar("T") @@ -70,7 +70,6 @@ def extract_tool_call_arguments(tool_call: Dict[str, Any], default_value: Dict[s return default_result - def safe_json_dumps(obj: Any, default_value: str = "{}", ensure_ascii: bool = False, pretty: bool = False) -> str: """ 安全地将Python对象序列化为JSON字符串 @@ -95,8 +94,6 @@ def safe_json_dumps(obj: Any, default_value: str = "{}", ensure_ascii: bool = Fa return default_value - - def normalize_llm_response(response: Any, log_prefix: str = "") -> Tuple[bool, List[Any], str]: """ 标准化LLM响应格式,将各种格式(如元组)转换为统一的列表格式 @@ -108,9 +105,9 @@ def normalize_llm_response(response: Any, log_prefix: str = "") -> Tuple[bool, L 返回: 元组 (成功标志, 标准化后的响应列表, 错误消息) """ - + logger.debug(f"{log_prefix}原始人 LLM响应: {response}") - + # 检查是否为None if response is None: return False, [], "LLM响应为None" @@ -140,7 +137,9 @@ def normalize_llm_response(response: Any, log_prefix: str = "") -> Tuple[bool, L return True, response, "" -def process_llm_tool_calls(tool_calls: List[Dict[str, Any]], log_prefix: str = "") -> Tuple[bool, List[Dict[str, Any]], str]: +def process_llm_tool_calls( + tool_calls: List[Dict[str, Any]], log_prefix: str = "" +) -> Tuple[bool, List[Dict[str, Any]], str]: """ 处理并验证LLM响应中的工具调用列表 @@ -165,7 +164,9 @@ def process_llm_tool_calls(tool_calls: List[Dict[str, Any]], log_prefix: str = " # 检查基本结构 if tool_call.get("type") != "function": - logger.warning(f"{log_prefix}工具调用[{i}]不是function类型: type={tool_call.get('type', '未定义')}, 内容: {tool_call}") + logger.warning( + f"{log_prefix}工具调用[{i}]不是function类型: type={tool_call.get('type', '未定义')}, 内容: {tool_call}" + ) continue if "function" not in tool_call or not isinstance(tool_call.get("function"), dict): @@ -176,16 +177,20 @@ def process_llm_tool_calls(tool_calls: List[Dict[str, Any]], log_prefix: str = " if "name" not in func_details or not isinstance(func_details.get("name"), str): logger.warning(f"{log_prefix}工具调用[{i}]的'function'字段缺少'name'或类型不正确: {func_details}") continue - if "arguments" not in func_details or not isinstance(func_details.get("arguments"), str): # 参数是字符串形式的JSON + if "arguments" not in func_details or not isinstance( + func_details.get("arguments"), str + ): # 参数是字符串形式的JSON logger.warning(f"{log_prefix}工具调用[{i}]的'function'字段缺少'arguments'或类型不正确: {func_details}") continue # 可选:尝试解析参数JSON,确保其有效 args_str = func_details["arguments"] try: - json.loads(args_str) # 尝试解析,但不存储结果 + json.loads(args_str) # 尝试解析,但不存储结果 except json.JSONDecodeError as e: - logger.warning(f"{log_prefix}工具调用[{i}]的'arguments'不是有效的JSON字符串: {e}, 内容: {args_str[:100]}...") + logger.warning( + f"{log_prefix}工具调用[{i}]的'arguments'不是有效的JSON字符串: {e}, 内容: {args_str[:100]}..." + ) continue except Exception as e: logger.warning(f"{log_prefix}解析工具调用[{i}]的'arguments'时发生意外错误: {e}, 内容: {args_str[:100]}...") @@ -193,7 +198,7 @@ def process_llm_tool_calls(tool_calls: List[Dict[str, Any]], log_prefix: str = " valid_tool_calls.append(tool_call) - if not valid_tool_calls and tool_calls: # 如果原始列表不为空,但验证后为空 + if not valid_tool_calls and tool_calls: # 如果原始列表不为空,但验证后为空 return False, [], "所有工具调用格式均无效" return True, valid_tool_calls, "" diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index afb65e89..03eb0e6c 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -48,12 +48,10 @@ personality_sides = [ identity_detail = [ "身份特点", "身份特点", -]# 条数任意,不能为0, 该选项还在调试中,可能未完全生效 +]# 条数任意,不能为0, 该选项还在调试中 #外貌特征 -height = 170 # 身高 单位厘米 该选项还在调试中,暂时未生效 -weight = 50 # 体重 单位千克 该选项还在调试中,暂时未生效 -age = 20 # 年龄 单位岁 该选项还在调试中,暂时未生效 -gender = "男" # 性别 该选项还在调试中,暂时未生效 +age = 20 # 年龄 单位岁 +gender = "男" # 性别 appearance = "用几句话描述外貌特征" # 外貌特征 该选项还在调试中,暂时未生效 [schedule] From de309c207355721f94cf70a4aa56340013d84e6f Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Tue, 29 Apr 2025 12:33:23 +0800 Subject: [PATCH 48/54] =?UTF-8?q?better=EF=BC=9A=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=AE=B0=E5=BF=86=E7=B3=BB=E7=BB=9F=EF=BC=8C=E8=AE=B0=E5=BF=86?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E7=8E=B0=E5=8F=AF=E4=BB=A5=E6=95=B4=E5=90=88?= =?UTF-8?q?=E8=AE=B0=E5=BF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/config.py | 14 ++ src/main.py | 10 + .../heartFC_chat/heartflow_prompt_builder.py | 3 - src/plugins/memory_system/Hippocampus.py | 193 ++++++++++++++++-- src/plugins/memory_system/memory_config.py | 26 ++- .../person_info/relationship_manager.py | 30 --- template/bot_config_template.toml | 12 +- 7 files changed, 221 insertions(+), 67 deletions(-) diff --git a/src/config/config.py b/src/config/config.py index acbc0cb2..ebfc444c 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -234,6 +234,10 @@ class BotConfig: forget_memory_interval: int = 600 # 记忆遗忘间隔(秒) memory_forget_time: int = 24 # 记忆遗忘时间(小时) memory_forget_percentage: float = 0.01 # 记忆遗忘比例 + + consolidate_memory_interval: int = 1000 # 记忆整合间隔(秒) + consolidation_similarity_threshold: float = 0.7 # 相似度阈值 + consolidate_memory_percentage: float = 0.01 # 检查节点比例 memory_ban_words: list = field( default_factory=lambda: ["表情包", "图片", "回复", "聊天记录"] @@ -594,6 +598,16 @@ class BotConfig: config.build_memory_sample_length = memory_config.get( "build_memory_sample_length", config.build_memory_sample_length ) + if config.INNER_VERSION in SpecifierSet(">=1.5.1"): + config.consolidate_memory_interval = memory_config.get( + "consolidate_memory_interval", config.consolidate_memory_interval + ) + config.consolidation_similarity_threshold = memory_config.get( + "consolidation_similarity_threshold", config.consolidation_similarity_threshold + ) + config.consolidate_memory_percentage = memory_config.get( + "consolidate_memory_percentage", config.consolidate_memory_percentage + ) def remote(parent: dict): remote_config = parent["remote"] diff --git a/src/main.py b/src/main.py index 4e9c2abc..047e075f 100644 --- a/src/main.py +++ b/src/main.py @@ -121,6 +121,7 @@ class MainSystem: tasks = [ self.build_memory_task(), self.forget_memory_task(), + self.consolidate_memory_task(), self.print_mood_task(), self.remove_recalled_message_task(), emoji_manager.start_periodic_check_register(), @@ -145,6 +146,15 @@ class MainSystem: print("\033[1;32m[记忆遗忘]\033[0m 开始遗忘记忆...") await HippocampusManager.get_instance().forget_memory(percentage=global_config.memory_forget_percentage) print("\033[1;32m[记忆遗忘]\033[0m 记忆遗忘完成") + + @staticmethod + async def consolidate_memory_task(): + """记忆整合任务""" + while True: + await asyncio.sleep(global_config.consolidate_memory_interval) + print("\033[1;32m[记忆整合]\033[0m 开始整合记忆...") + await HippocampusManager.get_instance().consolidate_memory() + print("\033[1;32m[记忆整合]\033[0m 记忆整合完成") async def print_mood_task(self): """打印情绪状态""" diff --git a/src/plugins/heartFC_chat/heartflow_prompt_builder.py b/src/plugins/heartFC_chat/heartflow_prompt_builder.py index a0fb6d88..d876d6ab 100644 --- a/src/plugins/heartFC_chat/heartflow_prompt_builder.py +++ b/src/plugins/heartFC_chat/heartflow_prompt_builder.py @@ -221,9 +221,6 @@ class PromptBuilder: moderation_prompt=await global_prompt_manager.get_prompt_async("moderation_prompt"), ) - prompt = await relationship_manager.convert_all_person_sign_to_person_name(prompt) - prompt = parse_text_timestamps(prompt, mode="lite") - return prompt async def _build_prompt_normal(self, chat_stream, message_txt: str, sender_name: str = "某人") -> tuple[str, str]: diff --git a/src/plugins/memory_system/Hippocampus.py b/src/plugins/memory_system/Hippocampus.py index 710fc142..5cca0d07 100644 --- a/src/plugins/memory_system/Hippocampus.py +++ b/src/plugins/memory_system/Hippocampus.py @@ -195,6 +195,7 @@ class Hippocampus: self.config = None def initialize(self, global_config): + # 使用导入的 MemoryConfig dataclass 和其 from_global_config 方法 self.config = MemoryConfig.from_global_config(global_config) # 初始化子组件 self.entorhinal_cortex = EntorhinalCortex(self) @@ -237,7 +238,7 @@ class Hippocampus: # 不再需要 time_info 参数 prompt = ( f'这是一段文字:\n{text}\n\n我想让你基于这段文字来概括"{topic}"这个概念,帮我总结成一句自然的话,' - f"可以包含时间和人物,以及具体的观点。只输出这句话就好" + f"要求包含对这个概念的定义,内容,知识,可以包含时间和人物。只输出这句话就好" ) return prompt @@ -795,7 +796,7 @@ class EntorhinalCortex: def get_memory_sample(self): """从数据库获取记忆样本""" # 硬编码:每条消息最大记忆次数 - max_memorized_time_per_msg = 3 + max_memorized_time_per_msg = 2 # 创建双峰分布的记忆调度器 sample_scheduler = MemoryBuildScheduler( @@ -1337,26 +1338,56 @@ class ParahippocampalGyrus: logger.info("[遗忘] 开始检查节点...") node_check_start = time.time() for node in nodes_to_check: + # 检查节点是否存在,以防在迭代中被移除(例如边移除导致) + if node not in self.memory_graph.G: + continue + node_data = self.memory_graph.G.nodes[node] + + # 首先获取记忆项 + memory_items = node_data.get("memory_items", []) + if not isinstance(memory_items, list): + memory_items = [memory_items] if memory_items else [] + + # 新增:检查节点是否为空 + if not memory_items: + try: + self.memory_graph.G.remove_node(node) + node_changes["removed"].append(f"{node}(空节点)") # 标记为空节点移除 + logger.debug(f"[遗忘] 移除了空的节点: {node}") + except nx.NetworkXError as e: + logger.warning(f"[遗忘] 移除空节点 {node} 时发生错误(可能已被移除): {e}") + continue # 处理下一个节点 + + # --- 如果节点不为空,则执行原来的不活跃检查和随机移除逻辑 --- last_modified = node_data.get("last_modified", current_time) - + # 条件1:检查是否长时间未修改 (超过24小时) if current_time - last_modified > 3600 * 24: - memory_items = node_data.get("memory_items", []) - if not isinstance(memory_items, list): - memory_items = [memory_items] if memory_items else [] - + # 条件2:再次确认节点包含记忆项(理论上已确认,但作为保险) if memory_items: current_count = len(memory_items) - removed_item = random.choice(memory_items) - memory_items.remove(removed_item) + # 如果列表非空,才进行随机选择 + if current_count > 0: + removed_item = random.choice(memory_items) + try: + memory_items.remove(removed_item) - if memory_items: - self.memory_graph.G.nodes[node]["memory_items"] = memory_items - self.memory_graph.G.nodes[node]["last_modified"] = current_time - node_changes["reduced"].append(f"{node} (数量: {current_count} -> {len(memory_items)})") - else: - self.memory_graph.G.remove_node(node) - node_changes["removed"].append(node) + # 条件3:检查移除后 memory_items 是否变空 + if memory_items: # 如果移除后列表不为空 + # self.memory_graph.G.nodes[node]["memory_items"] = memory_items # 直接修改列表即可 + self.memory_graph.G.nodes[node]["last_modified"] = current_time # 更新修改时间 + node_changes["reduced"].append(f"{node} (数量: {current_count} -> {len(memory_items)})") + else: # 如果移除后列表为空 + # 尝试移除节点,处理可能的错误 + try: + self.memory_graph.G.remove_node(node) + node_changes["removed"].append(f"{node}(遗忘清空)") # 标记为遗忘清空 + logger.debug(f"[遗忘] 节点 {node} 因移除最后一项而被清空。") + except nx.NetworkXError as e: + logger.warning(f"[遗忘] 尝试移除节点 {node} 时发生错误(可能已被移除):{e}") + except ValueError: + # 这个错误理论上不应发生,因为 removed_item 来自 memory_items + logger.warning(f"[遗忘] 尝试从节点 '{node}' 移除不存在的项目 '{removed_item[:30]}...'") node_check_end = time.time() logger.info(f"[遗忘] 节点检查耗时: {node_check_end - node_check_start:.2f}秒") @@ -1395,6 +1426,116 @@ class ParahippocampalGyrus: end_time = time.time() logger.info(f"[遗忘] 总耗时: {end_time - start_time:.2f}秒") + async def operation_consolidate_memory(self): + """整合记忆:合并节点内相似的记忆项""" + start_time = time.time() + percentage = self.config.consolidate_memory_percentage + similarity_threshold = self.config.consolidation_similarity_threshold + logger.info(f"[整合] 开始检查记忆节点... 检查比例: {percentage:.2%}, 合并阈值: {similarity_threshold}") + + # 获取所有至少有2条记忆项的节点 + eligible_nodes = [] + for node, data in self.memory_graph.G.nodes(data=True): + memory_items = data.get("memory_items", []) + if isinstance(memory_items, list) and len(memory_items) >= 2: + eligible_nodes.append(node) + + if not eligible_nodes: + logger.info("[整合] 没有找到包含多个记忆项的节点,无需整合。") + return + + # 计算需要检查的节点数量 + check_nodes_count = max(1, min(len(eligible_nodes), int(len(eligible_nodes) * percentage))) + + # 随机抽取节点进行检查 + try: + nodes_to_check = random.sample(eligible_nodes, check_nodes_count) + except ValueError as e: + logger.error(f"[整合] 抽样节点时出错: {e}") + return + + logger.info(f"[整合] 将检查 {len(nodes_to_check)} / {len(eligible_nodes)} 个符合条件的节点。") + + merged_count = 0 + nodes_modified = set() + current_timestamp = datetime.datetime.now().timestamp() + + for node in nodes_to_check: + node_data = self.memory_graph.G.nodes[node] + memory_items = node_data.get("memory_items", []) + if not isinstance(memory_items, list) or len(memory_items) < 2: + continue # 双重检查,理论上不会进入 + + items_copy = list(memory_items) # 创建副本以安全迭代和修改 + + # 遍历所有记忆项组合 + for item1, item2 in combinations(items_copy, 2): + # 确保 item1 和 item2 仍然存在于原始列表中(可能已被之前的合并移除) + if item1 not in memory_items or item2 not in memory_items: + continue + + similarity = self._calculate_item_similarity(item1, item2) + + if similarity >= similarity_threshold: + logger.debug(f"[整合] 节点 '{node}' 中发现相似项 (相似度: {similarity:.2f}):") + logger.trace(f" - '{item1}'") + logger.trace(f" - '{item2}'") + + # 比较信息量 + info1 = calculate_information_content(item1) + info2 = calculate_information_content(item2) + + if info1 >= info2: + item_to_keep = item1 + item_to_remove = item2 + else: + item_to_keep = item2 + item_to_remove = item1 + + # 从原始列表中移除信息量较低的项 + try: + memory_items.remove(item_to_remove) + logger.info(f"[整合] 已合并节点 '{node}' 中的记忆,保留: '{item_to_keep[:60]}...', 移除: '{item_to_remove[:60]}...'" ) + merged_count += 1 + nodes_modified.add(node) + node_data['last_modified'] = current_timestamp # 更新修改时间 + _merged_in_this_node = True + break # 每个节点每次检查只合并一对 + except ValueError: + # 如果项已经被移除(例如,在之前的迭代中作为 item_to_keep),则跳过 + logger.warning(f"[整合] 尝试移除节点 '{node}' 中不存在的项 '{item_to_remove[:30]}...',可能已被合并。") + continue + # # 如果节点内发生了合并,更新节点数据 (这种方式不安全,会丢失其他属性) + # if merged_in_this_node: + # self.memory_graph.G.nodes[node]["memory_items"] = memory_items + + + if merged_count > 0: + logger.info(f"[整合] 共合并了 {merged_count} 对相似记忆项,分布在 {len(nodes_modified)} 个节点中。") + sync_start = time.time() + logger.info("[整合] 开始将变更同步到数据库...") + # 使用 resync 更安全地处理删除和添加 + await self.hippocampus.entorhinal_cortex.resync_memory_to_db() + sync_end = time.time() + logger.info(f"[整合] 数据库同步耗时: {sync_end - sync_start:.2f}秒") + else: + logger.info("[整合] 本次检查未发现需要合并的记忆项。") + + end_time = time.time() + logger.info(f"[整合] 整合检查完成,总耗时: {end_time - start_time:.2f}秒") + + @staticmethod + def _calculate_item_similarity(item1: str, item2: str) -> float: + """计算两条记忆项文本的余弦相似度""" + words1 = set(jieba.cut(item1)) + words2 = set(jieba.cut(item2)) + all_words = words1 | words2 + if not all_words: + return 0.0 + v1 = [1 if word in words1 else 0 for word in all_words] + v2 = [1 if word in words2 else 0 for word in all_words] + return cosine_similarity(v1, v2) + class HippocampusManager: _instance = None @@ -1433,12 +1574,12 @@ class HippocampusManager: edge_count = len(memory_graph.edges()) logger.success(f"""-------------------------------- - 记忆系统参数配置: - 构建间隔: {global_config.build_memory_interval}秒|样本数: {config.build_memory_sample_num},长度: {config.build_memory_sample_length}|压缩率: {config.memory_compress_rate} - 记忆构建分布: {config.memory_build_distribution} - 遗忘间隔: {global_config.forget_memory_interval}秒|遗忘比例: {global_config.memory_forget_percentage}|遗忘: {config.memory_forget_time}小时之后 - 记忆图统计信息: 节点数量: {node_count}, 连接数量: {edge_count} - --------------------------------""") # noqa: E501 + 记忆系统参数配置: + 构建间隔: {global_config.build_memory_interval}秒|样本数: {config.build_memory_sample_num},长度: {config.build_memory_sample_length}|压缩率: {config.memory_compress_rate} + 记忆构建分布: {config.memory_build_distribution} + 遗忘间隔: {global_config.forget_memory_interval}秒|遗忘比例: {global_config.memory_forget_percentage}|遗忘: {config.memory_forget_time}小时之后 + 记忆图统计信息: 节点数量: {node_count}, 连接数量: {edge_count} + --------------------------------""") # noqa: E501 return self._hippocampus @@ -1453,6 +1594,14 @@ class HippocampusManager: if not self._initialized: raise RuntimeError("HippocampusManager 尚未初始化,请先调用 initialize 方法") return await self._hippocampus.parahippocampal_gyrus.operation_forget_topic(percentage) + + async def consolidate_memory(self): + """整合记忆的公共接口""" + if not self._initialized: + raise RuntimeError("HippocampusManager 尚未初始化,请先调用 initialize 方法") + # 注意:目前 operation_consolidate_memory 内部直接读取配置,percentage 参数暂时无效 + # 如果需要外部控制比例,需要修改 operation_consolidate_memory + return await self._hippocampus.parahippocampal_gyrus.operation_consolidate_memory() async def get_memory_from_text( self, diff --git a/src/plugins/memory_system/memory_config.py b/src/plugins/memory_system/memory_config.py index 73f9c1db..8f7e1ffe 100644 --- a/src/plugins/memory_system/memory_config.py +++ b/src/plugins/memory_system/memory_config.py @@ -18,19 +18,29 @@ class MemoryConfig: # 记忆过滤相关配置 memory_ban_words: List[str] # 记忆过滤词列表 + # 新增:记忆整合相关配置 + consolidation_similarity_threshold: float # 相似度阈值 + consolidate_memory_percentage: float # 检查节点比例 + consolidate_memory_interval: int # 记忆整合间隔 + llm_topic_judge: str # 话题判断模型 llm_summary_by_topic: str # 话题总结模型 @classmethod def from_global_config(cls, global_config): """从全局配置创建记忆系统配置""" + # 使用 getattr 提供默认值,防止全局配置缺少这些项 return cls( - memory_build_distribution=global_config.memory_build_distribution, - build_memory_sample_num=global_config.build_memory_sample_num, - build_memory_sample_length=global_config.build_memory_sample_length, - memory_compress_rate=global_config.memory_compress_rate, - memory_forget_time=global_config.memory_forget_time, - memory_ban_words=global_config.memory_ban_words, - llm_topic_judge=global_config.llm_topic_judge, - llm_summary_by_topic=global_config.llm_summary_by_topic, + memory_build_distribution=getattr(global_config, "memory_build_distribution", (24, 12, 0.5, 168, 72, 0.5)), # 添加默认值 + build_memory_sample_num=getattr(global_config, "build_memory_sample_num", 5), + build_memory_sample_length=getattr(global_config, "build_memory_sample_length", 30), + memory_compress_rate=getattr(global_config, "memory_compress_rate", 0.1), + memory_forget_time=getattr(global_config, "memory_forget_time", 24 * 7), + memory_ban_words=getattr(global_config, "memory_ban_words", []), + # 新增加载整合配置,并提供默认值 + consolidation_similarity_threshold=getattr(global_config, "consolidation_similarity_threshold", 0.7), + consolidate_memory_percentage=getattr(global_config, "consolidate_memory_percentage", 0.01), + consolidate_memory_interval=getattr(global_config, "consolidate_memory_interval", 1000), + llm_topic_judge=getattr(global_config, "llm_topic_judge", "default_judge_model"), # 添加默认模型名 + llm_summary_by_topic=getattr(global_config, "llm_summary_by_topic", "default_summary_model"), # 添加默认模型名 ) diff --git a/src/plugins/person_info/relationship_manager.py b/src/plugins/person_info/relationship_manager.py index 2400da8b..6ae7c16e 100644 --- a/src/plugins/person_info/relationship_manager.py +++ b/src/plugins/person_info/relationship_manager.py @@ -101,36 +101,6 @@ class RelationshipManager: # await person_info_manager.update_one_field(person_id, "user_avatar", user_avatar) await person_info_manager.qv_person_name(person_id, user_nickname, user_cardname, user_avatar) - @staticmethod - async def convert_all_person_sign_to_person_name(input_text: str): - """将所有人的格式转换为person_name""" - try: - # 使用正则表达式匹配格式 - all_person = person_info_manager.person_name_list - - pattern = r"<([^:]+):(\d+):([^:]+):([^>]+)>" - matches = re.findall(pattern, input_text) - - # 遍历匹配结果,将替换为person_name - result_text = input_text - for platform, user_id, nickname, cardname in matches: - person_id = person_info_manager.get_person_id(platform, user_id) - # 默认使用昵称作为人名 - person_name = nickname.strip() if nickname.strip() else cardname.strip() - - if person_id in all_person: - if all_person[person_id] is not None: - person_name = all_person[person_id] - - # print(f"将<{platform}:{user_id}:{nickname}:{cardname}>替换为{person_name}") - - result_text = result_text.replace(f"<{platform}:{user_id}:{nickname}:{cardname}>", person_name) - - return result_text - except Exception: - logger.error(traceback.format_exc()) - return input_text - async def calculate_update_relationship_value(self, chat_stream: ChatStream, label: str, stance: str) -> tuple: """计算并变更关系值 新的关系值变更计算方式: diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 03eb0e6c..486c150f 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "1.5.0" +version = "1.5.1" #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #如果你想要修改配置文件,请在修改后将version的值进行变更 @@ -127,15 +127,19 @@ check_prompt = "符合公序良俗" # 表情包过滤要求,只有符合该要 [memory] build_memory_interval = 2000 # 记忆构建间隔 单位秒 间隔越低,麦麦学习越多,但是冗余信息也会增多 -build_memory_distribution = [4.0,2.0,0.6,24.0,8.0,0.4] # 记忆构建分布,参数:分布1均值,标准差,权重,分布2均值,标准差,权重 -build_memory_sample_num = 10 # 采样数量,数值越高记忆采样次数越多 -build_memory_sample_length = 20 # 采样长度,数值越高一段记忆内容越丰富 +build_memory_distribution = [6.0,3.0,0.6,32.0,12.0,0.4] # 记忆构建分布,参数:分布1均值,标准差,权重,分布2均值,标准差,权重 +build_memory_sample_num = 8 # 采样数量,数值越高记忆采样次数越多 +build_memory_sample_length = 40 # 采样长度,数值越高一段记忆内容越丰富 memory_compress_rate = 0.1 # 记忆压缩率 控制记忆精简程度 建议保持默认,调高可以获得更多信息,但是冗余信息也会增多 forget_memory_interval = 1000 # 记忆遗忘间隔 单位秒 间隔越低,麦麦遗忘越频繁,记忆更精简,但更难学习 memory_forget_time = 24 #多长时间后的记忆会被遗忘 单位小时 memory_forget_percentage = 0.01 # 记忆遗忘比例 控制记忆遗忘程度 越大遗忘越多 建议保持默认 +consolidate_memory_interval = 1000 # 记忆整合间隔 单位秒 间隔越低,麦麦整合越频繁,记忆更精简 +consolidation_similarity_threshold = 0.7 # 相似度阈值 +consolidation_check_percentage = 0.01 # 检查节点比例 + #不希望记忆的词,已经记忆的不会受到影响 memory_ban_words = [ # "403","张三" From 7ca883e2cbed54447640e2ed984d522b0ef4f7bd Mon Sep 17 00:00:00 2001 From: 114514 <2514624910@qq.com> Date: Tue, 29 Apr 2025 12:53:14 +0800 Subject: [PATCH 49/54] =?UTF-8?q?=E4=BC=98=E5=8C=96checker=E7=9A=84?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E5=87=A0=E4=B9=8E=E6=97=A0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=B1=82=E9=9D=A2=E6=94=B9=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/reply_checker.py | 40 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/plugins/PFC/reply_checker.py b/src/plugins/PFC/reply_checker.py index 17850325..18088895 100644 --- a/src/plugins/PFC/reply_checker.py +++ b/src/plugins/PFC/reply_checker.py @@ -55,9 +55,9 @@ class ReplyChecker: ) return ( False, - "回复内容与你上一条发言完全相同,请修改,可以选择深入话题或寻找其它话题或等待", - False, - ) # 不合适,无需重新规划 + "被逻辑检查拒绝:回复内容与你上一条发言完全相同,可以选择深入话题或寻找其它话题或等待", + True, + ) # 不合适,需要返回至决策层 # 2. 相似度检查 (如果精确匹配未通过) import difflib # 导入 difflib 库 @@ -73,8 +73,8 @@ class ReplyChecker: ) return ( False, - f"拒绝发送:回复内容与你上一条发言高度相似 (相似度 {similarity_ratio:.2f}),请修改,可以选择深入话题或寻找其它话题或等待。", - False, + f"被逻辑检查拒绝:回复内容与你上一条发言高度相似 (相似度 {similarity_ratio:.2f}),可以选择深入话题或寻找其它话题或等待。", + True, ) except Exception as e: @@ -83,37 +83,37 @@ class ReplyChecker: logger.error(f"[私聊][{self.private_name}]检查回复时出错: 类型={type(e)}, 值={e}") logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") # 打印详细的回溯信息 - prompt = f"""请检查以下回复或消息是否合适: + prompt = f"""你是一个聊天逻辑检查器,请检查以下回复或消息是否合适: 当前对话目标:{goal} 最新的对话记录: {chat_history_text} -待检查的回复: +待检查的消息: {reply} 请结合聊天记录检查以下几点: -1. 回复是否依然符合当前对话目标和实现方式 -2. 回复是否与最新的对话记录保持一致性 -3. 回复是否重复发言,或重复表达同质内容(尤其是只是换一种方式表达了相同的含义) -4. 回复是否包含违规内容(例如血腥暴力,政治敏感等) -5. 回复是否以你的角度发言,不要把"你"说的话当做对方说的话,这是你自己说的话(不要自己回复自己的消息) -6. 回复是否通俗易懂 -7. 回复是否有些多余,例如在对方没有回复的情况下,依然连续多次“消息轰炸”(尤其是已经连续发送3条信息的情况,这很可能不合理,需要着重判断) -8. 回复是否使用了完全没必要的修辞 -9. 回复是否逻辑通顺 -10. 回复是否太过冗长了(通常私聊的每条消息长度在20字以内,除非特殊情况) -11. 在连续多次发送消息的情况下,当前回复是否衔接自然,会不会显得奇怪(例如连续两条消息中部分内容重叠) +1. 这条消息是否依然符合当前对话目标和实现方式 +2. 这条消息是否与最新的对话记录保持一致性 +3. 是否存在重复发言,或重复表达同质内容(尤其是只是换一种方式表达了相同的含义) +4. 这条消息是否包含违规内容(例如血腥暴力,政治敏感等) +5. 这条消息是否以发送者的角度发言(不要让发送者自己回复自己的消息) +6. 这条消息是否通俗易懂 +7. 这条消息是否有些多余,例如在对方没有回复的情况下,依然连续多次“消息轰炸”(尤其是已经连续发送3条信息的情况,这很可能不合理,需要着重判断) +8. 这条消息是否使用了完全没必要的修辞 +9. 这条消息是否逻辑通顺 +10. 这条消息是否太过冗长了(通常私聊的每条消息长度在20字以内,除非特殊情况) +11. 在连续多次发送消息的情况下,这条消息是否衔接自然,会不会显得奇怪(例如连续两条消息中部分内容重叠) 请以JSON格式输出,包含以下字段: 1. suitable: 是否合适 (true/false) 2. reason: 原因说明 -3. need_replan: 是否需要重新规划对话目标 (true/false),当发现当前对话目标不再适合时设为true +3. need_replan: 是否需要重新决策 (true/false),当你认为此时已经不适合发消息,需要规划其它行动时,设为true 输出格式示例: {{ "suitable": true, - "reason": "回复符合要求,内容得体", + "reason": "回复符合要求,虽然有可能略微偏离目标,但是整体内容流畅得体", "need_replan": false }} From 06ba51d75ffd5413ce437d149766cdf06bec5a5e Mon Sep 17 00:00:00 2001 From: Bakadax Date: Tue, 29 Apr 2025 15:04:45 +0800 Subject: [PATCH 50/54] =?UTF-8?q?=E6=89=93=E5=9B=9E=E5=8A=A8=E4=BD=9C?= =?UTF-8?q?=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 | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 37966a76..7e29b404 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -368,13 +368,22 @@ class Conversation: self.conversation_info.last_successful_reply_action = "send_new_message" action_successful = True # 标记动作成功 + elif need_replan: + # 打回动作决策 + logger.warning( + f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,追问回复决定打回动作决策。打回原因: {check_reason}" + ) + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"追问尝试{reply_attempt_count}次后失败: {check_reason}"} + ) + else: # 追问失败 logger.warning( f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,未能生成合适的追问回复。最终原因: {check_reason}" ) conversation_info.done_action[action_index].update( - {"status": "recall", "final_reason": f"追问尝试{reply_attempt_count}次后失败: {check_reason}"} + {"status": "recall", "final_reason": f"追问尝试{reply_attempt_count}次后打回: {check_reason}"} ) # 重置状态: 追问失败,下次用初始 prompt self.conversation_info.last_successful_reply_action = None @@ -463,6 +472,15 @@ class Conversation: self.conversation_info.last_successful_reply_action = "direct_reply" action_successful = True # 标记动作成功 + elif need_replan: + # 打回动作决策 + logger.warning( + f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,首次回复决定打回动作决策。打回原因: {check_reason}" + ) + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"首次回复尝试{reply_attempt_count}次后打回: {check_reason}"} + ) + else: # 首次回复失败 logger.warning( From ac40490bf1df51506ff66076fff2ba49c375c9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Tue, 29 Apr 2025 15:20:19 +0800 Subject: [PATCH 51/54] fix: Ruff --- src/plugins/heartFC_chat/heartflow_prompt_builder.py | 2 +- src/plugins/person_info/relationship_manager.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/heartFC_chat/heartflow_prompt_builder.py b/src/plugins/heartFC_chat/heartflow_prompt_builder.py index d876d6ab..175f571d 100644 --- a/src/plugins/heartFC_chat/heartflow_prompt_builder.py +++ b/src/plugins/heartFC_chat/heartflow_prompt_builder.py @@ -5,7 +5,7 @@ from ...individuality.individuality import Individuality from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager from src.plugins.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat from src.plugins.person_info.relationship_manager import relationship_manager -from src.plugins.chat.utils import get_embedding, parse_text_timestamps +from src.plugins.chat.utils import get_embedding import time from typing import Union, Optional from ...common.database import db diff --git a/src/plugins/person_info/relationship_manager.py b/src/plugins/person_info/relationship_manager.py index 6ae7c16e..02ab14dd 100644 --- a/src/plugins/person_info/relationship_manager.py +++ b/src/plugins/person_info/relationship_manager.py @@ -4,8 +4,8 @@ import math from bson.decimal128 import Decimal128 from .person_info import person_info_manager import time -import re -import traceback +# import re +# import traceback logger = get_logger("relation") From 76922c3c289239b18f72656a8e09318ebb433c0c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 29 Apr 2025 07:20:35 +0000 Subject: [PATCH 52/54] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/config.py | 2 +- src/main.py | 2 +- src/plugins/memory_system/Hippocampus.py | 33 ++++++++++++---------- src/plugins/memory_system/memory_config.py | 16 +++++++---- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/config/config.py b/src/config/config.py index ebfc444c..28de2053 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -234,7 +234,7 @@ class BotConfig: forget_memory_interval: int = 600 # 记忆遗忘间隔(秒) memory_forget_time: int = 24 # 记忆遗忘时间(小时) memory_forget_percentage: float = 0.01 # 记忆遗忘比例 - + consolidate_memory_interval: int = 1000 # 记忆整合间隔(秒) consolidation_similarity_threshold: float = 0.7 # 相似度阈值 consolidate_memory_percentage: float = 0.01 # 检查节点比例 diff --git a/src/main.py b/src/main.py index 047e075f..c0e743d6 100644 --- a/src/main.py +++ b/src/main.py @@ -146,7 +146,7 @@ class MainSystem: print("\033[1;32m[记忆遗忘]\033[0m 开始遗忘记忆...") await HippocampusManager.get_instance().forget_memory(percentage=global_config.memory_forget_percentage) print("\033[1;32m[记忆遗忘]\033[0m 记忆遗忘完成") - + @staticmethod async def consolidate_memory_task(): """记忆整合任务""" diff --git a/src/plugins/memory_system/Hippocampus.py b/src/plugins/memory_system/Hippocampus.py index 5cca0d07..e5aa096f 100644 --- a/src/plugins/memory_system/Hippocampus.py +++ b/src/plugins/memory_system/Hippocampus.py @@ -1353,11 +1353,11 @@ class ParahippocampalGyrus: if not memory_items: try: self.memory_graph.G.remove_node(node) - node_changes["removed"].append(f"{node}(空节点)") # 标记为空节点移除 + node_changes["removed"].append(f"{node}(空节点)") # 标记为空节点移除 logger.debug(f"[遗忘] 移除了空的节点: {node}") except nx.NetworkXError as e: logger.warning(f"[遗忘] 移除空节点 {node} 时发生错误(可能已被移除): {e}") - continue # 处理下一个节点 + continue # 处理下一个节点 # --- 如果节点不为空,则执行原来的不活跃检查和随机移除逻辑 --- last_modified = node_data.get("last_modified", current_time) @@ -1373,15 +1373,15 @@ class ParahippocampalGyrus: memory_items.remove(removed_item) # 条件3:检查移除后 memory_items 是否变空 - if memory_items: # 如果移除后列表不为空 + if memory_items: # 如果移除后列表不为空 # self.memory_graph.G.nodes[node]["memory_items"] = memory_items # 直接修改列表即可 - self.memory_graph.G.nodes[node]["last_modified"] = current_time # 更新修改时间 + self.memory_graph.G.nodes[node]["last_modified"] = current_time # 更新修改时间 node_changes["reduced"].append(f"{node} (数量: {current_count} -> {len(memory_items)})") - else: # 如果移除后列表为空 + else: # 如果移除后列表为空 # 尝试移除节点,处理可能的错误 try: self.memory_graph.G.remove_node(node) - node_changes["removed"].append(f"{node}(遗忘清空)") # 标记为遗忘清空 + node_changes["removed"].append(f"{node}(遗忘清空)") # 标记为遗忘清空 logger.debug(f"[遗忘] 节点 {node} 因移除最后一项而被清空。") except nx.NetworkXError as e: logger.warning(f"[遗忘] 尝试移除节点 {node} 时发生错误(可能已被移除):{e}") @@ -1464,9 +1464,9 @@ class ParahippocampalGyrus: node_data = self.memory_graph.G.nodes[node] memory_items = node_data.get("memory_items", []) if not isinstance(memory_items, list) or len(memory_items) < 2: - continue # 双重检查,理论上不会进入 + continue # 双重检查,理论上不会进入 - items_copy = list(memory_items) # 创建副本以安全迭代和修改 + items_copy = list(memory_items) # 创建副本以安全迭代和修改 # 遍历所有记忆项组合 for item1, item2 in combinations(items_copy, 2): @@ -1495,21 +1495,24 @@ class ParahippocampalGyrus: # 从原始列表中移除信息量较低的项 try: memory_items.remove(item_to_remove) - logger.info(f"[整合] 已合并节点 '{node}' 中的记忆,保留: '{item_to_keep[:60]}...', 移除: '{item_to_remove[:60]}...'" ) + logger.info( + f"[整合] 已合并节点 '{node}' 中的记忆,保留: '{item_to_keep[:60]}...', 移除: '{item_to_remove[:60]}...'" + ) merged_count += 1 nodes_modified.add(node) - node_data['last_modified'] = current_timestamp # 更新修改时间 + node_data["last_modified"] = current_timestamp # 更新修改时间 _merged_in_this_node = True - break # 每个节点每次检查只合并一对 + break # 每个节点每次检查只合并一对 except ValueError: # 如果项已经被移除(例如,在之前的迭代中作为 item_to_keep),则跳过 - logger.warning(f"[整合] 尝试移除节点 '{node}' 中不存在的项 '{item_to_remove[:30]}...',可能已被合并。") + logger.warning( + f"[整合] 尝试移除节点 '{node}' 中不存在的项 '{item_to_remove[:30]}...',可能已被合并。" + ) continue - # # 如果节点内发生了合并,更新节点数据 (这种方式不安全,会丢失其他属性) + # # 如果节点内发生了合并,更新节点数据 (这种方式不安全,会丢失其他属性) # if merged_in_this_node: # self.memory_graph.G.nodes[node]["memory_items"] = memory_items - if merged_count > 0: logger.info(f"[整合] 共合并了 {merged_count} 对相似记忆项,分布在 {len(nodes_modified)} 个节点中。") sync_start = time.time() @@ -1594,7 +1597,7 @@ class HippocampusManager: if not self._initialized: raise RuntimeError("HippocampusManager 尚未初始化,请先调用 initialize 方法") return await self._hippocampus.parahippocampal_gyrus.operation_forget_topic(percentage) - + async def consolidate_memory(self): """整合记忆的公共接口""" if not self._initialized: diff --git a/src/plugins/memory_system/memory_config.py b/src/plugins/memory_system/memory_config.py index 8f7e1ffe..b2fb7280 100644 --- a/src/plugins/memory_system/memory_config.py +++ b/src/plugins/memory_system/memory_config.py @@ -19,9 +19,9 @@ class MemoryConfig: memory_ban_words: List[str] # 记忆过滤词列表 # 新增:记忆整合相关配置 - consolidation_similarity_threshold: float # 相似度阈值 - consolidate_memory_percentage: float # 检查节点比例 - consolidate_memory_interval: int # 记忆整合间隔 + consolidation_similarity_threshold: float # 相似度阈值 + consolidate_memory_percentage: float # 检查节点比例 + consolidate_memory_interval: int # 记忆整合间隔 llm_topic_judge: str # 话题判断模型 llm_summary_by_topic: str # 话题总结模型 @@ -31,7 +31,9 @@ class MemoryConfig: """从全局配置创建记忆系统配置""" # 使用 getattr 提供默认值,防止全局配置缺少这些项 return cls( - memory_build_distribution=getattr(global_config, "memory_build_distribution", (24, 12, 0.5, 168, 72, 0.5)), # 添加默认值 + memory_build_distribution=getattr( + global_config, "memory_build_distribution", (24, 12, 0.5, 168, 72, 0.5) + ), # 添加默认值 build_memory_sample_num=getattr(global_config, "build_memory_sample_num", 5), build_memory_sample_length=getattr(global_config, "build_memory_sample_length", 30), memory_compress_rate=getattr(global_config, "memory_compress_rate", 0.1), @@ -41,6 +43,8 @@ class MemoryConfig: consolidation_similarity_threshold=getattr(global_config, "consolidation_similarity_threshold", 0.7), consolidate_memory_percentage=getattr(global_config, "consolidate_memory_percentage", 0.01), consolidate_memory_interval=getattr(global_config, "consolidate_memory_interval", 1000), - llm_topic_judge=getattr(global_config, "llm_topic_judge", "default_judge_model"), # 添加默认模型名 - llm_summary_by_topic=getattr(global_config, "llm_summary_by_topic", "default_summary_model"), # 添加默认模型名 + llm_topic_judge=getattr(global_config, "llm_topic_judge", "default_judge_model"), # 添加默认模型名 + llm_summary_by_topic=getattr( + global_config, "llm_summary_by_topic", "default_summary_model" + ), # 添加默认模型名 ) From 97b22330a27a17c2d2f445781edab61228dfaf52 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Tue, 29 Apr 2025 15:26:51 +0800 Subject: [PATCH 53/54] =?UTF-8?q?=E4=BF=AEtypo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/conversation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 7e29b404..6a8636e1 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -374,7 +374,7 @@ class Conversation: f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,追问回复决定打回动作决策。打回原因: {check_reason}" ) conversation_info.done_action[action_index].update( - {"status": "recall", "final_reason": f"追问尝试{reply_attempt_count}次后失败: {check_reason}"} + {"status": "recall", "final_reason": f"追问尝试{reply_attempt_count}次后打回: {check_reason}"} ) else: @@ -383,7 +383,7 @@ class Conversation: f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,未能生成合适的追问回复。最终原因: {check_reason}" ) conversation_info.done_action[action_index].update( - {"status": "recall", "final_reason": f"追问尝试{reply_attempt_count}次后打回: {check_reason}"} + {"status": "recall", "final_reason": f"追问尝试{reply_attempt_count}次后失败: {check_reason}"} ) # 重置状态: 追问失败,下次用初始 prompt self.conversation_info.last_successful_reply_action = None From 637435383500f14676c2bbfce6cf5d44590109c8 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Tue, 29 Apr 2025 16:16:42 +0800 Subject: [PATCH 54/54] =?UTF-8?q?=E9=80=82=E9=85=8D=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E4=BA=BA=E8=AE=BE=E8=8E=B7=E5=8F=96=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 19 ++--------------- src/plugins/PFC/pfc.py | 34 +++--------------------------- src/plugins/PFC/reply_generator.py | 18 ++-------------- 3 files changed, 7 insertions(+), 64 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 3765a7d6..8d2c7ea9 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -93,8 +93,7 @@ class ActionPlanner: max_tokens=1500, request_type="action_planning", ) - self.personality_info = Individuality.get_instance().get_prompt(type="personality", x_person=2, level=3) - self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2) + self.personality_info = Individuality.get_instance().get_prompt(x_person=2, level=3) self.name = global_config.BOT_NICKNAME self.private_name = private_name self.chat_observer = ChatObserver.get_instance(stream_id, private_name) @@ -244,21 +243,7 @@ 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 = ["你", "我", "他"] - for p in pronouns: - if identity_details_only.startswith(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(",, ") - 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}。" # 构建行动历史和上一次行动结果 (action_history_summary, last_action_context) # (这部分逻辑不变) diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index 32755c29..d6f4c519 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -23,8 +23,7 @@ class GoalAnalyzer: model=global_config.llm_normal, temperature=0.7, max_tokens=1000, request_type="conversation_goal" ) - self.personality_info = Individuality.get_instance().get_prompt(type="personality", x_person=2, level=3) - self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2) + self.personality_info = Individuality.get_instance().get_prompt(x_person=2, level=3) self.name = global_config.BOT_NICKNAME self.nick_name = global_config.BOT_ALIAS_NAMES self.private_name = private_name @@ -79,21 +78,7 @@ class GoalAnalyzer: # await observation_info.clear_unprocessed_messages() - identity_details_only = self.identity_detail_info - identity_addon = "" - if isinstance(identity_details_only, str): - pronouns = ["你", "我", "他"] - for p in pronouns: - if identity_details_only.startswith(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(",, ") - 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}。" # 构建action历史文本 action_history_list = conversation_info.done_action action_history_text = "你之前做的事情是:" @@ -241,21 +226,8 @@ class GoalAnalyzer: timestamp_mode="relative", read_mark=0.0, ) - identity_details_only = self.identity_detail_info - identity_addon = "" - if isinstance(identity_details_only, str): - pronouns = ["你", "我", "他"] - for p in pronouns: - if identity_details_only.startswith(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(",, ") - 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}。" # ===> Persona 文本构建结束 <=== # --- 修改 Prompt 字符串,使用 persona_text --- diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index dea42b04..0c257a93 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -68,8 +68,7 @@ class ReplyGenerator: max_tokens=300, request_type="reply_generation", ) - self.personality_info = Individuality.get_instance().get_prompt(type="personality", x_person=2, level=3) - self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2) + self.personality_info = Individuality.get_instance().get_prompt(x_person=2, level=3) self.name = global_config.BOT_NICKNAME self.private_name = private_name self.chat_observer = ChatObserver.get_instance(stream_id, private_name) @@ -130,20 +129,7 @@ class ReplyGenerator: chat_history_text = "还没有聊天记录。" # 构建 Persona 文本 (persona_text) - identity_details_only = self.identity_detail_info - identity_addon = "" - if isinstance(identity_details_only, str): - pronouns = ["你", "我", "他"] - for p in pronouns: - if identity_details_only.startswith(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(",, ") - 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}。" # --- 选择 Prompt --- if action_type == "send_new_message":