From 6226de10ea29c38ff8a3b7f9892c3b88b2f6dfa0 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sun, 13 Jul 2025 15:19:56 +0800 Subject: [PATCH] =?UTF-8?q?remove=EF=BC=9A=E7=A7=BB=E9=99=A4pfc=E9=81=97?= =?UTF-8?q?=E7=95=99=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/focus_chat/heartFC_chat.py | 3 +- src/chat/message_receive/bot.py | 1 - src/chat/planner_actions/action_modifier.py | 2 +- src/chat/planner_actions/planner.py | 2 +- src/chat/replyer/default_generator.py | 4 +- src/experimental/PFC/action_planner.py | 490 ------------- src/experimental/PFC/chat_observer.py | 383 ---------- src/experimental/PFC/chat_states.py | 290 -------- src/experimental/PFC/conversation.py | 700 ------------------- src/experimental/PFC/conversation_info.py | 10 - src/experimental/PFC/message_storage.py | 131 ---- src/experimental/PFC/observation_info.py | 389 ----------- src/experimental/PFC/pfc.py | 346 --------- src/experimental/PFC/pfc_KnowledgeFetcher.py | 91 --- src/experimental/PFC/pfc_manager.py | 115 --- src/experimental/PFC/pfc_types.py | 23 - src/experimental/PFC/pfc_utils.py | 127 ---- src/experimental/PFC/reply_checker.py | 183 ----- src/experimental/PFC/reply_generator.py | 227 ------ src/experimental/PFC/waiter.py | 79 --- src/experimental/only_message_process.py | 70 -- src/mais4u/mais4u_chat/s4u_mood_manager.py | 6 +- src/mood/mood_manager.py | 2 +- 23 files changed, 9 insertions(+), 3665 deletions(-) delete mode 100644 src/experimental/PFC/action_planner.py delete mode 100644 src/experimental/PFC/chat_observer.py delete mode 100644 src/experimental/PFC/chat_states.py delete mode 100644 src/experimental/PFC/conversation.py delete mode 100644 src/experimental/PFC/conversation_info.py delete mode 100644 src/experimental/PFC/message_storage.py delete mode 100644 src/experimental/PFC/observation_info.py delete mode 100644 src/experimental/PFC/pfc.py delete mode 100644 src/experimental/PFC/pfc_KnowledgeFetcher.py delete mode 100644 src/experimental/PFC/pfc_manager.py delete mode 100644 src/experimental/PFC/pfc_types.py delete mode 100644 src/experimental/PFC/pfc_utils.py delete mode 100644 src/experimental/PFC/reply_checker.py delete mode 100644 src/experimental/PFC/reply_generator.py delete mode 100644 src/experimental/PFC/waiter.py delete mode 100644 src/experimental/only_message_process.py diff --git a/src/chat/focus_chat/heartFC_chat.py b/src/chat/focus_chat/heartFC_chat.py index db75ef578..88506eaa2 100644 --- a/src/chat/focus_chat/heartFC_chat.py +++ b/src/chat/focus_chat/heartFC_chat.py @@ -18,11 +18,10 @@ from src.chat.focus_chat.hfc_utils import CycleDetail from src.chat.focus_chat.hfc_utils import get_recent_message_stats from src.person_info.relationship_builder_manager import relationship_builder_manager from src.person_info.person_info import get_person_info_manager -from src.plugin_system.base.component_types import ActionInfo, ChatMode +from src.plugin_system.base.component_types import ActionInfo from src.plugin_system.apis import generator_api, send_api, message_api from src.chat.willing.willing_manager import get_willing_manager from ...mais4u.mais4u_chat.priority_manager import PriorityManager -from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat diff --git a/src/chat/message_receive/bot.py b/src/chat/message_receive/bot.py index 13e2a743e..ae3a7576f 100644 --- a/src/chat/message_receive/bot.py +++ b/src/chat/message_receive/bot.py @@ -14,7 +14,6 @@ from src.chat.message_receive.storage import MessageStorage from src.chat.heart_flow.heartflow_message_processor import HeartFCMessageReceiver from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.experimental.only_message_process import MessageProcessor -from src.experimental.PFC.pfc_manager import PFCManager from src.plugin_system.core.component_registry import component_registry # 导入新插件系统 from src.plugin_system.base.base_command import BaseCommand from src.mais4u.mais4u_chat.s4u_msg_processor import S4UMessageProcessor diff --git a/src/chat/planner_actions/action_modifier.py b/src/chat/planner_actions/action_modifier.py index 1673f2d9d..79394995c 100644 --- a/src/chat/planner_actions/action_modifier.py +++ b/src/chat/planner_actions/action_modifier.py @@ -11,7 +11,7 @@ from src.chat.focus_chat.hfc_utils import CycleDetail from src.chat.message_receive.chat_stream import get_chat_manager, ChatMessageContext from src.chat.planner_actions.action_manager import ActionManager from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, build_readable_messages -from src.plugin_system.base.component_types import ChatMode, ActionInfo, ActionActivationType +from src.plugin_system.base.component_types import ActionInfo, ActionActivationType if TYPE_CHECKING: from src.chat.message_receive.chat_stream import ChatStream diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 1dc8863ed..6cbaaa434 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -19,7 +19,7 @@ from src.chat.utils.chat_message_builder import ( from src.chat.utils.utils import get_chat_type_and_target_info from src.chat.planner_actions.action_manager import ActionManager from src.chat.message_receive.chat_stream import get_chat_manager -from src.plugin_system.base.component_types import ChatMode, ActionInfo +from src.plugin_system.base.component_types import ActionInfo logger = get_logger("planner") diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index ddc54bc62..912cd7997 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -11,8 +11,8 @@ from datetime import datetime from src.common.logger import get_logger from src.config.config import global_config from src.llm_models.utils_model import LLMRequest -from src.chat.message_receive.message import UserInfo, Seg, MessageRecv, MessageThinking, MessageSending -from src.chat.message_receive.chat_stream import get_chat_manager, ChatStream +from src.chat.message_receive.message import UserInfo, Seg, MessageRecv, MessageSending +from src.chat.message_receive.chat_stream import ChatStream from src.chat.message_receive.uni_message_sender import HeartFCSender from src.chat.utils.timer_calculator import Timer # <--- Import Timer from src.chat.utils.utils import get_chat_type_and_target_info diff --git a/src/experimental/PFC/action_planner.py b/src/experimental/PFC/action_planner.py deleted file mode 100644 index e7045f2aa..000000000 --- a/src/experimental/PFC/action_planner.py +++ /dev/null @@ -1,490 +0,0 @@ -import time -from typing import Tuple, Optional # 增加了 Optional -from src.common.logger import get_logger -from src.llm_models.utils_model import LLMRequest -from src.config.config import global_config -from src.experimental.PFC.chat_observer import ChatObserver -from src.experimental.PFC.pfc_utils import get_items_from_json -from src.individuality.individuality import get_individuality -from src.experimental.PFC.observation_info import ObservationInfo -from src.experimental.PFC.conversation_info import ConversationInfo -from src.chat.utils.chat_message_builder import build_readable_messages - - -logger = get_logger("pfc_action_planner") - - -# --- 定义 Prompt 模板 --- - -# Prompt(1): 首次回复或非连续回复时的决策 Prompt -PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以回复,可以倾听,可以调取知识,甚至可以屏蔽对方: - -【当前对话目标】 -{goals_str} -{knowledge_info_str} - -【最近行动历史概要】 -{action_history_summary} -【上一次行动的详细情况和结果】 -{last_action_context} -【时间和超时提示】 -{time_since_last_bot_message_info}{timeout_context} -【最近的对话记录】(包括你已成功发送的消息 和 新收到的消息) -{chat_history_text} - ------- -可选行动类型以及解释: -fetch_knowledge: 需要调取知识或记忆,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择 -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} -{knowledge_info_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格式输出,不要包含任何其他内容。""" - -# 新增:Prompt(3): 决定是否在结束对话前发送告别语 -PROMPT_END_DECISION = """{persona_text}。刚刚你决定结束一场 QQ 私聊。 - -【你们之前的聊天记录】 -{chat_history_text} - -你觉得你们的对话已经完整结束了吗?有时候,在对话自然结束后再说点什么可能会有点奇怪,但有时也可能需要一条简短的消息来圆满结束。 -如果觉得确实有必要再发一条简短、自然、符合你人设的告别消息(比如 "好,下次再聊~" 或 "嗯,先这样吧"),就输出 "yes"。 -如果觉得当前状态下直接结束对话更好,没有必要再发消息,就输出 "no"。 - -请以 JSON 格式输出你的选择: -{{ - "say_bye": "yes/no", - "reason": "选择 yes 或 no 的原因和内心想法 (简要说明)" -}} - -注意:请严格按照 JSON 格式输出,不要包含任何其他内容。""" - - -# ActionPlanner 类定义,顶格 -class ActionPlanner: - """行动规划器""" - - 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"], - request_type="action_planning", - ) - self.personality_info = get_individuality().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) - # 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]: - """规划下一步行动 - - 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_account) - if hasattr(observation_info, "chat_history") and observation_info.chat_history: - for i in range(len(observation_info.chat_history) - 1, -1, -1): - msg = observation_info.chat_history[i] - if not isinstance(msg, dict): - continue - sender_info = msg.get("user_info", {}) - sender_id = str(sender_info.get("user_id")) if isinstance(sender_info, dict) else None - msg_time = msg.get("time") - if sender_id == bot_id and msg_time: - time_diff = time.time() - msg_time - if time_diff < 60.0: - time_since_last_bot_message_info = ( - f"提示:你上一条成功发送的消息是在 {time_diff:.1f} 秒前。\n" - ) - break - else: - 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." - ) - except Exception as e: - logger.warning(f"[私聊][{self.private_name}]获取 Bot 上次发言时间时出错: {e}") - - # --- 获取超时提示信息 --- - # (这部分逻辑不变) - timeout_context = "" - try: - if hasattr(conversation_info, "goal_list") and conversation_info.goal_list: - 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("你等待了", "") - timeout_context = f"重要提示:对方已经长时间({timeout_minutes_text})没有回复你的消息了(这可能代表对方繁忙/不想回复/没注意到你的消息等情况,或在对方看来本次聊天已告一段落),请基于此情况规划下一步。\n" - except Exception: - timeout_context = "重要提示:对方已经长时间没有回复你的消息了(这可能代表对方繁忙/不想回复/没注意到你的消息等情况,或在对方看来本次聊天已告一段落),请基于此情况规划下一步。\n" - else: - 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." - ) - except Exception as e: - logger.warning(f"[私聊][{self.private_name}]检查超时目标时出错: {e}") - - # --- 构建通用 Prompt 参数 --- - logger.debug( - f"[私聊][{self.private_name}]开始规划行动:当前目标: {getattr(conversation_info, 'goal_list', '不可用')}" - ) - - # 构建对话目标 (goals_str) - goals_str = "" - try: - if hasattr(conversation_info, "goal_list") and conversation_info.goal_list: - for goal_reason in conversation_info.goal_list: - 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: - goals_str = "- 目前没有明确对话目标,请考虑设定一个。\n" - else: - goals_str = "- 目前没有明确对话目标,请考虑设定一个。\n" - except AttributeError: - 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}") - goals_str = "- 构建对话目标时出错。\n" - - # --- 知识信息字符串构建开始 --- - knowledge_info_str = "【已获取的相关知识和记忆】\n" - try: - # 检查 conversation_info 是否有 knowledge_list 并且不为空 - if hasattr(conversation_info, "knowledge_list") and conversation_info.knowledge_list: - # 最多只显示最近的 5 条知识,防止 Prompt 过长 - recent_knowledge = conversation_info.knowledge_list[-5:] - for i, knowledge_item in enumerate(recent_knowledge): - if isinstance(knowledge_item, dict): - query = knowledge_item.get("query", "未知查询") - knowledge = knowledge_item.get("knowledge", "无知识内容") - source = knowledge_item.get("source", "未知来源") - # 只取知识内容的前 2000 个字,避免太长 - knowledge_snippet = knowledge[:2000] + "..." if len(knowledge) > 2000 else knowledge - knowledge_info_str += ( - f"{i + 1}. 关于 '{query}' 的知识 (来源: {source}):\n {knowledge_snippet}\n" - ) - else: - # 处理列表里不是字典的异常情况 - knowledge_info_str += f"{i + 1}. 发现一条格式不正确的知识记录。\n" - - if not recent_knowledge: # 如果 knowledge_list 存在但为空 - knowledge_info_str += "- 暂无相关知识和记忆。\n" - - else: - # 如果 conversation_info 没有 knowledge_list 属性,或者列表为空 - knowledge_info_str += "- 暂无相关知识记忆。\n" - except AttributeError: - logger.warning(f"[私聊][{self.private_name}]ConversationInfo 对象可能缺少 knowledge_list 属性。") - knowledge_info_str += "- 获取知识列表时出错。\n" - except Exception as e: - logger.error(f"[私聊][{self.private_name}]构建知识信息字符串时出错: {e}") - knowledge_info_str += "- 处理知识列表时出错。\n" - # --- 知识信息字符串构建结束 --- - - # 获取聊天历史记录 (chat_history_text) - 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: - chat_history_text = "还没有聊天记录。\n" - else: - chat_history_text = "还没有聊天记录。\n" - - if hasattr(observation_info, "new_messages_count") and observation_info.new_messages_count > 0: - if hasattr(observation_info, "unprocessed_messages") and observation_info.unprocessed_messages: - new_messages_list = observation_info.unprocessed_messages - new_messages_str = 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}" - ) - else: - logger.warning( - 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." - ) - chat_history_text = "获取聊天记录时出错。\n" - except Exception as e: - logger.error(f"[私聊][{self.private_name}]处理聊天记录时发生未知错误: {e}") - chat_history_text = "处理聊天记录时出错。\n" - - # 构建 Persona 文本 (persona_text) - persona_text = f"你的名字是{self.name},{self.personality_info}。" - - # 构建行动历史和上一次行动结果 (action_history_summary, last_action_context) - # (这部分逻辑不变) - action_history_summary = "你最近执行的行动历史:\n" - last_action_context = "关于你【上一次尝试】的行动:\n" - action_history_list = [] - try: - if hasattr(conversation_info, "done_action") and conversation_info.done_action: - action_history_list = conversation_info.done_action[-5:] - 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." - ) - except Exception as e: - logger.error(f"[私聊][{self.private_name}]访问行动历史时出错: {e}") - - if not action_history_list: - action_history_summary += "- 还没有执行过行动。\n" - last_action_context += "- 这是你规划的第一个行动。\n" - else: - for i, action_data in enumerate(action_history_list): - action_type = "未知" - plan_reason = "未知" - status = "未知" - final_reason = "" - action_time = "" - - if isinstance(action_data, dict): - action_type = action_data.get("action", "未知") - plan_reason = action_data.get("plan_reason", "未知规划原因") - status = action_data.get("status", "未知") - 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] - 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}" - action_history_summary += summary_line + "\n" - - if i == len(action_history_list) - 1: - last_action_context += f"- 上次【规划】的行动是: '{action_type}'\n" - 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 --- - if last_successful_reply_action in ["direct_reply", "send_new_message"]: - prompt_template = PROMPT_FOLLOW_UP - logger.debug(f"[私聊][{self.private_name}]使用 PROMPT_FOLLOW_UP (追问决策)") - else: - prompt_template = PROMPT_INITIAL_REPLY - logger.debug(f"[私聊][{self.private_name}]使用 PROMPT_INITIAL_REPLY (首次/非连续回复决策)") - - # --- 格式化最终的 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 "还没有聊天记录。", - knowledge_info_str=knowledge_info_str, - ) - - logger.debug(f"[私聊][{self.private_name}]发送到LLM的最终提示词:\n------\n{prompt}\n------") - try: - content, _ = await self.llm.generate_response_async(prompt) - logger.debug(f"[私聊][{self.private_name}]LLM (行动规划) 原始返回内容: {content}") - - # --- 初始行动规划解析 --- - success, initial_result = get_items_from_json( - content, - self.private_name, - "action", - "reason", - default_values={"action": "wait", "reason": "LLM返回格式错误或未提供原因,默认等待"}, - ) - - initial_action = initial_result.get("action", "wait") - initial_reason = initial_result.get("reason", "LLM未提供原因,默认等待") - - # 检查是否需要进行结束对话决策 --- - if initial_action == "end_conversation": - logger.info(f"[私聊][{self.private_name}]初步规划结束对话,进入告别决策...") - - # 使用新的 PROMPT_END_DECISION - end_decision_prompt = PROMPT_END_DECISION.format( - persona_text=persona_text, # 复用之前的 persona_text - chat_history_text=chat_history_text, # 复用之前的 chat_history_text - ) - - logger.debug( - f"[私聊][{self.private_name}]发送到LLM的结束决策提示词:\n------\n{end_decision_prompt}\n------" - ) - try: - end_content, _ = await self.llm.generate_response_async(end_decision_prompt) # 再次调用LLM - logger.debug(f"[私聊][{self.private_name}]LLM (结束决策) 原始返回内容: {end_content}") - - # 解析结束决策的JSON - end_success, end_result = get_items_from_json( - end_content, - self.private_name, - "say_bye", - "reason", - default_values={"say_bye": "no", "reason": "结束决策LLM返回格式错误,默认不告别"}, - required_types={"say_bye": str, "reason": str}, # 明确类型 - ) - - say_bye_decision = end_result.get("say_bye", "no").lower() # 转小写方便比较 - end_decision_reason = end_result.get("reason", "未提供原因") - - if end_success and say_bye_decision == "yes": - # 决定要告别,返回新的 'say_goodbye' 动作 - logger.info( - f"[私聊][{self.private_name}]结束决策: yes, 准备生成告别语. 原因: {end_decision_reason}" - ) - # 注意:这里的 reason 可以考虑拼接初始原因和结束决策原因,或者只用结束决策原因 - final_action = "say_goodbye" - final_reason = f"决定发送告别语。决策原因: {end_decision_reason} (原结束理由: {initial_reason})" - return final_action, final_reason - else: - # 决定不告别 (包括解析失败或明确说no) - logger.info( - f"[私聊][{self.private_name}]结束决策: no, 直接结束对话. 原因: {end_decision_reason}" - ) - # 返回原始的 'end_conversation' 动作 - final_action = "end_conversation" - final_reason = initial_reason # 保持原始的结束理由 - return final_action, final_reason - - except Exception as end_e: - logger.error(f"[私聊][{self.private_name}]调用结束决策LLM或处理结果时出错: {str(end_e)}") - # 出错时,默认执行原始的结束对话 - logger.warning(f"[私聊][{self.private_name}]结束决策出错,将按原计划执行 end_conversation") - return "end_conversation", initial_reason # 返回原始动作和原因 - - else: - action = initial_action - reason = initial_reason - - # 验证action类型 (保持不变) - valid_actions = [ - "direct_reply", - "send_new_message", - "fetch_knowledge", - "wait", - "listening", - "rethink_goal", - "end_conversation", # 仍然需要验证,因为可能从上面决策后返回 - "block_and_ignore", - "say_goodbye", # 也要验证这个新动作 - ] - if action not in valid_actions: - logger.warning(f"[私聊][{self.private_name}]LLM返回了未知的行动类型: '{action}',强制改为 wait") - reason = f"(原始行动'{action}'无效,已强制改为wait) {reason}" - action = "wait" - - logger.info(f"[私聊][{self.private_name}]规划的行动: {action}") - logger.info(f"[私聊][{self.private_name}]行动原因: {reason}") - return action, reason - - except Exception as e: - # 外层异常处理保持不变 - logger.error(f"[私聊][{self.private_name}]规划行动时调用 LLM 或处理结果出错: {str(e)}") - return "wait", f"行动规划处理中发生错误,暂时等待: {str(e)}" diff --git a/src/experimental/PFC/chat_observer.py b/src/experimental/PFC/chat_observer.py deleted file mode 100644 index 6021ef73c..000000000 --- a/src/experimental/PFC/chat_observer.py +++ /dev/null @@ -1,383 +0,0 @@ -import time -import asyncio -import traceback -from typing import Optional, Dict, Any, List -from src.common.logger import get_logger -from maim_message import UserInfo -from src.config.config import global_config -from src.experimental.PFC.chat_states import ( - NotificationManager, - create_new_message_notification, - create_cold_chat_notification, -) -from src.experimental.PFC.message_storage import PeeweeMessageStorage -from rich.traceback import install - -install(extra_lines=3) - -logger = get_logger("chat_observer") - - -class ChatObserver: - """聊天状态观察器""" - - # 类级别的实例管理 - _instances: Dict[str, "ChatObserver"] = {} - - @classmethod - def get_instance(cls, stream_id: str, private_name: str) -> "ChatObserver": - """获取或创建观察器实例 - - Args: - stream_id: 聊天流ID - private_name: 私聊名称 - - Returns: - ChatObserver: 观察器实例 - """ - if stream_id not in cls._instances: - cls._instances[stream_id] = cls(stream_id, private_name) - return cls._instances[stream_id] - - def __init__(self, stream_id: str, private_name: str): - """初始化观察器 - - Args: - stream_id: 聊天流ID - """ - self.last_check_time = None - self.last_bot_speak_time = None - self.last_user_speak_time = None - if stream_id in self._instances: - 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 = PeeweeMessageStorage() - - # self.last_user_speak_time: Optional[float] = None # 对方上次发言时间 - # self.last_bot_speak_time: Optional[float] = None # 机器人上次发言时间 - # self.last_check_time: float = time.time() # 上次查看聊天记录时间 - self.last_message_read: Optional[Dict[str, Any]] = None # 最后读取的消息ID - self.last_message_time: float = time.time() - - self.waiting_start_time: float = time.time() # 等待开始时间,初始化为当前时间 - - # 运行状态 - self._running: bool = False - self._task: Optional[asyncio.Task] = None - self._update_event = asyncio.Event() # 触发更新的事件 - self._update_complete = asyncio.Event() # 更新完成的事件 - - # 通知管理器 - self.notification_manager = NotificationManager() - - # 冷场检查配置 - self.cold_chat_threshold: float = 60.0 # 60秒无消息判定为冷场 - self.last_cold_chat_check: float = time.time() - self.is_cold_chat_state: bool = False - - self.update_event = asyncio.Event() - self.update_interval = 2 # 更新间隔(秒) - self.message_cache = [] - self.update_running = False - - async def check(self) -> bool: - """检查距离上一次观察之后是否有了新消息 - - Returns: - bool: 是否有新消息 - """ - 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(f"[私聊][{self.private_name}]发现新消息") - self.last_check_time = time.time() - - return new_message_exists - - async def _add_message_to_history(self, message: Dict[str, Any]): - """添加消息到历史记录并发送通知 - - Args: - message: 消息数据 - """ - try: - # 发送新消息通知 - notification = create_new_message_notification( - sender="chat_observer", target="observation_info", message=message - ) - # print(self.notification_manager) - await self.notification_manager.send_notification(notification) - except Exception as e: - logger.error(f"[私聊][{self.private_name}]添加消息到历史记录时出错: {e}") - print(traceback.format_exc()) - - # 检查并更新冷场状态 - await self._check_cold_chat() - - async def _check_cold_chat(self): - """检查是否处于冷场状态并发送通知""" - current_time = time.time() - - # 每10秒检查一次冷场状态 - if current_time - self.last_cold_chat_check < 10: - return - - self.last_cold_chat_check = current_time - - # 判断是否冷场 - is_cold = ( - True - if self.last_message_time is None - else (current_time - self.last_message_time) > self.cold_chat_threshold - ) - - # 如果冷场状态发生变化,发送通知 - if is_cold != self.is_cold_chat_state: - self.is_cold_chat_state = is_cold - notification = create_cold_chat_notification(sender="chat_observer", target="pfc", is_cold=is_cold) - await self.notification_manager.send_notification(notification) - - def new_message_after(self, time_point: float) -> bool: - """判断是否在指定时间点后有新消息 - - Args: - time_point: 时间戳 - - Returns: - bool: 是否有新消息 - """ - - if self.last_message_time is None: - logger.debug(f"[私聊][{self.private_name}]没有最后消息时间,返回 False") - return False - - has_new = self.last_message_time > time_point - logger.debug( - f"[私聊][{self.private_name}]判断是否在指定时间点后有新消息: {self.last_message_time} > {time_point} = {has_new}" - ) - return has_new - - def get_message_history( - self, - start_time: Optional[float] = None, - end_time: Optional[float] = None, - limit: Optional[int] = None, - user_id: Optional[str] = None, - ) -> List[Dict[str, Any]]: - """获取消息历史 - - Args: - start_time: 开始时间戳 - end_time: 结束时间戳 - limit: 限制返回消息数量 - user_id: 指定用户ID - - Returns: - List[Dict[str, Any]]: 消息列表 - """ - filtered_messages = self.message_history - - if start_time is not None: - filtered_messages = [m for m in filtered_messages if m["time"] >= start_time] - - if end_time is not None: - filtered_messages = [m for m in filtered_messages if m["time"] <= end_time] - - if user_id is not None: - filtered_messages = [ - m for m in filtered_messages if UserInfo.from_dict(m.get("user_info", {})).user_id == user_id - ] - - if limit is not None: - filtered_messages = filtered_messages[-limit:] - - return filtered_messages - - async def _fetch_new_messages(self) -> List[Dict[str, Any]]: - """获取新消息 - - Returns: - List[Dict[str, Any]]: 新消息列表 - """ - new_messages = await self.message_storage.get_messages_after(self.stream_id, self.last_message_time) - - if new_messages: - self.last_message_read = new_messages[-1] - self.last_message_time = new_messages[-1]["time"] - - # print(f"获取数据库中找到的新消息: {new_messages}") - - return new_messages - - async def _fetch_new_messages_before(self, time_point: float) -> List[Dict[str, Any]]: - """获取指定时间点之前的消息 - - Args: - time_point: 时间戳 - - Returns: - List[Dict[str, Any]]: 最多5条消息 - """ - new_messages = await self.message_storage.get_messages_before(self.stream_id, time_point) - - if new_messages: - self.last_message_read = new_messages[-1]["message_id"] - - logger.debug(f"[私聊][{self.private_name}]获取指定时间点111之前的消息: {new_messages}") - - return new_messages - - """主要观察循环""" - - async def _update_loop(self): - """更新循环""" - # try: - # start_time = time.time() - # messages = await self._fetch_new_messages_before(start_time) - # for message in messages: - # await self._add_message_to_history(message) - # logger.debug(f"[私聊][{self.private_name}]缓冲消息: {messages}") - # except Exception as e: - # logger.error(f"[私聊][{self.private_name}]缓冲消息出错: {e}") - - while self._running: - try: - # 等待事件或超时(1秒) - try: - # print("等待事件") - await asyncio.wait_for(self._update_event.wait(), timeout=1) - - except asyncio.TimeoutError: - # print("超时") - pass # 超时后也执行一次检查 - - self._update_event.clear() # 重置触发事件 - self._update_complete.clear() # 重置完成事件 - - # 获取新消息 - new_messages = await self._fetch_new_messages() - - if new_messages: - # 处理新消息 - for message in new_messages: - await self._add_message_to_history(message) - - # 设置完成事件 - self._update_complete.set() - - except Exception as e: - logger.error(f"[私聊][{self.private_name}]更新循环出错: {e}") - logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") - self._update_complete.set() # 即使出错也要设置完成事件 - - def trigger_update(self): - """触发一次立即更新""" - self._update_event.set() - - async def wait_for_update(self, timeout: float = 5.0) -> bool: - """等待更新完成 - - Args: - timeout: 超时时间(秒) - - Returns: - bool: 是否成功完成更新(False表示超时) - """ - try: - await asyncio.wait_for(self._update_complete.wait(), timeout=timeout) - return True - except asyncio.TimeoutError: - logger.warning(f"[私聊][{self.private_name}]等待更新完成超时({timeout}秒)") - return False - - def start(self): - """启动观察器""" - if self._running: - return - - self._running = True - self._task = asyncio.create_task(self._update_loop()) - logger.debug(f"[私聊][{self.private_name}]ChatObserver for {self.stream_id} started") - - def stop(self): - """停止观察器""" - self._running = False - self._update_event.set() # 设置事件以解除等待 - self._update_complete.set() # 设置完成事件以解除等待 - if self._task: - self._task.cancel() - logger.debug(f"[私聊][{self.private_name}]ChatObserver for {self.stream_id} stopped") - - async def process_chat_history(self, messages: list): - """处理聊天历史 - - Args: - messages: 消息列表 - """ - self.update_check_time() - - for msg in messages: - try: - user_info = UserInfo.from_dict(msg.get("user_info", {})) - if user_info.user_id == global_config.bot.qq_account: - self.update_bot_speak_time(msg["time"]) - else: - self.update_user_speak_time(msg["time"]) - except Exception as e: - logger.warning(f"[私聊][{self.private_name}]处理消息时间时出错: {e}") - continue - - def update_check_time(self): - """更新查看时间""" - self.last_check_time = time.time() - - def update_bot_speak_time(self, speak_time: Optional[float] = None): - """更新机器人说话时间""" - self.last_bot_speak_time = speak_time or time.time() - - def update_user_speak_time(self, speak_time: Optional[float] = None): - """更新用户说话时间""" - self.last_user_speak_time = speak_time or time.time() - - def get_time_info(self) -> str: - """获取时间信息文本""" - current_time = time.time() - time_info = "" - - if self.last_bot_speak_time: - bot_speak_ago = current_time - self.last_bot_speak_time - time_info += f"\n距离你上次发言已经过去了{int(bot_speak_ago)}秒" - - if self.last_user_speak_time: - user_speak_ago = current_time - self.last_user_speak_time - time_info += f"\n距离对方上次发言已经过去了{int(user_speak_ago)}秒" - - return time_info - - def get_cached_messages(self, limit: int = 50) -> List[Dict[str, Any]]: - """获取缓存的消息历史 - - Args: - limit: 获取的最大消息数量,默认50 - - Returns: - List[Dict[str, Any]]: 缓存的消息历史列表 - """ - return self.message_cache[-limit:] - - def get_last_message(self) -> Optional[Dict[str, Any]]: - """获取最后一条消息 - - Returns: - Optional[Dict[str, Any]]: 最后一条消息,如果没有则返回None - """ - if not self.message_cache: - return None - return self.message_cache[-1] - - def __str__(self): - return f"ChatObserver for {self.stream_id}" diff --git a/src/experimental/PFC/chat_states.py b/src/experimental/PFC/chat_states.py deleted file mode 100644 index 4b839b7bd..000000000 --- a/src/experimental/PFC/chat_states.py +++ /dev/null @@ -1,290 +0,0 @@ -from enum import Enum, auto -from typing import Optional, Dict, Any, List, Set -from dataclasses import dataclass -from datetime import datetime -from abc import ABC, abstractmethod - - -class ChatState(Enum): - """聊天状态枚举""" - - NORMAL = auto() # 正常状态 - NEW_MESSAGE = auto() # 有新消息 - COLD_CHAT = auto() # 冷场状态 - ACTIVE_CHAT = auto() # 活跃状态 - BOT_SPEAKING = auto() # 机器人正在说话 - USER_SPEAKING = auto() # 用户正在说话 - SILENT = auto() # 沉默状态 - ERROR = auto() # 错误状态 - - -class NotificationType(Enum): - """通知类型枚举""" - - NEW_MESSAGE = auto() # 新消息通知 - COLD_CHAT = auto() # 冷场通知 - ACTIVE_CHAT = auto() # 活跃通知 - BOT_SPEAKING = auto() # 机器人说话通知 - USER_SPEAKING = auto() # 用户说话通知 - MESSAGE_DELETED = auto() # 消息删除通知 - USER_JOINED = auto() # 用户加入通知 - USER_LEFT = auto() # 用户离开通知 - ERROR = auto() # 错误通知 - - -@dataclass -class ChatStateInfo: - """聊天状态信息""" - - state: ChatState - last_message_time: Optional[float] = None - last_message_content: Optional[str] = None - last_speaker: Optional[str] = None - message_count: int = 0 - cold_duration: float = 0.0 # 冷场持续时间(秒) - active_duration: float = 0.0 # 活跃持续时间(秒) - - -@dataclass -class Notification: - """通知基类""" - - type: NotificationType - timestamp: float - sender: str # 发送者标识 - target: str # 接收者标识 - data: Dict[str, Any] - - def to_dict(self) -> Dict[str, Any]: - """转换为字典格式""" - return {"type": self.type.name, "timestamp": self.timestamp, "data": self.data} - - -@dataclass -class StateNotification(Notification): - """持续状态通知""" - - is_active: bool = True - - def to_dict(self) -> Dict[str, Any]: - base_dict = super().to_dict() - base_dict["is_active"] = self.is_active - return base_dict - - -class NotificationHandler(ABC): - """通知处理器接口""" - - @abstractmethod - async def handle_notification(self, notification: Notification): - """处理通知""" - pass - - -class NotificationManager: - """通知管理器""" - - def __init__(self): - # 按接收者和通知类型存储处理器 - self._handlers: Dict[str, Dict[NotificationType, List[NotificationHandler]]] = {} - self._active_states: Set[NotificationType] = set() - self._notification_history: List[Notification] = [] - - def register_handler(self, target: str, notification_type: NotificationType, handler: NotificationHandler): - """注册通知处理器 - - Args: - target: 接收者标识(例如:"pfc") - notification_type: 要处理的通知类型 - handler: 处理器实例 - """ - if target not in self._handlers: - self._handlers[target] = {} - if notification_type not in self._handlers[target]: - self._handlers[target][notification_type] = [] - # print(self._handlers[target][notification_type]) - self._handlers[target][notification_type].append(handler) - # print(self._handlers[target][notification_type]) - - def unregister_handler(self, target: str, notification_type: NotificationType, handler: NotificationHandler): - """注销通知处理器 - - Args: - target: 接收者标识 - notification_type: 通知类型 - handler: 要注销的处理器实例 - """ - if target in self._handlers and notification_type in self._handlers[target]: - handlers = self._handlers[target][notification_type] - if handler in handlers: - handlers.remove(handler) - # 如果该类型的处理器列表为空,删除该类型 - if not handlers: - del self._handlers[target][notification_type] - # 如果该目标没有任何处理器,删除该目标 - if not self._handlers[target]: - del self._handlers[target] - - async def send_notification(self, notification: Notification): - """发送通知""" - self._notification_history.append(notification) - - # 如果是状态通知,更新活跃状态 - if isinstance(notification, StateNotification): - if notification.is_active: - self._active_states.add(notification.type) - else: - self._active_states.discard(notification.type) - - # 调用目标接收者的处理器 - target = notification.target - if target in self._handlers: - handlers = self._handlers[target].get(notification.type, []) - # print(handlers) - for handler in handlers: - # print(f"调用处理器: {handler}") - await handler.handle_notification(notification) - - def get_active_states(self) -> Set[NotificationType]: - """获取当前活跃的状态""" - return self._active_states.copy() - - def is_state_active(self, state_type: NotificationType) -> bool: - """检查特定状态是否活跃""" - return state_type in self._active_states - - def get_notification_history( - self, sender: Optional[str] = None, target: Optional[str] = None, limit: Optional[int] = None - ) -> List[Notification]: - """获取通知历史 - - Args: - sender: 过滤特定发送者的通知 - target: 过滤特定接收者的通知 - limit: 限制返回数量 - """ - history = self._notification_history - - if sender: - history = [n for n in history if n.sender == sender] - if target: - history = [n for n in history if n.target == target] - - if limit is not None: - history = history[-limit:] - - return history - - def __str__(self): - str = "" - for target, handlers in self._handlers.items(): - for notification_type, handler_list in handlers.items(): - str += f"NotificationManager for {target} {notification_type} {handler_list}" - return str - - -# 一些常用的通知创建函数 -def create_new_message_notification(sender: str, target: str, message: Dict[str, Any]) -> Notification: - """创建新消息通知""" - return Notification( - type=NotificationType.NEW_MESSAGE, - timestamp=datetime.now().timestamp(), - sender=sender, - target=target, - data={ - "message_id": message.get("message_id"), - "processed_plain_text": message.get("processed_plain_text"), - "detailed_plain_text": message.get("detailed_plain_text"), - "user_info": message.get("user_info"), - "time": message.get("time"), - }, - ) - - -def create_cold_chat_notification(sender: str, target: str, is_cold: bool) -> StateNotification: - """创建冷场状态通知""" - return StateNotification( - type=NotificationType.COLD_CHAT, - timestamp=datetime.now().timestamp(), - sender=sender, - target=target, - data={"is_cold": is_cold}, - is_active=is_cold, - ) - - -def create_active_chat_notification(sender: str, target: str, is_active: bool) -> StateNotification: - """创建活跃状态通知""" - return StateNotification( - type=NotificationType.ACTIVE_CHAT, - timestamp=datetime.now().timestamp(), - sender=sender, - target=target, - data={"is_active": is_active}, - is_active=is_active, - ) - - -class ChatStateManager: - """聊天状态管理器""" - - def __init__(self): - self.current_state = ChatState.NORMAL - self.state_info = ChatStateInfo(state=ChatState.NORMAL) - self.state_history: list[ChatStateInfo] = [] - - def update_state(self, new_state: ChatState, **kwargs): - """更新聊天状态 - - Args: - new_state: 新的状态 - **kwargs: 其他状态信息 - """ - self.current_state = new_state - self.state_info.state = new_state - - # 更新其他状态信息 - for key, value in kwargs.items(): - if hasattr(self.state_info, key): - setattr(self.state_info, key, value) - - # 记录状态历史 - self.state_history.append(self.state_info) - - def get_current_state_info(self) -> ChatStateInfo: - """获取当前状态信息""" - return self.state_info - - def get_state_history(self) -> list[ChatStateInfo]: - """获取状态历史""" - return self.state_history - - def is_cold_chat(self, threshold: float = 60.0) -> bool: - """判断是否处于冷场状态 - - Args: - threshold: 冷场阈值(秒) - - Returns: - bool: 是否冷场 - """ - if not self.state_info.last_message_time: - return True - - current_time = datetime.now().timestamp() - return (current_time - self.state_info.last_message_time) > threshold - - def is_active_chat(self, threshold: float = 5.0) -> bool: - """判断是否处于活跃状态 - - Args: - threshold: 活跃阈值(秒) - - Returns: - bool: 是否活跃 - """ - if not self.state_info.last_message_time: - return False - - current_time = datetime.now().timestamp() - return (current_time - self.state_info.last_message_time) <= threshold diff --git a/src/experimental/PFC/conversation.py b/src/experimental/PFC/conversation.py deleted file mode 100644 index c333f3998..000000000 --- a/src/experimental/PFC/conversation.py +++ /dev/null @@ -1,700 +0,0 @@ -import time -import asyncio -import datetime - -# from .message_storage import MongoDBMessageStorage -from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat - -# from ...config.config import global_config -from typing import Dict, Any, Optional -from src.chat.message_receive.message import Message -from .pfc_types import ConversationState -from .pfc import ChatObserver, GoalAnalyzer -from src.common.logger import get_logger -from .action_planner import ActionPlanner -from .observation_info import ObservationInfo -from .conversation_info import ConversationInfo # 确保导入 ConversationInfo -from .reply_generator import ReplyGenerator -from src.chat.message_receive.chat_stream import ChatStream -from src.chat.message_receive.message import UserInfo -from src.chat.message_receive.chat_stream import get_chat_manager -from .pfc_KnowledgeFetcher import KnowledgeFetcher -from .waiter import Waiter - -import traceback -from rich.traceback import install - -install(extra_lines=3) - -logger = get_logger("pfc") - - -class Conversation: - """对话类,负责管理单个对话的状态和行为""" - - 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 - - # 回复相关 - self.generated_reply = "" - - async def _initialize(self): - """初始化实例,注册所有组件""" - - try: - 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.private_name) - self.waiter = Waiter(self.stream_id, self.private_name) - self.direct_sender = DirectMessageSender(self.private_name) - - # 获取聊天流信息 - self.chat_stream = get_chat_manager().get_stream(self.stream_id) - - self.stop_action_planner = False - except Exception as e: - 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.private_name) - self.chat_observer.start() - self.observation_info = ObservationInfo(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() - except Exception as e: - logger.error(f"[私聊][{self.private_name}]初始化对话实例:注册信息组件失败: {e}") - logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") - raise - try: - 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(), - limit=30, # 加载最近30条作为初始上下文,可以调整 - ) - chat_talking_prompt = build_readable_messages( - initial_messages, - replace_bot_name=True, - merge_messages=False, - timestamp_mode="relative", - read_mark=0.0, - ) - if initial_messages: - # 将加载的消息填充到 ObservationInfo 的 chat_history - self.observation_info.chat_history = initial_messages - self.observation_info.chat_history_str = chat_talking_prompt + "\n" - self.observation_info.chat_history_count = len(initial_messages) - - # 更新 ObservationInfo 中的时间戳等信息 - last_msg = initial_messages[-1] - self.observation_info.last_message_time = last_msg.get("time") - last_user_info = UserInfo.from_dict(last_msg.get("user_info", {})) - self.observation_info.last_message_sender = last_user_info.user_id - self.observation_info.last_message_content = last_msg.get("processed_plain_text", "") - - logger.info( - 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(f"[私聊][{self.private_name}]没有找到初始聊天记录。") - - except Exception as load_err: - logger.error(f"[私聊][{self.private_name}]加载初始聊天记录时出错: {load_err}") - # 出错也要继续,只是没有历史记录而已 - # 组件准备完成,启动该论对话 - self.should_continue = True - asyncio.create_task(self.start()) - - async def start(self): - """开始对话流程""" - try: - logger.info(f"[私聊][{self.private_name}]对话系统启动中...") - asyncio.create_task(self._plan_and_action_loop()) - except Exception as e: - logger.error(f"[私聊][{self.private_name}]启动对话系统失败: {e}") - raise - - 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) - continue - elif self.ignore_until_timestamp and time.time() >= self.ignore_until_timestamp: - logger.info(f"[私聊][{self.private_name}]忽略时间已到 {self.stream_id},准备结束对话。") - self.ignore_until_timestamp = None - self.should_continue = False - continue - try: - # --- 在规划前记录当前新消息数量 --- - initial_new_message_count = 0 - 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." - ) - - # --- 调用 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 - ) - - # --- 规划后检查是否有 *更多* 新消息到达 --- - current_new_message_count = 0 - 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." - ) - - if current_new_message_count > initial_new_message_count + 2: - logger.info( - f"[私聊][{self.private_name}]规划期间发现新增消息 ({initial_new_message_count} -> {current_new_message_count}),跳过本次行动,重新规划" - ) - # 如果规划期间有新消息,也应该重置上次回复状态,因为现在要响应新消息了 - self.conversation_info.last_successful_reply_action = None - await asyncio.sleep(0.1) - continue - - # 包含 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} 条规划时已知的新消息。" - ) - 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 方法!" - ) - - 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_item in self.conversation_info.goal_list: - if 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(f"[私聊][{self.private_name}]检测到'结束对话'目标,停止循环。") - - except Exception as loop_err: - 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"[私聊][{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( - f"[私聊][{self.private_name}]ObservationInfo 未初始化或缺少 'new_messages_count' 属性,无法检查新消息。" - ) - return False # 或者根据需要抛出错误 - - if self.observation_info.new_messages_count > 2: - logger.info( - 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( - f"[私聊][{self.private_name}]ConversationInfo 未初始化,无法重置 last_successful_reply_action。" - ) - return True - return False - - def _convert_to_message(self, msg_dict: Dict[str, Any]) -> Message: - """将消息字典转换为Message对象""" - try: - # 尝试从 msg_dict 直接获取 chat_stream,如果失败则从全局 get_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 = get_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.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"[私聊][{self.private_name}]转换消息时出错: {e}") - # 可以选择返回 None 或重新抛出异常,这里选择重新抛出以指示问题 - raise ValueError(f"无法将字典转换为 Message 对象: {e}") from e - - async def _handle_action( - self, action: str, reason: str, observation_info: ObservationInfo, conversation_info: ConversationInfo - ): - """处理规划的行动""" - - logger.debug(f"[私聊][{self.private_name}]执行行动: {action}, 原因: {reason}") - - # 记录action历史 (逻辑不变) - current_action_record = { - "action": action, - "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 执行 --- - - # send_new_message 失败后执行 wait - if action == "send_new_message": - 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"[私聊][{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}" - ) - - # 2. 检查回复 (逻辑不变) - self.state = ConversationState.CHECKING - try: - 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, - chat_history=observation_info.chat_history, - chat_history_str=observation_info.chat_history_str, - retry_count=reply_attempt_count - 1, - ) - logger.info( - 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"[私聊][{self.private_name}]第 {reply_attempt_count} 次追问检查建议重新规划,停止尝试。原因: {check_reason}" - ) - break - except Exception as check_err: - logger.error( - f"[私聊][{self.private_name}]第 {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(f"[私聊][{self.private_name}]生成追问回复期间收到新消息,取消发送,重新规划行动") - 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() # <--- 调用恢复后的函数 - - # 更新状态: 标记上次成功是 send_new_message - 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}"} - ) - # 重置状态: 追问失败,下次用初始 prompt - self.conversation_info.last_successful_reply_action = None - - # 执行 Wait 操作 - logger.info(f"[私聊][{self.private_name}]由于无法生成合适追问回复,执行 '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"[私聊][{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}" - ) - - # 2. 检查回复 - self.state = ConversationState.CHECKING - try: - 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, - chat_history=observation_info.chat_history, - chat_history_str=observation_info.chat_history_str, - retry_count=reply_attempt_count - 1, - ) - logger.info( - 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"[私聊][{self.private_name}]第 {reply_attempt_count} 次首次回复检查建议重新规划,停止尝试。原因: {check_reason}" - ) - break - except Exception as check_err: - logger.error( - f"[私聊][{self.private_name}]第 {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(f"[私聊][{self.private_name}]生成首次回复期间收到新消息,取消发送,重新规划行动") - 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 # 标记动作成功 - - 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}"} - ) - # 重置状态: 首次回复失败,下次还是用初始 prompt - self.conversation_info.last_successful_reply_action = None - - # 执行 Wait 操作 (保持原有逻辑) - logger.info(f"[私聊][{self.private_name}]由于无法生成合适首次回复,执行 'wait' 操作...") - self.state = ConversationState.WAITING - await self.waiter.wait(self.conversation_info) - wait_action_record = { - "action": "wait", - "plan_reason": "因 direct_reply 多次尝试失败而执行的后备等待", - "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.state = ConversationState.FETCHING - knowledge_query = reason - try: - # 检查 knowledge_fetcher 是否存在 - if not hasattr(self, "knowledge_fetcher"): - 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"[私聊][{self.private_name}]获取到知识: {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"[私聊][{self.private_name}]获取知识时出错: {str(fetch_err)}") - conversation_info.done_action[action_index].update( - {"status": "recall", "final_reason": f"获取知识失败: {str(fetch_err)}"} - ) - 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"): - 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"[私聊][{self.private_name}]重新思考目标时出错: {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(f"[私聊][{self.private_name}]倾听对方发言...") - try: - # 检查 waiter 是否存在 - if not hasattr(self, "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"[私聊][{self.private_name}]倾听时出错: {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 == "say_goodbye": - self.state = ConversationState.GENERATING # 也可以定义一个新的状态,如 ENDING - logger.info(f"[私聊][{self.private_name}]执行行动: 生成并发送告别语...") - try: - # 1. 生成告别语 (使用 'say_goodbye' action_type) - self.generated_reply = await self.reply_generator.generate( - observation_info, conversation_info, action_type="say_goodbye" - ) - logger.info(f"[私聊][{self.private_name}]生成的告别语: {self.generated_reply}") - - # 2. 直接发送告别语 (不经过检查) - if self.generated_reply: # 确保生成了内容 - await self._send_reply() # 调用发送方法 - # 发送成功后,标记动作成功 - action_successful = True - logger.info(f"[私聊][{self.private_name}]告别语已发送。") - else: - logger.warning(f"[私聊][{self.private_name}]未能生成告别语内容,无法发送。") - action_successful = False # 标记动作失败 - conversation_info.done_action[action_index].update( - {"status": "recall", "final_reason": "未能生成告别语内容"} - ) - - # 3. 无论是否发送成功,都准备结束对话 - self.should_continue = False - logger.info(f"[私聊][{self.private_name}]发送告别语流程结束,即将停止对话实例。") - - except Exception as goodbye_err: - logger.error(f"[私聊][{self.private_name}]生成或发送告别语时出错: {goodbye_err}") - logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") - # 即使出错,也结束对话 - self.should_continue = False - action_successful = False # 标记动作失败 - conversation_info.done_action[action_index].update( - {"status": "recall", "final_reason": f"生成或发送告别语时出错: {goodbye_err}"} - ) - - elif action == "end_conversation": - # 这个分支现在只会在 action_planner 最终决定不告别时被调用 - self.should_continue = False - logger.info(f"[私聊][{self.private_name}]收到最终结束指令,停止对话...") - action_successful = True # 标记这个指令本身是成功的 - - elif action == "block_and_ignore": - 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)}" - ) - self.state = ConversationState.IGNORED - action_successful = True # 标记动作成功 - - else: # 对应 'wait' 动作 - self.state = ConversationState.WAITING - logger.info(f"[私聊][{self.private_name}]等待更多信息...") - try: - # 检查 waiter 是否存在 - if not hasattr(self, "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"[私聊][{self.private_name}]等待时出错: {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"), - } - ) - # 重置状态: 对于非回复类动作的成功,清除上次回复状态 - if action not in ["direct_reply", "send_new_message"]: - self.conversation_info.last_successful_reply_action = None - 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(f"[私聊][{self.private_name}]没有生成回复内容,无法发送。") - return - - 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(f"[私聊][{self.private_name}]DirectMessageSender 未初始化,无法发送回复。") - return - if not self.chat_stream: - logger.error(f"[私聊][{self.private_name}]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(f"[私聊][{self.private_name}]等待 ChatObserver 更新完成超时") - - self.state = ConversationState.ANALYZING # 更新状态 - - except Exception as e: - 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): - """发送超时结束消息""" - try: - messages = self.chat_observer.get_cached_messages(limit=1) - if not messages: - return - - latest_message = self._convert_to_message(messages[0]) - await self.direct_sender.send_message( - chat_stream=self.chat_stream, content="TODO:超时消息", reply_to_message=latest_message - ) - except Exception as e: - logger.error(f"[私聊][{self.private_name}]发送超时消息失败: {str(e)}") diff --git a/src/experimental/PFC/conversation_info.py b/src/experimental/PFC/conversation_info.py deleted file mode 100644 index 04524b697..000000000 --- a/src/experimental/PFC/conversation_info.py +++ /dev/null @@ -1,10 +0,0 @@ -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/experimental/PFC/message_storage.py b/src/experimental/PFC/message_storage.py deleted file mode 100644 index 2505a06f5..000000000 --- a/src/experimental/PFC/message_storage.py +++ /dev/null @@ -1,131 +0,0 @@ -from abc import ABC, abstractmethod -from typing import List, Dict, Any, Callable - -from playhouse import shortcuts - -from src.common.database.database_model import Messages # Peewee Messages 模型导入 - -model_to_dict: Callable[..., dict] = shortcuts.model_to_dict # Peewee 模型转换为字典的快捷函数 - - -class MessageStorage(ABC): - """消息存储接口""" - - @abstractmethod - async def get_messages_after(self, chat_id: str, message: Dict[str, Any]) -> List[Dict[str, Any]]: - """获取指定消息ID之后的所有消息 - - Args: - chat_id: 聊天ID - message: 消息 - - Returns: - List[Dict[str, Any]]: 消息列表 - """ - pass - - @abstractmethod - async def get_messages_before(self, chat_id: str, time_point: float, limit: int = 5) -> List[Dict[str, Any]]: - """获取指定时间点之前的消息 - - Args: - chat_id: 聊天ID - time_point: 时间戳 - limit: 最大消息数量 - - Returns: - List[Dict[str, Any]]: 消息列表 - """ - pass - - @abstractmethod - async def has_new_messages(self, chat_id: str, after_time: float) -> bool: - """检查是否有新消息 - - Args: - chat_id: 聊天ID - after_time: 时间戳 - - Returns: - bool: 是否有新消息 - """ - pass - - -class PeeweeMessageStorage(MessageStorage): - """Peewee消息存储实现""" - - async def get_messages_after(self, chat_id: str, message_time: float) -> List[Dict[str, Any]]: - query = ( - Messages.select() - .where((Messages.chat_id == chat_id) & (Messages.time > message_time)) - .order_by(Messages.time.asc()) - ) - - # print(f"storage_check_message: {message_time}") - messages_models = list(query) - return [model_to_dict(msg) for msg in messages_models] - - async def get_messages_before(self, chat_id: str, time_point: float, limit: int = 5) -> List[Dict[str, Any]]: - query = ( - Messages.select() - .where((Messages.chat_id == chat_id) & (Messages.time < time_point)) - .order_by(Messages.time.desc()) - .limit(limit) - ) - - messages_models = list(query) - # 将消息按时间正序排列 - messages_models.reverse() - return [model_to_dict(msg) for msg in messages_models] - - async def has_new_messages(self, chat_id: str, after_time: float) -> bool: - return Messages.select().where((Messages.chat_id == chat_id) & (Messages.time > after_time)).exists() - - -# # 创建一个内存消息存储实现,用于测试 -# class InMemoryMessageStorage(MessageStorage): -# """内存消息存储实现,主要用于测试""" - -# def __init__(self): -# self.messages: Dict[str, List[Dict[str, Any]]] = {} - -# async def get_messages_after(self, chat_id: str, message_id: Optional[str] = None) -> List[Dict[str, Any]]: -# if chat_id not in self.messages: -# return [] - -# messages = self.messages[chat_id] -# if not message_id: -# return messages - -# # 找到message_id的索引 -# try: -# index = next(i for i, m in enumerate(messages) if m["message_id"] == message_id) -# return messages[index + 1:] -# except StopIteration: -# return [] - -# async def get_messages_before(self, chat_id: str, time_point: float, limit: int = 5) -> List[Dict[str, Any]]: -# if chat_id not in self.messages: -# return [] - -# messages = [ -# m for m in self.messages[chat_id] -# if m["time"] < time_point -# ] - -# return messages[-limit:] - -# async def has_new_messages(self, chat_id: str, after_time: float) -> bool: -# if chat_id not in self.messages: -# return False - -# return any(m["time"] > after_time for m in self.messages[chat_id]) - -# # 测试辅助方法 -# def add_message(self, chat_id: str, message: Dict[str, Any]): -# """添加测试消息""" -# if chat_id not in self.messages: -# self.messages[chat_id] = [] -# self.messages[chat_id].append(message) -# self.messages[chat_id].sort(key=lambda m: m["time"]) diff --git a/src/experimental/PFC/observation_info.py b/src/experimental/PFC/observation_info.py deleted file mode 100644 index 5a7d72da8..000000000 --- a/src/experimental/PFC/observation_info.py +++ /dev/null @@ -1,389 +0,0 @@ -from typing import List, Optional, Dict, Any, Set -from maim_message import UserInfo -import time -from src.common.logger import get_logger -from src.experimental.PFC.chat_observer import ChatObserver -from src.experimental.PFC.chat_states import NotificationHandler, NotificationType, Notification -from src.chat.utils.chat_message_builder import build_readable_messages -import traceback # 导入 traceback 用于调试 - -logger = get_logger("observation_info") - - -class ObservationInfoHandler(NotificationHandler): - """ObservationInfo的通知处理器""" - - def __init__(self, observation_info: "ObservationInfo", private_name: str): - """初始化处理器 - - 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: Notification): # 添加类型提示 - # 获取通知类型和数据 - notification_type = notification.type - data = notification.data - - 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") - - # 确保 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 - - 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.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.ACTIVE_CHAT: - # 处理活跃通知 (通常由 COLD_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.USER_SPEAKING: - # 处理用户说话通知 - self.observation_info.is_typing = False - self.observation_info.last_user_speak_time = time.time() - - 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.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的通知信息 (手动实现 __init__)""" - - # 类型提示保留,可用于文档和静态分析 - private_name: str - chat_history: List[Dict[str, Any]] - chat_history_str: str - unprocessed_messages: List[Dict[str, Any]] - active_users: Set[str] - last_bot_speak_time: Optional[float] - last_user_speak_time: Optional[float] - last_message_time: Optional[float] - last_message_id: Optional[str] - last_message_content: str - last_message_sender: Optional[str] - bot_id: Optional[str] - chat_history_count: int - new_messages_count: int - cold_chat_start_time: Optional[float] - cold_chat_duration: float - is_typing: bool - is_cold_chat: bool - changed: bool - chat_observer: Optional[ChatObserver] - handler: Optional[ObservationInfoHandler] - - def __init__(self, private_name: str): - """ - 手动初始化 ObservationInfo 的所有实例变量。 - """ - - # 接收的参数 - self.private_name: str = private_name - - # data_list - self.chat_history: List[Dict[str, Any]] = [] - self.chat_history_str: str = "" - self.unprocessed_messages: List[Dict[str, Any]] = [] - self.active_users: Set[str] = set() - - # data - self.last_bot_speak_time: Optional[float] = None - self.last_user_speak_time: Optional[float] = None - self.last_message_time: Optional[float] = None - self.last_message_id: Optional[str] = None - self.last_message_content: str = "" - self.last_message_sender: Optional[str] = None - self.bot_id: Optional[str] = None - self.chat_history_count: int = 0 - self.new_messages_count: int = 0 - self.cold_chat_start_time: Optional[float] = None - self.cold_chat_duration: float = 0.0 - - # state - self.is_typing: bool = False - self.is_cold_chat: bool = False - self.changed: bool = False - - # 关联对象 - self.chat_observer: Optional[ChatObserver] = None - - self.handler: ObservationInfoHandler = ObservationInfoHandler(self, self.private_name) - - def bind_to_chat_observer(self, chat_observer: ChatObserver): - """绑定到指定的chat_observer - - Args: - chat_observer: 要绑定的 ChatObserver 实例 - """ - if self.chat_observer: - logger.warning(f"[私聊][{self.private_name}]尝试重复绑定 ChatObserver") - return - - self.chat_observer = chat_observer - try: - if not self.handler: # 确保 handler 已经被创建 - logger.error(f"[私聊][{self.private_name}] 尝试绑定时 handler 未初始化!") - self.chat_observer = None # 重置,防止后续错误 - return - - # 注册关心的通知类型 - 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 and hasattr(self.chat_observer, "notification_manager") and self.handler - ): # 增加 handler 检查 - 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 不存在、无效或 handler 未设置") - - # 修改:update_from_message 接收 UserInfo 对象 - async def update_from_message(self, message: Dict[str, Any], user_info: Optional[UserInfo]): - """从消息更新信息 - - Args: - message: 消息数据字典 - user_info: 解析后的 UserInfo 对象 (可能为 None) - """ - message_time = message.get("time") - message_id = message.get("message_id") - processed_text = 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 - - 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: - # 如果消息时间戳不是最新的,可能不需要处理,或者记录一个警告 - 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): - """更新冷场状态 - - Args: - is_cold: 是否处于冷场状态 - current_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: 最后一条消息到现在的时长(秒) - """ - if not self.last_message_time: - return 0.0 - return time.time() - self.last_message_time - - def get_user_response_time(self) -> Optional[float]: - """获取用户最后响应时间 (距离用户最后发言的时间) - - Returns: - Optional[float]: 用户最后发言到现在的时长(秒),如果没有用户发言则返回None - """ - if not self.last_user_speak_time: - return None - return time.time() - self.last_user_speak_time - - def get_bot_response_time(self) -> Optional[float]: - """获取机器人最后响应时间 (距离机器人最后发言的时间) - - Returns: - Optional[float]: 机器人最后发言到现在的时长(秒),如果没有机器人发言则返回None - """ - if not self.last_bot_speak_time: - return None - return time.time() - self.last_bot_speak_time - - async def clear_unprocessed_messages(self): - """将未处理消息移入历史记录,并更新相关状态""" - 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 = 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.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() # 状态改变 diff --git a/src/experimental/PFC/pfc.py b/src/experimental/PFC/pfc.py deleted file mode 100644 index 4050ae58e..000000000 --- a/src/experimental/PFC/pfc.py +++ /dev/null @@ -1,346 +0,0 @@ -from typing import List, Tuple, TYPE_CHECKING -from src.common.logger import get_logger -from src.llm_models.utils_model import LLMRequest -from src.config.config import global_config -from src.experimental.PFC.chat_observer import ChatObserver -from src.experimental.PFC.pfc_utils import get_items_from_json -from src.individuality.individuality import get_individuality -from src.experimental.PFC.conversation_info import ConversationInfo -from src.experimental.PFC.observation_info import ObservationInfo -from src.chat.utils.chat_message_builder import build_readable_messages -from rich.traceback import install - -install(extra_lines=3) - -if TYPE_CHECKING: - pass - -logger = get_logger("pfc") - - -def _calculate_similarity(goal1: str, goal2: str) -> float: - """简单计算两个目标之间的相似度 - - 这里使用一个简单的实现,实际可以使用更复杂的文本相似度算法 - - Args: - goal1: 第一个目标 - goal2: 第二个目标 - - Returns: - float: 相似度得分 (0-1) - """ - # 简单实现:检查重叠字数比例 - words1 = set(goal1) - words2 = set(goal2) - overlap = len(words1.intersection(words2)) - total = len(words1.union(words2)) - return overlap / total if total > 0 else 0 - - -class GoalAnalyzer: - """对话目标分析器""" - - def __init__(self, stream_id: str, private_name: str): - # TODO: API-Adapter修改标记 - self.llm = LLMRequest( - model=global_config.model.utils, temperature=0.7, max_tokens=1000, request_type="conversation_goal" - ) - - self.personality_info = get_individuality().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 - self.chat_observer = ChatObserver.get_instance(stream_id, private_name) - - # 多目标存储结构 - self.goals = [] # 存储多个目标 - self.max_goals = 3 # 同时保持的最大目标数量 - self.current_goal_and_reason = None - - async def analyze_goal(self, conversation_info: ConversationInfo, observation_info: ObservationInfo): - """分析对话历史并设定目标 - - Args: - conversation_info: 对话信息 - observation_info: 观察信息 - - Returns: - Tuple[str, str, str]: (目标, 方法, 原因) - """ - # 构建对话目标 - goals_str = "" - if conversation_info.goal_list: - for goal_reason in conversation_info.goal_list: - if isinstance(goal_reason, dict): - 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 - else: - goal = "目前没有明确对话目标" - reasoning = "目前没有明确对话目标,最好思考一个对话目标" - goals_str = f"目标:{goal},产生该对话目标的原因:{reasoning}\n" - - # 获取聊天历史记录 - chat_history_text = observation_info.chat_history_str - - if observation_info.new_messages_count > 0: - new_messages_list = observation_info.unprocessed_messages - new_messages_str = 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_text = f"你的名字是{self.name},{self.personality_info}。" - # 构建action历史文本 - action_history_list = conversation_info.done_action - action_history_text = "你之前做的事情是:" - for action in action_history_list: - action_history_text += f"{action}\n" - - prompt = f"""{persona_text}。现在你在参与一场QQ聊天,请分析以下聊天记录,并根据你的性格特征确定多个明确的对话目标。 -这些目标应该反映出对话的不同方面和意图。 - -{action_history_text} -当前对话目标: -{goals_str} - -聊天记录: -{chat_history_text} - -请分析当前对话并确定最适合的对话目标。你可以: -1. 保持现有目标不变 -2. 修改现有目标 -3. 添加新目标 -4. 删除不再相关的目标 -5. 如果你想结束对话,请设置一个目标,目标goal为"结束对话",原因reasoning为你希望结束对话 - -请以JSON数组格式输出当前的所有对话目标,每个目标包含以下字段: -1. goal: 对话目标(简短的一句话) -2. reasoning: 对话原因,为什么设定这个目标(简要解释) - -输出格式示例: -[ -{{ - "goal": "回答用户关于Python编程的具体问题", - "reasoning": "用户提出了关于Python的技术问题,需要专业且准确的解答" -}}, -{{ - "goal": "回答用户关于python安装的具体问题", - "reasoning": "用户提出了关于Python的技术问题,需要专业且准确的解答" -}} -]""" - - logger.debug(f"[私聊][{self.private_name}]发送到LLM的提示词: {prompt}") - try: - content, _ = await self.llm.generate_response_async(prompt) - logger.debug(f"[私聊][{self.private_name}]LLM原始返回内容: {content}") - except Exception as e: - logger.error(f"[私聊][{self.private_name}]分析对话目标时出错: {str(e)}") - content = "" - - # 使用改进后的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, - ) - - if success: - # 判断结果是单个字典还是字典列表 - if isinstance(result, list): - # 清空现有目标列表并添加新目标 - conversation_info.goal_list = [] - for item in result: - conversation_info.goal_list.append(item) - - # 返回第一个目标作为当前主要目标(如果有) - if result: - first_goal = result[0] - return first_goal.get("goal", ""), "", first_goal.get("reasoning", "") - else: - # 单个目标的情况 - conversation_info.goal_list.append(result) - return goal, "", reasoning - - # 如果解析失败,返回默认值 - return "", "", "" - - async def _update_goals(self, new_goal: str, method: str, reasoning: str): - """更新目标列表 - - Args: - new_goal: 新的目标 - method: 实现目标的方法 - reasoning: 目标的原因 - """ - # 检查新目标是否与现有目标相似 - for i, (existing_goal, _, _) in enumerate(self.goals): - if _calculate_similarity(new_goal, existing_goal) > 0.7: # 相似度阈值 - # 更新现有目标 - self.goals[i] = (new_goal, method, reasoning) - # 将此目标移到列表前面(最主要的位置) - self.goals.insert(0, self.goals.pop(i)) - return - - # 添加新目标到列表前面 - self.goals.insert(0, (new_goal, method, reasoning)) - - # 限制目标数量 - if len(self.goals) > self.max_goals: - self.goals.pop() # 移除最老的目标 - - async def get_all_goals(self) -> List[Tuple[str, str, str]]: - """获取所有当前目标 - - Returns: - List[Tuple[str, str, str]]: 目标列表,每项为(目标, 方法, 原因) - """ - return self.goals.copy() - - async def get_alternative_goals(self) -> List[Tuple[str, str, str]]: - """获取除了当前主要目标外的其他备选目标 - - Returns: - List[Tuple[str, str, str]]: 备选目标列表 - """ - if len(self.goals) <= 1: - return [] - return self.goals[1:].copy() - - async def analyze_conversation(self, goal, reasoning): - messages = self.chat_observer.get_cached_messages() - chat_history_text = build_readable_messages( - messages, - replace_bot_name=True, - merge_messages=False, - timestamp_mode="relative", - read_mark=0.0, - ) - - persona_text = f"你的名字是{self.name},{self.personality_info}。" - # ===> Persona 文本构建结束 <=== - - # --- 修改 Prompt 字符串,使用 persona_text --- - prompt = f"""{persona_text}。现在你在参与一场QQ聊天, - 当前对话目标:{goal} - 产生该对话目标的原因:{reasoning} - - 请分析以下聊天记录,并根据你的性格特征评估该目标是否已经达到,或者你是否希望停止该次对话。 - 聊天记录: - {chat_history_text} - 请以JSON格式输出,包含以下字段: - 1. goal_achieved: 对话目标是否已经达到(true/false) - 2. stop_conversation: 是否希望停止该次对话(true/false) - 3. reason: 为什么希望停止该次对话(简要解释) - -输出格式示例: -{{ - "goal_achieved": true, - "stop_conversation": false, - "reason": "虽然目标已达成,但对话仍然有继续的价值" -}}""" - - try: - content, _ = await self.llm.generate_response_async(prompt) - logger.debug(f"[私聊][{self.private_name}]LLM原始返回内容: {content}") - - # 尝试解析JSON - success, result = get_items_from_json( - content, - self.private_name, - "goal_achieved", - "stop_conversation", - "reason", - required_types={"goal_achieved": bool, "stop_conversation": bool, "reason": str}, - ) - - if not success: - logger.error(f"[私聊][{self.private_name}]无法解析对话分析结果JSON") - return False, False, "解析结果失败" - - goal_achieved = result["goal_achieved"] - stop_conversation = result["stop_conversation"] - reason = result["reason"] - - return goal_achieved, stop_conversation, reason - - except Exception as e: - logger.error(f"[私聊][{self.private_name}]分析对话状态时出错: {str(e)}") - return False, False, f"分析出错: {str(e)}" - - -# 先注释掉,万一以后出问题了还能开回来((( -# class DirectMessageSender: -# """直接发送消息到平台的发送器""" - -# def __init__(self, private_name: str): -# self.logger = get_logger("direct_sender") -# self.storage = MessageStorage() -# self.private_name = private_name - -# async def send_via_ws(self, message: MessageSending) -> None: -# try: -# await get_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: -# """直接发送消息到平台 - -# 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(), -# ) - -# # 处理消息 -# await message.process() - -# _message_json = message.to_dict() - -# # 发送消息 -# try: -# await self.send_via_ws(message) -# await self.storage.store_message(message, chat_stream) -# logger.info(f"[私聊][{self.private_name}]PFC消息已发送: {content}") -# except Exception as e: -# logger.error(f"[私聊][{self.private_name}]PFC消息发送失败: {str(e)}") diff --git a/src/experimental/PFC/pfc_KnowledgeFetcher.py b/src/experimental/PFC/pfc_KnowledgeFetcher.py deleted file mode 100644 index a1d161a70..000000000 --- a/src/experimental/PFC/pfc_KnowledgeFetcher.py +++ /dev/null @@ -1,91 +0,0 @@ -from typing import List, Tuple -from src.common.logger import get_logger -from src.chat.memory_system.Hippocampus import hippocampus_manager -from src.llm_models.utils_model import LLMRequest -from src.config.config import global_config -from src.chat.message_receive.message import Message -from src.chat.knowledge.knowledge_lib import qa_manager -from src.chat.utils.chat_message_builder import build_readable_messages - -logger = get_logger("knowledge_fetcher") - - -class KnowledgeFetcher: - """知识调取器""" - - def __init__(self, private_name: str): - # TODO: API-Adapter修改标记 - self.llm = LLMRequest( - model=global_config.model.utils, - temperature=global_config.model.utils["temp"], - max_tokens=1000, - request_type="knowledge_fetch", - ) - self.private_name = private_name - - def _lpmm_get_knowledge(self, query: str) -> str: - """获取相关知识 - - Args: - query: 查询内容 - - Returns: - str: 构造好的,带相关度的知识 - """ - - logger.debug(f"[私聊][{self.private_name}]正在从LPMM知识库中获取知识") - try: - # 检查LPMM知识库是否启用 - if qa_manager is None: - logger.debug(f"[私聊][{self.private_name}]LPMM知识库已禁用,跳过知识获取") - return "未找到匹配的知识" - - knowledge_info = qa_manager.get_knowledge(query) - logger.debug(f"[私聊][{self.private_name}]LPMM知识库查询结果: {knowledge_info:150}") - return knowledge_info - except Exception as e: - logger.error(f"[私聊][{self.private_name}]LPMM知识库搜索工具执行失败: {str(e)}") - return "未找到匹配的知识" - - async def fetch(self, query: str, chat_history: List[Message]) -> Tuple[str, str]: - """获取相关知识 - - Args: - query: 查询内容 - chat_history: 聊天历史 - - Returns: - Tuple[str, str]: (获取的知识, 知识来源) - """ - # 构建查询上下文 - chat_history_text = build_readable_messages( - chat_history, - replace_bot_name=True, - merge_messages=False, - timestamp_mode="relative", - read_mark=0.0, - ) - - # 从记忆中获取相关知识 - related_memory = await hippocampus_manager.get_memory_from_text( - text=f"{query}\n{chat_history_text}", - max_memory_num=3, - max_memory_length=2, - max_depth=3, - fast_retrieval=False, - ) - knowledge_text = "" - sources_text = "无记忆匹配" # 默认值 - if related_memory: - sources = [] - for memory in related_memory: - knowledge_text += memory[1] + "\n" - sources.append(f"记忆片段{memory[0]}") - knowledge_text = knowledge_text.strip() - sources_text = ",".join(sources) - - knowledge_text += "\n现在有以下**知识**可供参考:\n " - knowledge_text += self._lpmm_get_knowledge(query) - knowledge_text += "\n请记住这些**知识**,并根据**知识**回答问题。\n" - - return knowledge_text or "未找到相关知识", sources_text or "无记忆匹配" diff --git a/src/experimental/PFC/pfc_manager.py b/src/experimental/PFC/pfc_manager.py deleted file mode 100644 index 174be78b1..000000000 --- a/src/experimental/PFC/pfc_manager.py +++ /dev/null @@ -1,115 +0,0 @@ -import time -from typing import Dict, Optional -from src.common.logger import get_logger -from .conversation import Conversation -import traceback - -logger = get_logger("pfc_manager") - - -class PFCManager: - """PFC对话管理器,负责管理所有对话实例""" - - # 单例模式 - _instance = None - - # 会话实例管理 - _instances: Dict[str, Conversation] = {} - _initializing: Dict[str, bool] = {} - - @classmethod - def get_instance(cls) -> "PFCManager": - """获取管理器单例 - - Returns: - PFCManager: 管理器实例 - """ - if cls._instance is None: - cls._instance = PFCManager() - return cls._instance - - async def get_or_create_conversation(self, stream_id: str, private_name: str) -> Optional[Conversation]: - """获取或创建对话实例 - - Args: - stream_id: 聊天流ID - private_name: 私聊名称 - - Returns: - Optional[Conversation]: 对话实例,创建失败则返回None - """ - # 检查是否已经有实例 - if stream_id in self._initializing and self._initializing[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"[私聊][{private_name}]使用现有会话实例: {stream_id}") - return self._instances[stream_id] - if stream_id in self._instances: - instance = self._instances[stream_id] - if ( - hasattr(instance, "ignore_until_timestamp") - and instance.ignore_until_timestamp - and time.time() < instance.ignore_until_timestamp - ): - logger.debug(f"[私聊][{private_name}]会话实例当前处于忽略状态: {stream_id}") - # 返回 None 阻止交互。或者可以返回实例但标记它被忽略了喵? - # 还是返回 None 吧喵。 - return None - - # 检查 should_continue 状态 - if instance.should_continue: - logger.debug(f"[私聊][{private_name}]使用现有会话实例: {stream_id}") - return instance - # else: 实例存在但不应继续 - try: - # 创建新实例 - logger.info(f"[私聊][{private_name}]创建新的对话实例: {stream_id}") - self._initializing[stream_id] = True - # 创建实例 - conversation_instance = Conversation(stream_id, private_name) - self._instances[stream_id] = conversation_instance - - # 启动实例初始化 - await self._initialize_conversation(conversation_instance) - except Exception as e: - logger.error(f"[私聊][{private_name}]创建会话实例失败: {stream_id}, 错误: {e}") - return None - - return conversation_instance - - async def _initialize_conversation(self, conversation: Conversation): - """初始化会话实例 - - Args: - conversation: 要初始化的会话实例 - """ - stream_id = conversation.stream_id - private_name = conversation.private_name - - try: - logger.info(f"[私聊][{private_name}]开始初始化会话实例: {stream_id}") - # 启动初始化流程 - await conversation._initialize() - - # 标记初始化完成 - self._initializing[stream_id] = False - - logger.info(f"[私聊][{private_name}]会话实例 {stream_id} 初始化完成") - - except Exception as e: - 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]: - """获取已存在的会话实例 - - Args: - stream_id: 聊天流ID - - Returns: - Optional[Conversation]: 会话实例,不存在则返回None - """ - return self._instances.get(stream_id) diff --git a/src/experimental/PFC/pfc_types.py b/src/experimental/PFC/pfc_types.py deleted file mode 100644 index 0ea5eda64..000000000 --- a/src/experimental/PFC/pfc_types.py +++ /dev/null @@ -1,23 +0,0 @@ -from enum import Enum -from typing import Literal - - -class ConversationState(Enum): - """对话状态""" - - INIT = "初始化" - RETHINKING = "重新思考" - ANALYZING = "分析历史" - PLANNING = "规划目标" - GENERATING = "生成回复" - CHECKING = "检查回复" - SENDING = "发送消息" - FETCHING = "获取知识" - WAITING = "等待" - LISTENING = "倾听" - ENDED = "结束" - JUDGING = "判断" - IGNORED = "屏蔽" - - -ActionType = Literal["direct_reply", "fetch_knowledge", "wait"] diff --git a/src/experimental/PFC/pfc_utils.py b/src/experimental/PFC/pfc_utils.py deleted file mode 100644 index b9e93ee51..000000000 --- a/src/experimental/PFC/pfc_utils.py +++ /dev/null @@ -1,127 +0,0 @@ -import json -import re -from typing import Dict, Any, Optional, Tuple, List, Union -from src.common.logger import get_logger - -logger = get_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, - allow_array: bool = True, -) -> Tuple[bool, Union[Dict[str, Any], List[Dict[str, Any]]]]: - """从文本中提取JSON内容并获取指定字段 - - Args: - content: 包含JSON的文本 - private_name: 私聊名称 - *items: 要提取的字段名 - default_values: 字段的默认值,格式为 {字段名: 默认值} - required_types: 字段的必需类型,格式为 {字段名: 类型} - allow_array: 是否允许解析JSON数组 - - Returns: - Tuple[bool, Union[Dict[str, Any], List[Dict[str, Any]]]]: (是否成功, 提取的字段字典或字典列表) - """ - content = content.strip() - result = {} - - # 设置默认值 - if default_values: - result.update(default_values) - - # 首先尝试解析为JSON数组 - if allow_array: - try: - # 尝试找到文本中的JSON数组 - array_pattern = r"\[[\s\S]*\]" - array_match = re.search(array_pattern, content) - if array_match: - array_content = array_match.group() - json_array = json.loads(array_content) - - # 确认是数组类型 - if isinstance(json_array, list): - # 验证数组中的每个项目是否包含所有必需字段 - valid_items = [] - for item in json_array: - if not isinstance(item, dict): - continue - - # 检查是否有所有必需字段 - if all(field in item for field in items): - # 验证字段类型 - if required_types: - type_valid = True - for field, expected_type in required_types.items(): - if field in item and not isinstance(item[field], expected_type): - type_valid = False - break - - if not type_valid: - continue - - # 验证字符串字段不为空 - string_valid = True - for field in items: - if isinstance(item[field], str) and not item[field].strip(): - string_valid = False - break - - if not string_valid: - continue - - valid_items.append(item) - - if valid_items: - return True, valid_items - except json.JSONDecodeError: - logger.debug(f"[私聊][{private_name}]JSON数组解析失败,尝试解析单个JSON对象") - except Exception as e: - logger.debug(f"[私聊][{private_name}]尝试解析JSON数组时出错: {str(e)}") - - # 尝试解析JSON对象 - try: - json_data = json.loads(content) - except json.JSONDecodeError: - # 如果直接解析失败,尝试查找和提取JSON部分 - json_pattern = r"\{[^{}]*\}" - json_match = re.search(json_pattern, content) - if json_match: - try: - json_data = json.loads(json_match.group()) - except json.JSONDecodeError: - logger.error(f"[私聊][{private_name}]提取的JSON内容解析失败") - return False, result - else: - logger.error(f"[私聊][{private_name}]无法在返回内容中找到有效的JSON") - return False, result - - # 提取字段 - for item in items: - if item in json_data: - result[item] = json_data[item] - - # 验证必需字段 - if not all(item in result for item in items): - 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"[私聊][{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"[私聊][{private_name}]{field} 不能为空") - return False, result - - return True, result diff --git a/src/experimental/PFC/reply_checker.py b/src/experimental/PFC/reply_checker.py deleted file mode 100644 index 78319d00f..000000000 --- a/src/experimental/PFC/reply_checker.py +++ /dev/null @@ -1,183 +0,0 @@ -import json -from typing import Tuple, List, Dict, Any -from src.common.logger import get_logger -from src.llm_models.utils_model import LLMRequest -from src.config.config import global_config -from src.experimental.PFC.chat_observer import ChatObserver -from maim_message import UserInfo - -logger = get_logger("reply_checker") - - -class ReplyChecker: - """回复检查器""" - - 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.private_name = private_name - self.chat_observer = ChatObserver.get_instance(stream_id, private_name) - self.max_retries = 3 # 最大重试次数 - - async def check( - self, reply: str, goal: str, chat_history: List[Dict[str, Any]], chat_history_text: str, retry_count: int = 0 - ) -> Tuple[bool, str, bool]: - """检查生成的回复是否合适 - - Args: - reply: 生成的回复 - goal: 对话目标 - chat_history: 对话历史记录 - chat_history_text: 对话历史记录文本 - retry_count: 当前重试次数 - - Returns: - Tuple[bool, str, bool]: (是否合适, 原因, 是否需要重新规划) - """ - # 不再从 observer 获取,直接使用传入的 chat_history - # messages = self.chat_observer.get_cached_messages(limit=20) - try: - # 筛选出最近由 Bot 自己发送的消息 - bot_messages = [] - for msg in reversed(chat_history): - user_info = UserInfo.from_dict(msg.get("user_info", {})) - if str(user_info.user_id) == str(global_config.bot.qq_account): # 确保比较的是字符串 - bot_messages.append(msg.get("processed_plain_text", "")) - if len(bot_messages) >= 2: # 只和最近的两条比较 - break - # 进行比较 - if bot_messages: - # 可以用简单比较,或者更复杂的相似度库 (如 difflib) - # 简单比较:是否完全相同 - if reply == bot_messages[0]: # 和最近一条完全一样 - logger.warning( - f"[私聊][{self.private_name}]ReplyChecker 检测到回复与上一条 Bot 消息完全相同: '{reply}'" - ) - return ( - False, - "被逻辑检查拒绝:回复内容与你上一条发言完全相同,可以选择深入话题或寻找其它话题或等待", - True, - ) # 不合适,需要返回至决策层 - # 2. 相似度检查 (如果精确匹配未通过) - import difflib # 导入 difflib 库 - - # 计算编辑距离相似度,ratio() 返回 0 到 1 之间的浮点数 - similarity_ratio = difflib.SequenceMatcher(None, reply, bot_messages[0]).ratio() - logger.debug(f"[私聊][{self.private_name}]ReplyChecker - 相似度: {similarity_ratio:.2f}") - - # 设置一个相似度阈值 - similarity_threshold = 0.9 - if similarity_ratio > similarity_threshold: - logger.warning( - f"[私聊][{self.private_name}]ReplyChecker 检测到回复与上一条 Bot 消息高度相似 (相似度 {similarity_ratio:.2f}): '{reply}'" - ) - return ( - False, - f"被逻辑检查拒绝:回复内容与你上一条发言高度相似 (相似度 {similarity_ratio:.2f}),可以选择深入话题或寻找其它话题或等待。", - True, - ) - - except Exception as e: - import traceback - - logger.error(f"[私聊][{self.private_name}]检查回复时出错: 类型={type(e)}, 值={e}") - logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") # 打印详细的回溯信息 - - prompt = f"""你是一个聊天逻辑检查器,请检查以下回复或消息是否合适: - -当前对话目标:{goal} -最新的对话记录: -{chat_history_text} - -待检查的消息: -{reply} - -请结合聊天记录检查以下几点: -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 - -输出格式示例: -{{ - "suitable": true, - "reason": "回复符合要求,虽然有可能略微偏离目标,但是整体内容流畅得体", - "need_replan": false -}} - -注意:请严格按照JSON格式输出,不要包含任何其他内容。""" - - try: - content, _ = await self.llm.generate_response_async(prompt) - logger.debug(f"[私聊][{self.private_name}]检查回复的原始返回: {content}") - - # 清理内容,尝试提取JSON部分 - content = content.strip() - try: - # 尝试直接解析 - result = json.loads(content) - except json.JSONDecodeError: - # 如果直接解析失败,尝试查找和提取JSON部分 - import re - - json_pattern = r"\{[^{}]*\}" - json_match = re.search(json_pattern, content) - if json_match: - try: - result = json.loads(json_match.group()) - except json.JSONDecodeError: - # 如果JSON解析失败,尝试从文本中提取结果 - is_suitable = "不合适" not in content.lower() and "违规" not in content.lower() - reason = content[:100] if content else "无法解析响应" - need_replan = "重新规划" in content.lower() or "目标不适合" in content.lower() - return is_suitable, reason, need_replan - else: - # 如果找不到JSON,从文本中判断 - is_suitable = "不合适" not in content.lower() and "违规" not in content.lower() - reason = content[:100] if content else "无法解析响应" - need_replan = "重新规划" in content.lower() or "目标不适合" in content.lower() - return is_suitable, reason, need_replan - - # 验证JSON字段 - suitable = result.get("suitable", None) - reason = result.get("reason", "未提供原因") - need_replan = result.get("need_replan", False) - - # 如果suitable字段是字符串,转换为布尔值 - if isinstance(suitable, str): - suitable = suitable.lower() == "true" - - # 如果suitable字段不存在或不是布尔值,从reason中判断 - if suitable is None: - suitable = "不合适" not in reason.lower() and "违规" not in reason.lower() - - # 如果不合适且未达到最大重试次数,返回需要重试 - if not suitable and retry_count < self.max_retries: - return False, reason, False - - # 如果不合适且已达到最大重试次数,返回需要重新规划 - if not suitable and retry_count >= self.max_retries: - return False, f"多次重试后仍不合适: {reason}", True - - return suitable, reason, need_replan - - except Exception as e: - logger.error(f"[私聊][{self.private_name}]检查回复时出错: {e}") - # 如果出错且已达到最大重试次数,建议重新规划 - if retry_count >= self.max_retries: - return False, "多次检查失败,建议重新规划", True - return False, f"检查过程出错,建议重试: {str(e)}", False diff --git a/src/experimental/PFC/reply_generator.py b/src/experimental/PFC/reply_generator.py deleted file mode 100644 index 530eba6c7..000000000 --- a/src/experimental/PFC/reply_generator.py +++ /dev/null @@ -1,227 +0,0 @@ -from typing import Tuple, List, Dict, Any -from src.common.logger import get_logger -from src.llm_models.utils_model import LLMRequest -from src.config.config import global_config -from src.experimental.PFC.chat_observer import ChatObserver -from src.experimental.PFC.reply_checker import ReplyChecker -from src.individuality.individuality import get_individuality -from .observation_info import ObservationInfo -from .conversation_info import ConversationInfo -from src.chat.utils.chat_message_builder import build_readable_messages - -logger = get_logger("reply_generator") - -# --- 定义 Prompt 模板 --- - -# Prompt for direct_reply (首次回复) -PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下信息生成一条回复: - -当前对话目标:{goals_str} - -{knowledge_info_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} - -{knowledge_info_str} - -最近的聊天记录: -{chat_history_text} - - -请根据上述信息,结合聊天记录,继续发一条新消息(例如对之前消息的补充,深入话题,或追问等等)。该消息应该: -1. 符合对话目标,以"你"的角度发言(不要自己与自己对话!) -2. 符合你的性格特征和身份细节 -3. 通俗易懂,自然流畅,像正常聊天一样,简短(通常20字以内,除非特殊情况) -4. 可以适当利用相关知识,但不要生硬引用 -5. 跟之前你发的消息自然的衔接,逻辑合理,且没有重复表达同质内容或部分重叠内容 - -请注意把握聊天内容,不用太有条理,可以有个性。请分清"你"和对方说的话,不要把"你"说的话当做对方说的话,这是你自己说的话。 -这条消息可以自然随意自然一些,就像真人一样,注意把握聊天内容,整体风格可以平和、简短,不要刻意突出自身学科背景,不要说你说过的话,可以简短,多简短都可以,但是避免冗长。 -请你注意不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只输出消息内容。 -不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 )。 - -请直接输出回复内容,不需要任何额外格式。""" - -# Prompt for say_goodbye (告别语生成) -PROMPT_FAREWELL = """{persona_text}。你在参与一场 QQ 私聊,现在对话似乎已经结束,你决定再发一条最后的消息来圆满结束。 - -最近的聊天记录: -{chat_history_text} - -请根据上述信息,结合聊天记录,构思一条**简短、自然、符合你人设**的最后的消息。 -这条消息应该: -1. 从你自己的角度发言。 -2. 符合你的性格特征和身份细节。 -3. 通俗易懂,自然流畅,通常很简短。 -4. 自然地为这场对话画上句号,避免开启新话题或显得冗长、刻意。 - -请像真人一样随意自然,**简洁是关键**。 -不要输出多余内容(包括前后缀、冒号、引号、括号、表情包、at或@等)。 - -请直接输出最终的告别消息内容,不需要任何额外格式。""" - - -class ReplyGenerator: - """回复生成器""" - - 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"], - request_type="reply_generation", - ) - self.personality_info = get_individuality().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) - self.reply_checker = ReplyChecker(stream_id, private_name) - - # 修改 generate 方法签名,增加 action_type 参数 - async def generate( - self, observation_info: ObservationInfo, conversation_info: ConversationInfo, action_type: str - ) -> str: - """生成回复 - - Args: - observation_info: 观察信息 - conversation_info: 对话信息 - action_type: 当前执行的动作类型 ('direct_reply' 或 'send_new_message') - - Returns: - str: 生成的回复 - """ - # 构建提示词 - logger.debug( - f"[私聊][{self.private_name}]开始生成回复 (动作类型: {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, 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" # 简化无目标情况 - - # --- 新增:构建知识信息字符串 --- - knowledge_info_str = "【供参考的相关知识和记忆】\n" # 稍微改下标题,表明是供参考 - try: - # 检查 conversation_info 是否有 knowledge_list 并且不为空 - if hasattr(conversation_info, "knowledge_list") and conversation_info.knowledge_list: - # 最多只显示最近的 5 条知识 - recent_knowledge = conversation_info.knowledge_list[-5:] - for i, knowledge_item in enumerate(recent_knowledge): - if isinstance(knowledge_item, dict): - query = knowledge_item.get("query", "未知查询") - knowledge = knowledge_item.get("knowledge", "无知识内容") - source = knowledge_item.get("source", "未知来源") - # 只取知识内容的前 2000 个字 - knowledge_snippet = knowledge[:2000] + "..." if len(knowledge) > 2000 else knowledge - knowledge_info_str += ( - f"{i + 1}. 关于 '{query}' (来源: {source}): {knowledge_snippet}\n" # 格式微调,更简洁 - ) - else: - knowledge_info_str += f"{i + 1}. 发现一条格式不正确的知识记录。\n" - - if not recent_knowledge: - knowledge_info_str += "- 暂无。\n" # 更简洁的提示 - - else: - knowledge_info_str += "- 暂无。\n" - except AttributeError: - logger.warning(f"[私聊][{self.private_name}]ConversationInfo 对象可能缺少 knowledge_list 属性。") - knowledge_info_str += "- 获取知识列表时出错。\n" - except Exception as e: - logger.error(f"[私聊][{self.private_name}]构建知识信息字符串时出错: {e}") - knowledge_info_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 = 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 = "还没有聊天记录。" - - # 构建 Persona 文本 (persona_text) - persona_text = f"你的名字是{self.name},{self.personality_info}。" - - # --- 选择 Prompt --- - if action_type == "send_new_message": - prompt_template = PROMPT_SEND_NEW_MESSAGE - logger.info(f"[私聊][{self.private_name}]使用 PROMPT_SEND_NEW_MESSAGE (追问生成)") - elif action_type == "say_goodbye": # 处理告别动作 - prompt_template = PROMPT_FAREWELL - logger.info(f"[私聊][{self.private_name}]使用 PROMPT_FAREWELL (告别语生成)") - else: # 默认使用 direct_reply 的 prompt (包括 'direct_reply' 或其他未明确处理的类型) - prompt_template = PROMPT_DIRECT_REPLY - logger.info(f"[私聊][{self.private_name}]使用 PROMPT_DIRECT_REPLY (首次/非连续回复生成)") - - # --- 格式化最终的 Prompt --- - prompt = prompt_template.format( - persona_text=persona_text, - goals_str=goals_str, - chat_history_text=chat_history_text, - knowledge_info_str=knowledge_info_str, - ) - - # --- 调用 LLM 生成 --- - logger.debug(f"[私聊][{self.private_name}]发送到LLM的生成提示词:\n------\n{prompt}\n------") - try: - content, _ = await self.llm.generate_response_async(prompt) - logger.debug(f"[私聊][{self.private_name}]生成的回复: {content}") - # 移除旧的检查新消息逻辑,这应该由 conversation 控制流处理 - return content - - except Exception as e: - logger.error(f"[私聊][{self.private_name}]生成回复时出错: {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]: - """检查回复是否合适 - (此方法逻辑保持不变) - """ - return await self.reply_checker.check(reply, goal, chat_history, chat_history_str, retry_count) diff --git a/src/experimental/PFC/waiter.py b/src/experimental/PFC/waiter.py deleted file mode 100644 index 530a48a4e..000000000 --- a/src/experimental/PFC/waiter.py +++ /dev/null @@ -1,79 +0,0 @@ -from src.common.logger import get_logger -from .chat_observer import ChatObserver -from .conversation_info import ConversationInfo - -# from src.individuality.individuality get_individuality,Individuality # 不再需要 -from src.config.config import global_config -import time -import asyncio - -logger = get_logger("waiter") - -# --- 在这里设定你想要的超时时间(秒) --- -# 例如: 120 秒 = 2 分钟 -DESIRED_TIMEOUT_SECONDS = 300 - - -class Waiter: - """等待处理类""" - - def __init__(self, stream_id: str, private_name: str): - 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 # 不再需要累加计时 - - async def wait(self, conversation_info: ConversationInfo) -> bool: - """等待用户新消息或超时""" - wait_start_time = time.time() - logger.info(f"[私聊][{self.private_name}]进入常规等待状态 (超时: {DESIRED_TIMEOUT_SECONDS} 秒)...") - - while True: - # 检查是否有新消息 - if self.chat_observer.new_message_after(wait_start_time): - 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"[私聊][{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"[私聊][{self.private_name}]添加目标: {wait_goal}") - return True # 返回 True 表示超时 - - await asyncio.sleep(5) # 每 5 秒检查一次 - logger.debug( - f"[私聊][{self.private_name}]等待中..." - ) # 可以考虑把这个频繁日志注释掉,只在超时或收到消息时输出 - - async def wait_listening(self, conversation_info: ConversationInfo) -> bool: - """倾听用户发言或超时""" - wait_start_time = time.time() - logger.info(f"[私聊][{self.private_name}]进入倾听等待状态 (超时: {DESIRED_TIMEOUT_SECONDS} 秒)...") - - while True: - # 检查是否有新消息 - if self.chat_observer.new_message_after(wait_start_time): - 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"[私聊][{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"[私聊][{self.private_name}]添加目标: {wait_goal}") - return True # 返回 True 表示超时 - - await asyncio.sleep(5) # 每 5 秒检查一次 - logger.debug(f"[私聊][{self.private_name}]倾听等待中...") # 同上,可以考虑注释掉 diff --git a/src/experimental/only_message_process.py b/src/experimental/only_message_process.py deleted file mode 100644 index e5ca6b82d..000000000 --- a/src/experimental/only_message_process.py +++ /dev/null @@ -1,70 +0,0 @@ -from src.common.logger import get_logger -from src.chat.message_receive.message import MessageRecv -from src.chat.message_receive.storage import MessageStorage -from src.config.config import global_config -from src.chat.message_receive.chat_stream import ChatStream - -from maim_message import UserInfo -from datetime import datetime -import re - -logger = get_logger("pfc") - - -class MessageProcessor: - """消息处理器,负责处理接收到的消息并存储""" - - def __init__(self): - self.storage = MessageStorage() - - @staticmethod - def _check_ban_words(text: str, chat: ChatStream, userinfo: UserInfo) -> bool: - """检查消息中是否包含过滤词""" - for word in global_config.message_receive.ban_words: - if word in text: - logger.info( - f"[{chat.group_info.group_name if chat.group_info else '私聊'}]{userinfo.user_nickname}:{text}" - ) - logger.info(f"[过滤词识别]消息中含有{word},filtered") - return True - return False - - @staticmethod - def _check_ban_regex(text: str, chat: ChatStream, userinfo: UserInfo) -> bool: - """检查消息是否匹配过滤正则表达式""" - for pattern in global_config.message_receive.ban_msgs_regex: - if re.search(pattern, text): - chat_name = chat.group_info.group_name if chat.group_info else "私聊" - logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}") - logger.info(f"[正则表达式过滤]消息匹配到{pattern},filtered") - return True - return False - - async def process_message(self, message: MessageRecv) -> None: - """处理消息并存储 - - Args: - message: 消息对象 - """ - userinfo = message.message_info.user_info - chat = message.chat_stream - - # 处理消息 - await message.process() - - # 过滤词/正则表达式过滤 - if self._check_ban_words(message.processed_plain_text, chat, userinfo) or self._check_ban_regex( - message.raw_message, chat, userinfo - ): - return - - # 存储消息 - await self.storage.store_message(message, chat) - - # 打印消息信息 - mes_name = chat.group_info.group_name if chat.group_info else "私聊" - # 将时间戳转换为datetime对象 - current_time = datetime.fromtimestamp(message.message_info.time).strftime("%H:%M:%S") - logger.info( - f"[{current_time}][{mes_name}]{message.message_info.user_info.user_nickname}: {message.processed_plain_text}" - ) diff --git a/src/mais4u/mais4u_chat/s4u_mood_manager.py b/src/mais4u/mais4u_chat/s4u_mood_manager.py index c78224f16..a42c19751 100644 --- a/src/mais4u/mais4u_chat/s4u_mood_manager.py +++ b/src/mais4u/mais4u_chat/s4u_mood_manager.py @@ -219,7 +219,7 @@ class FacialExpression: # 获取最强情绪 dominant_emotion = max(emotions, key=emotions.get) - dominant_value = emotions[dominant_emotion] + _dominant_value = emotions[dominant_emotion] # 根据情绪强度和类型选择表情 if dominant_emotion == "joy": @@ -408,7 +408,7 @@ class ChatMood: self.mood_state = text_mood_response if numerical_mood_response: - old_mood_values = self.mood_values.copy() + _old_mood_values = self.mood_values.copy() self.mood_values = numerical_mood_response # 发送面部表情 @@ -488,7 +488,7 @@ class ChatMood: self.mood_state = text_mood_response if numerical_mood_response: - old_mood_values = self.mood_values.copy() + _old_mood_values = self.mood_values.copy() self.mood_values = numerical_mood_response # 发送面部表情 diff --git a/src/mood/mood_manager.py b/src/mood/mood_manager.py index a6f3baa59..acd22fd5b 100644 --- a/src/mood/mood_manager.py +++ b/src/mood/mood_manager.py @@ -9,7 +9,7 @@ from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_by_timestamp_with_chat_inclusive from src.llm_models.utils_model import LLMRequest from src.manager.async_task_manager import AsyncTask, async_task_manager -from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager +from src.chat.message_receive.chat_stream import get_chat_manager logger = get_logger("mood")