diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 270ec451..96b1630c 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -20,7 +20,9 @@ logger = get_logger("pfc_action_planner") # --- 定义 Prompt 模板 --- # Prompt(1): 首次回复或非连续回复时的决策 Prompt -PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以回复,可以倾听,可以调取知识,甚至可以屏蔽对方: +PROMPT_INITIAL_REPLY = """ +当前时间:{current_time_str} +{persona_text}。现在你在参与一场QQ私聊,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以回复,可以倾听,可以调取知识,甚至可以屏蔽对方: 【当前对话目标】 {goals_str} @@ -54,7 +56,9 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在 注意:请严格按照JSON格式输出,不要包含任何其他内容。""" # Prompt(2): 上一次成功回复后,决定继续发言时的决策 Prompt -PROMPT_FOLLOW_UP = """{persona_text}。现在你在参与一场QQ私聊,刚刚你已经回复了对方,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以继续发送新消息,可以等待,可以倾听,可以调取知识,甚至可以屏蔽对方: +PROMPT_FOLLOW_UP = """ +当前时间:{current_time_str} +{persona_text}。现在你在参与一场QQ私聊,刚刚你已经回复了对方,请根据以下【所有信息】审慎且灵活的决策下一步行动,可以继续发送新消息,可以等待,可以倾听,可以调取知识,甚至可以屏蔽对方: 【当前对话目标】 {goals_str} @@ -88,7 +92,9 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在 注意:请严格按照JSON格式输出,不要包含任何其他内容。""" # 新增:Prompt(3): 决定是否在结束对话前发送告别语 -PROMPT_END_DECISION = """{persona_text}。刚刚你决定结束一场 QQ 私聊。 +PROMPT_END_DECISION = """ +当前时间:{current_time_str} +{persona_text}。刚刚你决定结束一场 QQ 私聊。 【你们之前的聊天记录】 {chat_history_text} @@ -187,6 +193,10 @@ class ActionPlanner: log_msg = "使用 PROMPT_INITIAL_REPLY (首次/非连续回复决策)" logger.debug(f"[私聊][{self.private_name}] {log_msg}") + current_time_value = "获取时间失败" # 默认值 + if observation_info and hasattr(observation_info, 'current_time_str') and observation_info.current_time_str: + current_time_value = observation_info.current_time_str + prompt = prompt_template.format( persona_text=persona_text, goals_str=goals_str if goals_str.strip() else "- 目前没有明确对话目标,请考虑设定一个。", @@ -197,6 +207,7 @@ class ActionPlanner: chat_history_text=chat_history_text if chat_history_text.strip() else "还没有聊天记录。", retrieved_memory_str=retrieved_memory_str if retrieved_memory_str else "无相关记忆。", retrieved_knowledge_str=retrieved_knowledge_str if retrieved_knowledge_str else "无相关知识。", + current_time_str=current_time_value # 新增:传入当前时间字符串 ) logger.debug(f"[私聊][{self.private_name}] 发送到LLM的最终提示词:\n------\n{prompt}\n------") except KeyError as fmt_key_err: @@ -235,8 +246,11 @@ class ActionPlanner: if initial_action == "end_conversation": try: + time_str_for_end_decision = "获取时间失败" + if observation_info and hasattr(observation_info, 'current_time_str') and observation_info.current_time_str: + time_str_for_end_decision = observation_info.current_time_str final_action, final_reason = await self._handle_end_conversation_decision( - persona_text, chat_history_text, initial_reason + persona_text, chat_history_text, initial_reason,time_str_for_end_decision ) except Exception as end_dec_err: logger.error(f"[私聊][{self.private_name}] 处理结束对话决策时出错: {end_dec_err}") @@ -446,11 +460,11 @@ class ActionPlanner: # --- Helper method for handling end_conversation decision --- async def _handle_end_conversation_decision( - self, persona_text: str, chat_history_text: str, initial_reason: str + self, persona_text: str, chat_history_text: str, initial_reason: str, current_time_str: str ) -> Tuple[str, str]: """处理结束对话前的告别决策""" logger.info(f"[私聊][{self.private_name}] 初步规划结束对话,进入告别决策...") - end_decision_prompt = PROMPT_END_DECISION.format(persona_text=persona_text, chat_history_text=chat_history_text) + end_decision_prompt = PROMPT_END_DECISION.format(persona_text=persona_text, chat_history_text=chat_history_text,current_time_str=current_time_str) logger.debug(f"[私聊][{self.private_name}] 发送到LLM的结束决策提示词:\n------\n{end_decision_prompt}\n------") llm_start_time = time.time() end_content, _ = await self.llm.generate_response_async(end_decision_prompt) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index f3abea51..d4ba017c 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -3,6 +3,7 @@ import asyncio import datetime import traceback from typing import Dict, Any, Optional, Set, List +from dateutil import tz # 导入日志记录器 from src.common.logger_manager import get_logger @@ -44,6 +45,19 @@ install(extra_lines=3) # 获取当前模块的日志记录器 logger = get_logger("pfc_conversation") +try: + from ...config.config import global_config +except ImportError: + logger.error("无法在 conversation.py 中导入 global_config,时区可能不准确!") + global_config = None + TIME_ZONE = tz.tzlocal() # 使用本地时区作为后备 +else: + # 确保 global_config.TIME_ZONE 存在且有效,否则使用默认值 + configured_tz = getattr(global_config, 'TIME_ZONE', 'Asia/Shanghai') # 使用 getattr 安全访问 + TIME_ZONE = tz.gettz(configured_tz) + if TIME_ZONE is None: # 如果 gettz 返回 None,说明时区字符串无效 + logger.error(f"配置的时区 '{configured_tz}' 无效,将使用默认时区 'Asia/Shanghai'") + TIME_ZONE = tz.gettz('Asia/Shanghai') class Conversation: """ @@ -347,6 +361,30 @@ class Conversation: while self.should_continue: loop_iter_start_time = time.time() # 记录本次循环开始时间 logger.debug(f"[私聊][{self.private_name}] 开始新一轮循环迭代 ({loop_iter_start_time:.2f})") + try: + # 重新获取 TIME_ZONE 以防在 __init__ 中导入失败 + if 'TIME_ZONE' not in locals() or TIME_ZONE is None: + from dateutil import tz + try: + from ...config.config import global_config + except ImportError: + global_config = None + TIME_ZONE = tz.tzlocal() + else: + configured_tz = getattr(global_config, 'TIME_ZONE', 'Asia/Shanghai') + TIME_ZONE = tz.gettz(configured_tz) + if TIME_ZONE is None: TIME_ZONE = tz.gettz('Asia/Shanghai') + + current_time = datetime.datetime.now(TIME_ZONE) + if self.observation_info: # 确保 observation_info 存在 + time_str = current_time.strftime("%Y-%m-%d %H:%M:%S %Z%z") # 包含时区信息的格式 + self.observation_info.current_time_str = time_str + logger.debug(f"[私聊][{self.private_name}] 更新 ObservationInfo 当前时间: {time_str}") + else: + logger.warning(f"[私聊][{self.private_name}] ObservationInfo 未初始化,无法更新当前时间。") + except Exception as time_update_err: + logger.error(f"[私聊][{self.private_name}] 更新 ObservationInfo 当前时间时出错: {time_update_err}") + # --- 更新时间代码结束 --- # --- 处理忽略状态 --- if self.ignore_until_timestamp and loop_iter_start_time < self.ignore_until_timestamp: @@ -389,7 +427,9 @@ class Conversation: logger.debug(f"[私聊][{self.private_name}] 调用 ActionPlanner.plan...") # 传入当前观察信息、对话信息和上次成功回复的动作类型 action, reason = await self.action_planner.plan( - self.observation_info, self.conversation_info, self.conversation_info.last_successful_reply_action + self.observation_info, + self.conversation_info, + self.conversation_info.last_successful_reply_action ) planning_duration = time.time() - planning_start_time logger.debug( @@ -663,6 +703,12 @@ class Conversation: # retry_count for checker starts from 0 current_retry_for_checker = reply_attempt_count - 1 + + current_time_value_for_check = "获取时间失败" + if observation_info and hasattr(observation_info, 'current_time_str') and observation_info.current_time_str: + current_time_value_for_check = observation_info.current_time_str + + logger.debug(f"{log_prefix} 调用 ReplyChecker 检查...") # 调用 ReplyChecker 的 check 方法 is_suitable, check_reason, need_replan_from_checker = await self.reply_checker.check( @@ -670,6 +716,7 @@ class Conversation: goal=current_goal_str, chat_history=chat_history_for_check, # 传递列表形式的历史记录 chat_history_text=chat_history_text_for_check, # 传递文本形式的历史记录 + current_time_str=current_time_value_for_check, # 新增:传递时间字符串 retry_count=current_retry_for_checker, ) logger.info( diff --git a/src/plugins/PFC/observation_info.py b/src/plugins/PFC/observation_info.py index 73ff4103..102730b4 100644 --- a/src/plugins/PFC/observation_info.py +++ b/src/plugins/PFC/observation_info.py @@ -1,7 +1,8 @@ +import datetime import time import traceback +from dateutil import tz from typing import List, Optional, Dict, Any, Set - from maim_message import UserInfo from src.common.logger import get_module_logger from src.plugins.utils.chat_message_builder import build_readable_messages @@ -12,6 +13,17 @@ from .chat_states import NotificationHandler, NotificationType, Notification logger = get_module_logger("observation_info") +try: + from ...config.config import global_config +except ImportError: + # 如果路径不对,可能需要调整 '...' 的数量 + # 或者在后面实际使用 TIME_ZONE 的地方导入 + logger.warning("无法在 observation_info.py 中直接导入 global_config,时区将在使用时获取") + global_config = None # 设置为 None,后面检查 + TIME_ZONE = tz.tzlocal() # 备选:使用本地时区 +else: + TIME_ZONE = tz.gettz(global_config.TIME_ZONE if global_config else 'Asia/Shanghai') # 使用配置的时区,提供默认值 + class ObservationInfoHandler(NotificationHandler): """ObservationInfo的通知处理器""" @@ -138,6 +150,9 @@ class ObservationInfo: # 其他状态 self.is_typing: bool = False # 是否正在输入 (未来可能用到) self.changed: bool = False # 状态是否有变化 (用于优化) + + # 用于存储格式化的当前时间 + self.current_time_str: Optional[str] = None # 关联对象 self.chat_observer: Optional[ChatObserver] = None diff --git a/src/plugins/PFC/reply_checker.py b/src/plugins/PFC/reply_checker.py index 35e9af50..3e153a8b 100644 --- a/src/plugins/PFC/reply_checker.py +++ b/src/plugins/PFC/reply_checker.py @@ -22,7 +22,7 @@ class ReplyChecker: 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 + self, reply: str, goal: str, chat_history: List[Dict[str, Any]], chat_history_text: str,current_time_str: str, retry_count: int = 0 ) -> Tuple[bool, str, bool]: """检查生成的回复是否合适 @@ -85,7 +85,9 @@ class ReplyChecker: logger.error(f"[私聊][{self.private_name}]检查回复时出错: 类型={type(e)}, 值={e}") logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") # 打印详细的回溯信息 - prompt = f"""你是一个聊天逻辑检查器,请检查以下回复或消息是否合适: + prompt_template = """ +当前时间:{current_time_str} +你是一个聊天逻辑检查器,请检查以下回复或消息是否合适: 当前对话目标:{goal} 最新的对话记录: @@ -121,6 +123,16 @@ class ReplyChecker: 注意:请严格按照JSON格式输出,不要包含任何其他内容。""" + prompt = prompt_template.format( + current_time_str=current_time_str, # 使用传入的参数 + goal=goal, + chat_history_text=chat_history_text, + reply=reply + ) + + # 调用 LLM + content, _ = await self.llm.generate_response_async(prompt) + try: content, _ = await self.llm.generate_response_async(prompt) logger.debug(f"[私聊][{self.private_name}]检查回复的原始返回: {content}") diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index 892e881b..b93171ea 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -21,7 +21,9 @@ logger = get_logger("reply_generator") # --- 定义 Prompt 模板 --- # Prompt for direct_reply (首次回复) -PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下信息生成一条回复: +PROMPT_DIRECT_REPLY = """ +当前时间:{current_time_str} +{persona_text}。现在你在参与一场QQ私聊,请根据以下信息生成一条回复: 当前对话目标:{goals_str} @@ -52,7 +54,9 @@ PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请 请直接输出回复内容,不需要任何额外格式。""" # Prompt for send_new_message (追问/补充) -PROMPT_SEND_NEW_MESSAGE = """{persona_text}。现在你在参与一场QQ私聊,**刚刚你已经发送了一条或多条消息**,现在请根据以下信息再发一条新消息: +PROMPT_SEND_NEW_MESSAGE = """ +当前时间:{current_time_str} +{persona_text}。现在你在参与一场QQ私聊,**刚刚你已经发送了一条或多条消息**,现在请根据以下信息再发一条新消息: 当前对话目标:{goals_str} @@ -82,7 +86,9 @@ PROMPT_SEND_NEW_MESSAGE = """{persona_text}。现在你在参与一场QQ私聊 请直接输出回复内容,不需要任何额外格式。""" # Prompt for say_goodbye (告别语生成) -PROMPT_FAREWELL = """{persona_text}。你在参与一场 QQ 私聊,现在对话似乎已经结束,你决定再发一条最后的消息来圆满结束。 +PROMPT_FAREWELL = """ +当前时间:{current_time_str} +{persona_text}。你在参与一场 QQ 私聊,现在对话似乎已经结束,你决定再发一条最后的消息来圆满结束。 最近的聊天记录: {chat_history_text} @@ -215,14 +221,26 @@ class ReplyGenerator: # --- 格式化最终的 Prompt --- try: # <--- 增加 try-except 块处理可能的 format 错误 - prompt = prompt_template.format( - persona_text=persona_text, - goals_str=goals_str, - chat_history_text=chat_history_text, - retrieved_memory_str=retrieved_memory_str if retrieved_memory_str else "无相关记忆。", - retrieved_knowledge_str=retrieved_knowledge_str if retrieved_knowledge_str else "无相关知识。", - last_rejection_info=last_rejection_info_str, # <--- 新增传递上次拒绝原因 - ) + current_time_value = "获取时间失败" # 默认值 + if observation_info and hasattr(observation_info, 'current_time_str') and observation_info.current_time_str: + current_time_value = observation_info.current_time_str + if action_type == "say_goodbye": + prompt = prompt_template.format( + persona_text=persona_text, + chat_history_text=chat_history_text, + current_time_str=current_time_value # 添加时间 + ) + + else: + prompt = prompt_template.format( + persona_text=persona_text, + goals_str=goals_str, + chat_history_text=chat_history_text, + retrieved_memory_str=retrieved_memory_str if retrieved_memory_str else "无相关记忆。", + retrieved_knowledge_str=retrieved_knowledge_str if retrieved_knowledge_str else "无相关知识。", + last_rejection_info=last_rejection_info_str, # <--- 新增传递上次拒绝原因 + current_time_str=current_time_value # 新增:传入当前时间字符串 + ) except KeyError as e: logger.error( f"[私聊][{self.private_name}]格式化 Prompt 时出错,缺少键: {e}。请检查 Prompt 模板和传递的参数。"