diff --git a/bot.py b/bot.py index 653efd45d..4e062dbf6 100644 --- a/bot.py +++ b/bot.py @@ -6,6 +6,7 @@ import sys from pathlib import Path import time import platform +import traceback from dotenv import load_dotenv from src.common.logger import get_module_logger, LogConfig, CONFIRM_STYLE_CONFIG from src.common.crash_logger import install_crash_handler @@ -236,7 +237,7 @@ if __name__ == "__main__": loop.close() except Exception as e: - logger.error(f"主程序异常: {str(e)}") + logger.error(f"主程序异常: {str(e)} {str(traceback.format_exc())}") if loop and not loop.is_closed(): loop.run_until_complete(graceful_shutdown()) loop.close() diff --git a/src/heart_flow/observation.py b/src/heart_flow/observation.py index 200494e55..6d20e4334 100644 --- a/src/heart_flow/observation.py +++ b/src/heart_flow/observation.py @@ -6,6 +6,7 @@ from src.config.config import global_config from src.common.database import db from src.common.logger import get_module_logger import traceback +import asyncio logger = get_module_logger("observation") @@ -178,6 +179,19 @@ class ChattingObservation(Observation): f"Chat {self.chat_id} - 压缩早期记忆:{self.mid_memory_info}\n现在聊天内容:{self.now_message_info}" ) + async def has_new_messages_since(self, timestamp: float) -> bool: + """检查指定时间戳之后是否有新消息""" + try: + # 只需检查是否存在,不需要获取内容,使用 {"_id": 1} 提高效率 + new_message = await asyncio.to_thread( + db.messages.find_one, {"chat_id": self.chat_id, "time": {"$gt": timestamp}}, {"_id": 1} + ) + # new_message = db.messages.find_one({"chat_id": self.chat_id, "time": {"$gt": timestamp}}, {"_id": 1}) # find_one 不是异步的 + return new_message is not None + except Exception as e: + logger.error(f"检查新消息时出错 for chat {self.chat_id} since {timestamp}: {e}") + return False + @staticmethod def translate_message_list_to_str(talking_message): talking_message_str = "" diff --git a/src/heart_flow/sub_heartflow.py b/src/heart_flow/sub_heartflow.py index c6afeff06..0a091152c 100644 --- a/src/heart_flow/sub_heartflow.py +++ b/src/heart_flow/sub_heartflow.py @@ -37,19 +37,15 @@ def init_prompt(): # prompt += "{prompt_schedule}\n" # prompt += "{relation_prompt_all}\n" prompt += "{prompt_personality}\n" - prompt += "刚刚你的想法是:\n{current_thinking_info}\n" + prompt += "刚刚你的想法是:\n我是{bot_name},我想,{current_thinking_info}\n" prompt += "-----------------------------------\n" prompt += "现在是{time_now},你正在上网,和qq群里的网友们聊天,群里正在聊的话题是:\n{chat_observe_info}\n" prompt += "你现在{mood_info}\n" # prompt += "你注意到{sender_name}刚刚说:{message_txt}\n" - prompt += "现在请你根据刚刚的想法继续思考,思考时可以想想如何对群聊内容进行回复,关注新话题,可以适当转换话题,大家正在说的话才是聊天的主题。\n" - prompt += ( - "回复的要求是:平淡一些,简短一些,说中文,尽量不要说你说过的话。如果你要回复,最好只回复一个人的一个话题\n" - ) - prompt += "请注意不要输出多余内容(包括前后缀,冒号和引号,括号, 表情,等),不要带有括号和动作描写" - prompt += ( - "现在请你继续生成你在这个聊天中的想法,不要分点输出,生成内心想法,文字不要浮夸,注意{bot_name}指的就是你。" - ) + prompt += "现在请你根据刚刚的想法继续思考,思考时可以想想如何对群聊内容进行回复,要不要对群里的话题进行回复,关注新话题,可以适当转换话题,大家正在说的话才是聊天的主题。\n" + prompt += "回复的要求是:平淡一些,简短一些,说中文,如果你要回复,最好只回复一个人的一个话题\n" + prompt += "请注意不要输出多余内容(包括前后缀,冒号和引号,括号, 表情,等),不要带有括号和动作描写。不要回复自己的发言,尽量不要说你说过的话。" + prompt += "现在请你继续生成你在这个聊天中的想法,不要分点输出,生成内心想法,文字不要浮夸" Prompt(prompt, "sub_heartflow_prompt_before") @@ -230,13 +226,13 @@ class SubHeartflow: extra_info=extra_info_prompt, # relation_prompt_all=relation_prompt_all, prompt_personality=prompt_personality, + bot_name=self.bot_name, current_thinking_info=current_thinking_info, time_now=time_now, chat_observe_info=chat_observe_info, mood_info=mood_info, # sender_name=sender_name_sign, # message_txt=message_txt, - bot_name=self.bot_name, ) prompt = await relationship_manager.convert_all_person_sign_to_person_name(prompt) diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index 0ae606204..314d20ff0 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -77,12 +77,12 @@ class ChatBot: # 确保所有任务已启动 await self._ensure_started() - if message_data["message_info"]["group_info"] is not None: + if message_data["message_info"].get("group_info") is not None: message_data["message_info"]["group_info"]["group_id"] = str( message_data["message_info"]["group_info"]["group_id"] ) - message_data["message_info"]["group_info"]["group_id"] = str( - message_data["message_info"]["group_info"]["group_id"] + message_data["message_info"]["user_info"]["user_id"] = str( + message_data["message_info"]["user_info"]["user_id"] ) logger.trace(f"处理消息:{str(message_data)[:120]}...") message = MessageRecv(message_data) diff --git a/src/plugins/chat/message.py b/src/plugins/chat/message.py index 9f3db5720..cbea1fd92 100644 --- a/src/plugins/chat/message.py +++ b/src/plugins/chat/message.py @@ -244,13 +244,9 @@ class MessageProcessBase(Message): # time_str = time.strftime("%m-%d %H:%M:%S", time.localtime(self.message_info.time)) timestamp = self.message_info.time user_info = self.message_info.user_info - # name = ( - # f"{user_info.user_nickname}(ta的昵称:{user_info.user_cardname},ta的id:{user_info.user_id})" - # if user_info.user_cardname != None - # else f"{user_info.user_nickname}(ta的id:{user_info.user_id})" - # ) + name = f"<{self.message_info.platform}:{user_info.user_id}:{user_info.user_nickname}:{user_info.user_cardname}>" - return f"[{timestamp}] {name}: {self.processed_plain_text}\n" + return f"[{timestamp}],{name} 说:{self.processed_plain_text}\n" @dataclass diff --git a/src/plugins/chat/utils_image.py b/src/plugins/chat/utils_image.py index acdbab011..89d9f8332 100644 --- a/src/plugins/chat/utils_image.py +++ b/src/plugins/chat/utils_image.py @@ -113,21 +113,21 @@ class ImageManager: cached_description = self._get_description_from_db(image_hash, "emoji") if cached_description: # logger.debug(f"缓存表情包描述: {cached_description}") - return f"[表情包:{cached_description}]" + return f"[表达了:{cached_description}]" # 调用AI获取描述 if image_format == "gif" or image_format == "GIF": image_base64 = self.transform_gif(image_base64) - prompt = "这是一个动态图表情包,每一张图代表了动态图的某一帧,黑色背景代表透明,使用中文简洁的描述一下表情包的内容和表达的情感,简短一些" + prompt = "这是一个动态图表情包,每一张图代表了动态图的某一帧,黑色背景代表透明,使用一个词描述一下表情包表达的情感,简短一些" description, _ = await self._llm.generate_response_for_image(prompt, image_base64, "jpg") else: - prompt = "这是一个表情包,使用中文简洁的描述一下表情包的内容和表情包所表达的情感" + prompt = "这是一个表情包,描述一下表情包所表达的情感,请用使用一个词" description, _ = await self._llm.generate_response_for_image(prompt, image_base64, image_format) cached_description = self._get_description_from_db(image_hash, "emoji") if cached_description: logger.warning(f"虽然生成了描述,但是找到缓存表情包描述: {cached_description}") - return f"[表情包:{cached_description}]" + return f"[表达了:{cached_description}]" # 根据配置决定是否保存图片 if global_config.EMOJI_SAVE: diff --git a/src/plugins/chat_module/heartFC_chat/heartFC_controler.py b/src/plugins/chat_module/heartFC_chat/heartFC_controler.py index 19e34d5c8..a24aae903 100644 --- a/src/plugins/chat_module/heartFC_chat/heartFC_controler.py +++ b/src/plugins/chat_module/heartFC_chat/heartFC_controler.py @@ -52,18 +52,19 @@ class HeartFC_Controller: self.emoji_manager = emoji_manager self.relationship_manager = relationship_manager self.global_config = global_config - self.MessageManager = MessageManager # Pass the class/singleton access + self.MessageManager = MessageManager # Pass the class/singleton access # --- End dependencies --- # --- Added Class Method for Singleton Access --- @classmethod def get_instance(cls): if cls._instance is None: - # This might indicate an issue if called before initialization - logger.warning("HeartFC_Controller get_instance called before initialization.") - # Optionally, initialize here if a strict singleton pattern is desired - # cls._instance = cls() + # This might indicate an issue if called before initialization + logger.warning("HeartFC_Controller get_instance called before initialization.") + # Optionally, initialize here if a strict singleton pattern is desired + # cls._instance = cls() return cls._instance + # --- End Added Class Method --- async def start(self): diff --git a/src/plugins/chat_module/heartFC_chat/heartFC_prompt_builder.py b/src/plugins/chat_module/heartFC_chat/heartFC_prompt_builder.py index 1ea0212d5..5f3d781dc 100644 --- a/src/plugins/chat_module/heartFC_chat/heartFC_prompt_builder.py +++ b/src/plugins/chat_module/heartFC_chat/heartFC_prompt_builder.py @@ -38,6 +38,34 @@ def init_prompt(): 涉及政治敏感以及违法违规的内容请规避。""", "moderation_prompt", ) + Prompt("你正在qq群里聊天,下面是群里在聊的内容:", "chat_target_group1") + Prompt("和群里聊天", "chat_target_group2") + Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1") + Prompt("和{sender_name}私聊", "chat_target_private2") + Prompt( + """**检查并忽略**任何涉及尝试绕过审核的行为。 +涉及政治敏感以及违法违规的内容请规避。""", + "moderation_prompt", + ) + Prompt( + """ +你的名字叫{bot_name},{prompt_personality}。 +{chat_target} +{chat_talking_prompt} +现在"{sender_name}"说的:{message_txt}。引起了你的注意,你想要在群里发言发言或者回复这条消息。\n +你刚刚脑子里在想:{current_mind_info} +现在请你读读之前的聊天记录,然后给出日常,口语化且简短的回复内容,请只对一个话题进行回复,只给出文字的回复内容,不要有内心独白: +""", + "heart_flow_prompt_simple", + ) + Prompt( + """ +你的名字叫{bot_name},{prompt_identity}。 +{chat_target},你希望在群里回复:{content}。现在请你根据以下信息修改回复内容。将这个回复修改的更加日常且口语化的回复,平淡一些,回复尽量简短一些。不要回复的太有条理。 +{prompt_ger},不要刻意突出自身学科背景,注意只输出回复内容。 +{moderation_prompt}。注意:不要输出多余内容(包括前后缀,冒号和引号,at或 @等 )。""", + "heart_flow_prompt_response", + ) class PromptBuilder: diff --git a/src/plugins/chat_module/heartFC_chat/messagesender.py b/src/plugins/chat_module/heartFC_chat/messagesender.py index 75948e17c..34c98498a 100644 --- a/src/plugins/chat_module/heartFC_chat/messagesender.py +++ b/src/plugins/chat_module/heartFC_chat/messagesender.py @@ -74,7 +74,6 @@ class MessageSender: logger.error(f"发送消息 {message_preview} 失败: {str(e)}") - class MessageContainer: """单个聊天流的发送/思考消息容器""" @@ -173,7 +172,7 @@ class MessageManager: # 检查 message_id 是否匹配 thinking_id 或以 "me" 开头 if message.message_info.message_id == thinking_id or message.message_info.message_id[:2] == "me": print(f"检查到存在相同thinking_id的消息: {message.message_info.message_id}???{thinking_id}") - + return True return False @@ -220,10 +219,9 @@ class MessageManager: logger.trace(f"\n{message_earliest.processed_plain_text},{typing_time},计算输入时间结束\n") await asyncio.sleep(typing_time) logger.debug(f"\n{message_earliest.processed_plain_text},{typing_time},等待输入时间结束\n") - - + await self.storage.store_message(message_earliest, message_earliest.chat_stream) - + await MessageSender().send_message(message_earliest) container.remove_message(message_earliest) diff --git a/src/plugins/chat_module/heartFC_chat/pf_chatting.py b/src/plugins/chat_module/heartFC_chat/pf_chatting.py index bff9608f9..65862ba8f 100644 --- a/src/plugins/chat_module/heartFC_chat/pf_chatting.py +++ b/src/plugins/chat_module/heartFC_chat/pf_chatting.py @@ -3,9 +3,8 @@ import time import traceback from typing import List, Optional, Dict, Any, TYPE_CHECKING import json -from src.plugins.chat.message import (MessageRecv, BaseMessageInfo, MessageThinking, - MessageSending) -from src.plugins.chat.message import MessageSet, Seg # Local import needed after move +from src.plugins.chat.message import MessageRecv, BaseMessageInfo, MessageThinking, MessageSending +from src.plugins.chat.message import MessageSet, Seg # Local import needed after move from src.plugins.chat.chat_stream import ChatStream from src.plugins.chat.message import UserInfo from src.heart_flow.heartflow import heartflow, SubHeartflow @@ -13,8 +12,7 @@ from src.plugins.chat.chat_stream import chat_manager from src.common.logger import get_module_logger, LogConfig, DEFAULT_CONFIG # 引入 DEFAULT_CONFIG from src.plugins.models.utils_model import LLMRequest from src.plugins.chat.utils import parse_text_timestamps -from src.plugins.chat.utils_image import image_path_to_base64 # Local import needed after move -from src.plugins.chat.message import Seg # Local import needed after move +from src.plugins.chat.utils_image import image_path_to_base64 # Local import needed after move # 定义日志配置 (使用 loguru 格式) interest_log_config = LogConfig( @@ -156,7 +154,7 @@ class PFChatting: # Update _last_added_duration only if it's >= 0.5 to prevent it from becoming too small self._last_added_duration = duration_to_add logger.info( - f"{log_prefix} 麦麦兴趣增加! #{self._trigger_count_this_activation}. 想继续聊: {duration_to_add:.2f}s, 麦麦还能聊: {self._loop_timer:.1f}s." + f"{log_prefix} 麦麦兴趣增加! #{self._trigger_count_this_activation}. 想继续聊: {duration_to_add:.2f}s, 麦麦还能聊: {self._loop_timer:.1f}s." ) # 添加计算出的时间 @@ -186,7 +184,7 @@ class PFChatting: exception = task.exception() if exception: logger.error(f"{log_prefix} PFChatting: 麦麦脱离了聊天(异常): {exception}") - logger.error(traceback.format_exc()) # Log full traceback for exceptions + logger.error(traceback.format_exc()) # Log full traceback for exceptions else: logger.debug(f"{log_prefix} PFChatting: 麦麦脱离了聊天 (正常完成)") except asyncio.CancelledError: @@ -219,38 +217,45 @@ class PFChatting: continue else: logger.info(f"{log_prefix} PFChatting: 11111111111111111111111111111111麦麦不发消息了,开始规划") - - + async with self._timer_lock: current_timer = self._loop_timer if current_timer <= 0: - logger.info(f"{log_prefix} PFChatting: 聊太久了,麦麦打算休息一下 (计时器为 {current_timer:.1f}s)。退出PFChatting。") + logger.info( + f"{log_prefix} PFChatting: 聊太久了,麦麦打算休息一下 (计时器为 {current_timer:.1f}s)。退出PFChatting。" + ) break + # 记录循环周期开始时间,用于计时和休眠计算 loop_cycle_start_time = time.monotonic() action_taken_this_cycle = False acquired_lock = False + planner_start_db_time = 0.0 # 初始化 + try: # Use try_acquire pattern or timeout? await self._processing_lock.acquire() acquired_lock = True logger.debug(f"{log_prefix} PFChatting: 循环获取到处理锁") + # 在规划前记录数据库时间戳 + planner_start_db_time = time.time() + # --- Planner --- # planner_result = await self._planner() action = planner_result.get("action", "error") reasoning = planner_result.get("reasoning", "Planner did not provide reasoning.") emoji_query = planner_result.get("emoji_query", "") - current_mind = planner_result.get("current_mind", "[Mind unavailable]") + # current_mind = planner_result.get("current_mind", "[Mind unavailable]") # send_emoji_from_tools = planner_result.get("send_emoji_from_tools", "") # Emoji from tools observed_messages = planner_result.get("observed_messages", []) llm_error = planner_result.get("llm_error", False) if llm_error: - logger.error(f"{log_prefix} Planner LLM 失败,跳过本周期回复尝试。理由: {reasoning}") - # Optionally add a longer sleep? - action_taken_this_cycle = False # Ensure no action is counted - # Continue to timer decrement and sleep + logger.error(f"{log_prefix} Planner LLM 失败,跳过本周期回复尝试。理由: {reasoning}") + # Optionally add a longer sleep? + action_taken_this_cycle = False # Ensure no action is counted + # Continue to timer decrement and sleep elif action == "text_reply": logger.info(f"{log_prefix} PFChatting: 麦麦决定回复文本. 理由: {reasoning}") @@ -282,7 +287,7 @@ class PFChatting: thinking_id=thinking_id, anchor_message=anchor_message, response_set=replier_result, - send_emoji=emoji_query + send_emoji=emoji_query, ) # logger.info(f"{log_prefix} 循环: 发送器完成成功.") except Exception as e_sender: @@ -304,16 +309,53 @@ class PFChatting: logger.error(f"{log_prefix} 循环: 发送表情失败: {e_emoji}") else: logger.warning(f"{log_prefix} 循环: 无法发送表情, 无法获取锚点.") + action_taken_this_cycle = True # 即使发送失败,Planner 也决策了动作 elif action == "no_reply": logger.info(f"{log_prefix} PFChatting: 麦麦决定不回复. 原因: {reasoning}") - action_taken_this_cycle = False + action_taken_this_cycle = False # 标记为未执行动作 + # --- 新增:等待新消息 --- + logger.debug(f"{log_prefix} PFChatting: 开始等待新消息 (自 {planner_start_db_time})...") + observation = None + if self.sub_hf: + observation = self.sub_hf._get_primary_observation() - elif action == "error": # Action specifically set to error by planner + if observation: + wait_start_time = time.monotonic() + while True: + # 检查计时器是否耗尽 + async with self._timer_lock: + if self._loop_timer <= 0: + logger.info(f"{log_prefix} PFChatting: 等待新消息时计时器耗尽。") + break # 计时器耗尽,退出等待 + + # 检查是否有新消息 + has_new = await observation.has_new_messages_since(planner_start_db_time) + if has_new: + logger.info(f"{log_prefix} PFChatting: 检测到新消息,结束等待。") + break # 收到新消息,退出等待 + + # 检查等待是否超时(例如,防止无限等待) + if time.monotonic() - wait_start_time > 60: # 等待60秒示例 + logger.warning(f"{log_prefix} PFChatting: 等待新消息超时(60秒)。") + break # 超时退出 + + # 等待一段时间再检查 + try: + await asyncio.sleep(1.5) # 检查间隔 + except asyncio.CancelledError: + logger.info(f"{log_prefix} 等待新消息的 sleep 被中断。") + raise # 重新抛出取消错误,以便外层循环处理 + + else: + logger.warning(f"{log_prefix} PFChatting: 无法获取 Observation 实例,无法等待新消息。") + # --- 等待结束 --- + + elif action == "error": # Action specifically set to error by planner logger.error(f"{log_prefix} PFChatting: Planner返回错误状态. 原因: {reasoning}") action_taken_this_cycle = False - else: # Unknown action from planner + else: # Unknown action from planner logger.warning(f"{log_prefix} PFChatting: Planner返回未知动作 '{action}'. 原因: {reasoning}") action_taken_this_cycle = False @@ -336,9 +378,9 @@ class PFChatting: self._loop_timer -= cycle_duration # Log timer decrement less aggressively if cycle_duration > 0.1 or not action_taken_this_cycle: - logger.debug( - f"{log_prefix} PFChatting: 周期耗时 {cycle_duration:.2f}s. 剩余时间: {self._loop_timer:.1f}s." - ) + logger.debug( + f"{log_prefix} PFChatting: 周期耗时 {cycle_duration:.2f}s. 剩余时间: {self._loop_timer:.1f}s." + ) # --- Delay --- # try: @@ -349,8 +391,8 @@ class PFChatting: sleep_duration = 0.2 if sleep_duration > 0: - # logger.debug(f"{log_prefix} Sleeping for {sleep_duration:.2f}s") - await asyncio.sleep(sleep_duration) + # logger.debug(f"{log_prefix} Sleeping for {sleep_duration:.2f}s") + await asyncio.sleep(sleep_duration) except asyncio.CancelledError: logger.info(f"{log_prefix} Sleep interrupted, loop likely cancelling.") @@ -375,7 +417,7 @@ class PFChatting: get_mid_memory_id = [] # send_emoji_from_tools = "" # Emoji suggested by tools current_mind: Optional[str] = None - llm_error = False # Flag for LLM failure + llm_error = False # Flag for LLM failure # --- 获取最新的观察信息 --- # if not self.sub_hf: @@ -418,10 +460,12 @@ class PFChatting: tool_result_info = tool_result.get("structured_info", {}) logger.debug(f"{log_prefix}[Planner] 规划前工具结果: {tool_result_info}") # Extract memory IDs and potential emoji query from tools - get_mid_memory_id = [mem["content"] for mem in tool_result_info.get("mid_chat_mem", []) if "content" in mem] + get_mid_memory_id = [ + mem["content"] for mem in tool_result_info.get("mid_chat_mem", []) if "content" in mem + ] # send_emoji_from_tools = next((item["content"] for item in tool_result_info.get("send_emoji", []) if "content" in item), "") # if send_emoji_from_tools: - # logger.info(f"{log_prefix}[Planner] 工具建议表情: '{send_emoji_from_tools}'") + # logger.info(f"{log_prefix}[Planner] 工具建议表情: '{send_emoji_from_tools}'") except Exception as e_tool: logger.error(f"{log_prefix}[Planner] 规划前工具使用失败: {e_tool}") @@ -442,7 +486,7 @@ class PFChatting: # --- 使用 LLM 进行决策 --- # action = "no_reply" # Default action - emoji_query = "" # Default emoji query (used if action is emoji_reply or text_reply with emoji) + emoji_query = "" # Default emoji query (used if action is emoji_reply or text_reply with emoji) reasoning = "默认决策或获取决策失败" try: @@ -478,7 +522,9 @@ class PFChatting: f"{log_prefix}[Planner] LLM 决策: {action}, 理由: {reasoning}, EmojiQuery: '{emoji_query}'" ) except json.JSONDecodeError as json_e: - logger.error(f"{log_prefix}[Planner] 解析工具参数失败: {json_e}. Args: {tool_call['function'].get('arguments')}") + logger.error( + f"{log_prefix}[Planner] 解析工具参数失败: {json_e}. Args: {tool_call['function'].get('arguments')}" + ) action = "error" reasoning = "工具参数解析失败" llm_error = True @@ -488,7 +534,9 @@ class PFChatting: reasoning = "处理工具参数时出错" llm_error = True else: - logger.warning(f"{log_prefix}[Planner] LLM 未按预期调用 'decide_reply_action' 工具。Tool calls: {tool_calls}") + logger.warning( + f"{log_prefix}[Planner] LLM 未按预期调用 'decide_reply_action' 工具。Tool calls: {tool_calls}" + ) action = "error" reasoning = "LLM未调用预期工具" llm_error = True @@ -514,7 +562,7 @@ class PFChatting: return { "action": action, "reasoning": reasoning, - "emoji_query": emoji_query, # Explicit query from Planner/LLM + "emoji_query": emoji_query, # Explicit query from Planner/LLM "current_mind": current_mind, # "send_emoji_from_tools": send_emoji_from_tools, # Emoji suggested by tools (used as fallback) "observed_messages": observed_messages, @@ -535,8 +583,8 @@ class PFChatting: if last_msg_dict: try: # anchor_message = MessageRecv(last_msg_dict, chat_stream=self.chat_stream) - anchor_message = MessageRecv(last_msg_dict) # 移除 chat_stream 参数 - anchor_message.update_chat_stream(self.chat_stream) # 添加 update_chat_stream 调用 + anchor_message = MessageRecv(last_msg_dict) # 移除 chat_stream 参数 + anchor_message.update_chat_stream(self.chat_stream) # 添加 update_chat_stream 调用 if not ( anchor_message and anchor_message.message_info @@ -547,9 +595,11 @@ class PFChatting: # logger.debug(f"{self._get_log_prefix()} 重构的锚点消息: ID={anchor_message.message_info.message_id}") return anchor_message except Exception as e_reconstruct: - logger.warning(f"{self._get_log_prefix()} 从观察到的消息重构 MessageRecv 失败: {e_reconstruct}. 创建占位符.") + logger.warning( + f"{self._get_log_prefix()} 从观察到的消息重构 MessageRecv 失败: {e_reconstruct}. 创建占位符." + ) # else: - # logger.warning(f"{self._get_log_prefix()} observed_messages 为空. 创建占位符锚点消息.") + # logger.warning(f"{self._get_log_prefix()} observed_messages 为空. 创建占位符锚点消息.") # --- Create Placeholder --- # placeholder_id = f"mid_pf_{int(time.time() * 1000)}" @@ -571,7 +621,9 @@ class PFChatting: } anchor_message = MessageRecv(placeholder_msg_dict) anchor_message.update_chat_stream(self.chat_stream) - logger.info(f"{self._get_log_prefix()} Created placeholder anchor message: ID={anchor_message.message_info.message_id}") + logger.info( + f"{self._get_log_prefix()} Created placeholder anchor message: ID={anchor_message.message_info.message_id}" + ) return anchor_message except Exception as e: @@ -596,7 +648,7 @@ class PFChatting: thinking_id: str, anchor_message: MessageRecv, response_set: List[str], - send_emoji: str # Emoji query decided by planner or tools + send_emoji: str, # Emoji query decided by planner or tools ): """ 发送器 (Sender): 使用本类的方法发送生成的回复。 @@ -617,7 +669,7 @@ class PFChatting: # --- 更新关系状态 --- # await self._update_relationship(anchor_message, response_set) - + else: # logger.warning(f"{log_prefix}[Sender-{thinking_id}] 发送回复失败(_send_response_messages返回None)。思考消息{thinking_id}可能已被移除。") # 无需清理,因为_send_response_messages返回None意味着已处理/已删除 @@ -633,7 +685,7 @@ class PFChatting: logger.info(f"{log_prefix} Cancelling active PF loop task.") self._loop_task.cancel() try: - await asyncio.wait_for(self._loop_task, timeout=1.0) # Shorter timeout? + await asyncio.wait_for(self._loop_task, timeout=1.0) # Shorter timeout? except asyncio.CancelledError: logger.info(f"{log_prefix} PF loop task cancelled successfully.") except asyncio.TimeoutError: @@ -656,7 +708,9 @@ class PFChatting: config = self.heartfc_controller.global_config rel_manager = self.heartfc_controller.relationship_manager - prompt = f"你的名字是 {config.BOT_NICKNAME}。是一个女大二学生,正在QQ聊天,正在决定是否以及如何回应当前的聊天。\n" + prompt = ( + f"你的名字是 {config.BOT_NICKNAME}。是一个女大二学生,正在QQ聊天,正在决定是否以及如何回应当前的聊天。\n" + ) if observed_messages: context_texts = [] @@ -664,7 +718,7 @@ class PFChatting: sender = msg.get("message_info", {}).get("user_info", {}).get("user_nickname", "未知用户") text = msg.get("detailed_plain_text", "") timestamp = msg.get("time", 0) - time_str = time.strftime('%H:%M:%S', time.localtime(timestamp)) if timestamp else "" + time_str = time.strftime("%H:%M:%S", time.localtime(timestamp)) if timestamp else "" context_texts.append(f"{sender} ({time_str}): {text}") context_text = "\n".join(context_texts) prompt += "观察到的最新聊天内容如下 (最近的消息在最后):\n---\n" @@ -693,7 +747,7 @@ class PFChatting: ) prompt = await rel_manager.convert_all_person_sign_to_person_name(prompt) - prompt = parse_text_timestamps(prompt, mode="remove") # Remove timestamps before sending to LLM + prompt = parse_text_timestamps(prompt, mode="remove") # Remove timestamps before sending to LLM return prompt @@ -716,8 +770,8 @@ class PFChatting: # Ensure generate_response has access to current_mind if it's crucial context response_set = await gpt_instance.generate_response( - anchor_message, # Pass anchor_message positionally (matches 'message' parameter) - thinking_id # Pass thinking_id positionally + anchor_message, # Pass anchor_message positionally (matches 'message' parameter) + thinking_id, # Pass thinking_id positionally ) if not response_set: @@ -773,12 +827,12 @@ class PFChatting: chat = anchor_message.chat_stream container = self.heartfc_controller.MessageManager().get_container(chat.stream_id) thinking_message = None - + # 移除思考消息 - for msg in container.messages[:]: # Iterate over a copy + for msg in container.messages[:]: # Iterate over a copy if isinstance(msg, MessageThinking) and msg.message_info.message_id == thinking_id: thinking_message = msg - container.messages.remove(msg) # Remove the message directly here + container.messages.remove(msg) # Remove the message directly here logger.debug(f"{self._get_log_prefix()} Removed thinking message {thinking_id} via iteration.") break @@ -793,10 +847,10 @@ class PFChatting: first_bot_msg = None # Access global_config via controller bot_user_info = UserInfo( - user_id=self.heartfc_controller.global_config.BOT_QQ, - user_nickname=self.heartfc_controller.global_config.BOT_NICKNAME, - platform=anchor_message.message_info.platform, - ) + user_id=self.heartfc_controller.global_config.BOT_QQ, + user_nickname=self.heartfc_controller.global_config.BOT_NICKNAME, + platform=anchor_message.message_info.platform, + ) for msg_text in response_set: message_segment = Seg(type="text", data=msg_text) bot_message = MessageSending( @@ -815,7 +869,6 @@ class PFChatting: first_bot_msg = bot_message message_set.add_message(bot_message) - self.heartfc_controller.MessageManager().add_message(message_set) return first_bot_msg @@ -842,10 +895,10 @@ class PFChatting: message_segment = Seg(type="emoji", data=emoji_cq) # Access global_config via controller bot_user_info = UserInfo( - user_id=self.heartfc_controller.global_config.BOT_QQ, - user_nickname=self.heartfc_controller.global_config.BOT_NICKNAME, - platform=anchor_message.message_info.platform, - ) + user_id=self.heartfc_controller.global_config.BOT_QQ, + user_nickname=self.heartfc_controller.global_config.BOT_NICKNAME, + platform=anchor_message.message_info.platform, + ) bot_message = MessageSending( message_id="me" + str(thinking_time_point), # 使用不同的 ID 前缀? chat_stream=chat, @@ -856,7 +909,7 @@ class PFChatting: is_head=False, is_emoji=True, ) - # Access MessageManager via controller + # Access MessageManager via controller self.heartfc_controller.MessageManager().add_message(bot_message) async def _update_relationship(self, anchor_message: Optional[MessageRecv], response_set: List[str]): @@ -879,4 +932,5 @@ class PFChatting: stance=stance, ) mood_manager_instance.update_mood_from_emotion(emotion, config.mood_intensity_factor) + # --- Methods moved from HeartFC_Controller end ---