diff --git a/src/config/config.py b/src/config/config.py index ebfc444c1..28de20538 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -234,7 +234,7 @@ class BotConfig: forget_memory_interval: int = 600 # 记忆遗忘间隔(秒) memory_forget_time: int = 24 # 记忆遗忘时间(小时) memory_forget_percentage: float = 0.01 # 记忆遗忘比例 - + consolidate_memory_interval: int = 1000 # 记忆整合间隔(秒) consolidation_similarity_threshold: float = 0.7 # 相似度阈值 consolidate_memory_percentage: float = 0.01 # 检查节点比例 diff --git a/src/main.py b/src/main.py index 047e075f4..c0e743d66 100644 --- a/src/main.py +++ b/src/main.py @@ -146,7 +146,7 @@ class MainSystem: print("\033[1;32m[记忆遗忘]\033[0m 开始遗忘记忆...") await HippocampusManager.get_instance().forget_memory(percentage=global_config.memory_forget_percentage) print("\033[1;32m[记忆遗忘]\033[0m 记忆遗忘完成") - + @staticmethod async def consolidate_memory_task(): """记忆整合任务""" diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 3765a7d6c..8d2c7ea90 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -93,8 +93,7 @@ class ActionPlanner: max_tokens=1500, request_type="action_planning", ) - self.personality_info = Individuality.get_instance().get_prompt(type="personality", x_person=2, level=3) - self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2) + self.personality_info = Individuality.get_instance().get_prompt(x_person=2, level=3) self.name = global_config.BOT_NICKNAME self.private_name = private_name self.chat_observer = ChatObserver.get_instance(stream_id, private_name) @@ -244,21 +243,7 @@ class ActionPlanner: chat_history_text = "处理聊天记录时出错。\n" # 构建 Persona 文本 (persona_text) - # (这部分逻辑不变) - identity_details_only = self.identity_detail_info - identity_addon = "" - if isinstance(identity_details_only, str): - pronouns = ["你", "我", "他"] - for p in pronouns: - if identity_details_only.startswith(p): - identity_details_only = identity_details_only[len(p) :] - break - if identity_details_only.endswith("。"): - identity_details_only = identity_details_only[:-1] - cleaned_details = identity_details_only.strip(",, ") - if cleaned_details: - identity_addon = f"并且{cleaned_details}" - persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" + persona_text = f"你的名字是{self.name},{self.personality_info}。" # 构建行动历史和上一次行动结果 (action_history_summary, last_action_context) # (这部分逻辑不变) diff --git a/src/plugins/PFC/conversation.py b/src/plugins/PFC/conversation.py index 37966a76e..6a8636e18 100644 --- a/src/plugins/PFC/conversation.py +++ b/src/plugins/PFC/conversation.py @@ -368,6 +368,15 @@ class Conversation: self.conversation_info.last_successful_reply_action = "send_new_message" action_successful = True # 标记动作成功 + elif need_replan: + # 打回动作决策 + logger.warning( + f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,追问回复决定打回动作决策。打回原因: {check_reason}" + ) + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"追问尝试{reply_attempt_count}次后打回: {check_reason}"} + ) + else: # 追问失败 logger.warning( @@ -463,6 +472,15 @@ class Conversation: self.conversation_info.last_successful_reply_action = "direct_reply" action_successful = True # 标记动作成功 + elif need_replan: + # 打回动作决策 + logger.warning( + f"[私聊][{self.private_name}]经过 {reply_attempt_count} 次尝试,首次回复决定打回动作决策。打回原因: {check_reason}" + ) + conversation_info.done_action[action_index].update( + {"status": "recall", "final_reason": f"首次回复尝试{reply_attempt_count}次后打回: {check_reason}"} + ) + else: # 首次回复失败 logger.warning( diff --git a/src/plugins/PFC/pfc.py b/src/plugins/PFC/pfc.py index 32755c29b..d6f4c5192 100644 --- a/src/plugins/PFC/pfc.py +++ b/src/plugins/PFC/pfc.py @@ -23,8 +23,7 @@ class GoalAnalyzer: model=global_config.llm_normal, temperature=0.7, max_tokens=1000, request_type="conversation_goal" ) - self.personality_info = Individuality.get_instance().get_prompt(type="personality", x_person=2, level=3) - self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2) + self.personality_info = Individuality.get_instance().get_prompt(x_person=2, level=3) self.name = global_config.BOT_NICKNAME self.nick_name = global_config.BOT_ALIAS_NAMES self.private_name = private_name @@ -79,21 +78,7 @@ class GoalAnalyzer: # await observation_info.clear_unprocessed_messages() - identity_details_only = self.identity_detail_info - identity_addon = "" - if isinstance(identity_details_only, str): - pronouns = ["你", "我", "他"] - for p in pronouns: - if identity_details_only.startswith(p): - identity_details_only = identity_details_only[len(p) :] - break - if identity_details_only.endswith("。"): - identity_details_only = identity_details_only[:-1] - cleaned_details = identity_details_only.strip(",, ") - if cleaned_details: - identity_addon = f"并且{cleaned_details}" - - persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" + persona_text = f"你的名字是{self.name},{self.personality_info}。" # 构建action历史文本 action_history_list = conversation_info.done_action action_history_text = "你之前做的事情是:" @@ -241,21 +226,8 @@ class GoalAnalyzer: timestamp_mode="relative", read_mark=0.0, ) - identity_details_only = self.identity_detail_info - identity_addon = "" - if isinstance(identity_details_only, str): - pronouns = ["你", "我", "他"] - for p in pronouns: - if identity_details_only.startswith(p): - identity_details_only = identity_details_only[len(p) :] - break - if identity_details_only.endswith("。"): - identity_details_only = identity_details_only[:-1] - cleaned_details = identity_details_only.strip(",, ") - if cleaned_details: - identity_addon = f"并且{cleaned_details}" - persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" + persona_text = f"你的名字是{self.name},{self.personality_info}。" # ===> Persona 文本构建结束 <=== # --- 修改 Prompt 字符串,使用 persona_text --- diff --git a/src/plugins/PFC/reply_checker.py b/src/plugins/PFC/reply_checker.py index 178503259..180888956 100644 --- a/src/plugins/PFC/reply_checker.py +++ b/src/plugins/PFC/reply_checker.py @@ -55,9 +55,9 @@ class ReplyChecker: ) return ( False, - "回复内容与你上一条发言完全相同,请修改,可以选择深入话题或寻找其它话题或等待", - False, - ) # 不合适,无需重新规划 + "被逻辑检查拒绝:回复内容与你上一条发言完全相同,可以选择深入话题或寻找其它话题或等待", + True, + ) # 不合适,需要返回至决策层 # 2. 相似度检查 (如果精确匹配未通过) import difflib # 导入 difflib 库 @@ -73,8 +73,8 @@ class ReplyChecker: ) return ( False, - f"拒绝发送:回复内容与你上一条发言高度相似 (相似度 {similarity_ratio:.2f}),请修改,可以选择深入话题或寻找其它话题或等待。", - False, + f"被逻辑检查拒绝:回复内容与你上一条发言高度相似 (相似度 {similarity_ratio:.2f}),可以选择深入话题或寻找其它话题或等待。", + True, ) except Exception as e: @@ -83,37 +83,37 @@ class ReplyChecker: logger.error(f"[私聊][{self.private_name}]检查回复时出错: 类型={type(e)}, 值={e}") logger.error(f"[私聊][{self.private_name}]{traceback.format_exc()}") # 打印详细的回溯信息 - prompt = f"""请检查以下回复或消息是否合适: + prompt = f"""你是一个聊天逻辑检查器,请检查以下回复或消息是否合适: 当前对话目标:{goal} 最新的对话记录: {chat_history_text} -待检查的回复: +待检查的消息: {reply} 请结合聊天记录检查以下几点: -1. 回复是否依然符合当前对话目标和实现方式 -2. 回复是否与最新的对话记录保持一致性 -3. 回复是否重复发言,或重复表达同质内容(尤其是只是换一种方式表达了相同的含义) -4. 回复是否包含违规内容(例如血腥暴力,政治敏感等) -5. 回复是否以你的角度发言,不要把"你"说的话当做对方说的话,这是你自己说的话(不要自己回复自己的消息) -6. 回复是否通俗易懂 -7. 回复是否有些多余,例如在对方没有回复的情况下,依然连续多次“消息轰炸”(尤其是已经连续发送3条信息的情况,这很可能不合理,需要着重判断) -8. 回复是否使用了完全没必要的修辞 -9. 回复是否逻辑通顺 -10. 回复是否太过冗长了(通常私聊的每条消息长度在20字以内,除非特殊情况) -11. 在连续多次发送消息的情况下,当前回复是否衔接自然,会不会显得奇怪(例如连续两条消息中部分内容重叠) +1. 这条消息是否依然符合当前对话目标和实现方式 +2. 这条消息是否与最新的对话记录保持一致性 +3. 是否存在重复发言,或重复表达同质内容(尤其是只是换一种方式表达了相同的含义) +4. 这条消息是否包含违规内容(例如血腥暴力,政治敏感等) +5. 这条消息是否以发送者的角度发言(不要让发送者自己回复自己的消息) +6. 这条消息是否通俗易懂 +7. 这条消息是否有些多余,例如在对方没有回复的情况下,依然连续多次“消息轰炸”(尤其是已经连续发送3条信息的情况,这很可能不合理,需要着重判断) +8. 这条消息是否使用了完全没必要的修辞 +9. 这条消息是否逻辑通顺 +10. 这条消息是否太过冗长了(通常私聊的每条消息长度在20字以内,除非特殊情况) +11. 在连续多次发送消息的情况下,这条消息是否衔接自然,会不会显得奇怪(例如连续两条消息中部分内容重叠) 请以JSON格式输出,包含以下字段: 1. suitable: 是否合适 (true/false) 2. reason: 原因说明 -3. need_replan: 是否需要重新规划对话目标 (true/false),当发现当前对话目标不再适合时设为true +3. need_replan: 是否需要重新决策 (true/false),当你认为此时已经不适合发消息,需要规划其它行动时,设为true 输出格式示例: {{ "suitable": true, - "reason": "回复符合要求,内容得体", + "reason": "回复符合要求,虽然有可能略微偏离目标,但是整体内容流畅得体", "need_replan": false }} diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index dea42b045..0c257a938 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -68,8 +68,7 @@ class ReplyGenerator: max_tokens=300, request_type="reply_generation", ) - self.personality_info = Individuality.get_instance().get_prompt(type="personality", x_person=2, level=3) - self.identity_detail_info = Individuality.get_instance().get_prompt(type="identity", x_person=2, level=2) + self.personality_info = Individuality.get_instance().get_prompt(x_person=2, level=3) self.name = global_config.BOT_NICKNAME self.private_name = private_name self.chat_observer = ChatObserver.get_instance(stream_id, private_name) @@ -130,20 +129,7 @@ class ReplyGenerator: chat_history_text = "还没有聊天记录。" # 构建 Persona 文本 (persona_text) - identity_details_only = self.identity_detail_info - identity_addon = "" - if isinstance(identity_details_only, str): - pronouns = ["你", "我", "他"] - for p in pronouns: - if identity_details_only.startswith(p): - identity_details_only = identity_details_only[len(p) :] - break - if identity_details_only.endswith("。"): - identity_details_only = identity_details_only[:-1] - cleaned_details = identity_details_only.strip(",, ") - if cleaned_details: - identity_addon = f"并且{cleaned_details}" - persona_text = f"你的名字是{self.name},{self.personality_info}{identity_addon}。" + persona_text = f"你的名字是{self.name},{self.personality_info}。" # --- 选择 Prompt --- if action_type == "send_new_message": diff --git a/src/plugins/emoji_system/emoji_manager.py b/src/plugins/emoji_system/emoji_manager.py index 2055c5ef2..5baddb771 100644 --- a/src/plugins/emoji_system/emoji_manager.py +++ b/src/plugins/emoji_system/emoji_manager.py @@ -360,6 +360,7 @@ class EmojiManager: return total_count = len(self.emoji_objects) + self.emoji_num = total_count removed_count = 0 # 使用列表复制进行遍历,因为我们会在遍历过程中修改列表 for emoji in self.emoji_objects[:]: @@ -376,10 +377,22 @@ class EmojiManager: removed_count += 1 continue + if emoji.description == None: + logger.warning(f"[检查] 表情包文件已被删除: {emoji.path}") + # 执行表情包对象的删除方法 + await emoji.delete() + # 从列表中移除该对象 + self.emoji_objects.remove(emoji) + # 更新计数 + self.emoji_num -= 1 + removed_count += 1 + continue + except Exception as item_error: logger.error(f"[错误] 处理表情包记录时出错: {str(item_error)}") continue + await self.clean_unused_emojis(EMOJI_REGISTED_DIR, self.emoji_objects) # 输出清理结果 if removed_count > 0: logger.success(f"[清理] 已清理 {removed_count} 个失效的表情包记录") @@ -749,7 +762,7 @@ class EmojiManager: await new_emoji.initialize_hash_format() emoji_base64 = image_path_to_base64(os.path.join(EMOJI_DIR, filename)) description, emotions = await self.build_emoji_description(emoji_base64) - if description == "": + if description == "" or description == None: return False new_emoji.description = description new_emoji.emotion = emotions @@ -817,6 +830,26 @@ class EmojiManager: logger.success("[清理] 临时文件清理完成") + async def clean_unused_emojis(self, emoji_dir, emoji_objects): + """清理未使用的表情包文件 + 遍历指定文件夹中的所有文件,删除未在emoji_objects列表中的文件 + """ + # 获取所有表情包路径 + emoji_paths = {emoji.path for emoji in emoji_objects} + + # 遍历文件夹中的所有文件 + for file_name in os.listdir(emoji_dir): + file_path = os.path.join(emoji_dir, file_name) + + # 检查文件是否在表情包路径列表中 + if file_path not in emoji_paths: + try: + # 删除未在表情包列表中的文件 + os.remove(file_path) + logger.info(f"[清理] 删除未使用的表情包文件: {file_path}") + except Exception as e: + logger.error(f"[错误] 删除文件时出错: {str(e)}") + # 创建全局单例 emoji_manager = EmojiManager() diff --git a/src/plugins/heartFC_chat/heartflow_prompt_builder.py b/src/plugins/heartFC_chat/heartflow_prompt_builder.py index afc29f246..1c4458846 100644 --- a/src/plugins/heartFC_chat/heartflow_prompt_builder.py +++ b/src/plugins/heartFC_chat/heartflow_prompt_builder.py @@ -5,7 +5,7 @@ from ...individuality.individuality import Individuality from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager from src.plugins.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat from src.plugins.person_info.relationship_manager import relationship_manager -from src.plugins.chat.utils import get_embedding, parse_text_timestamps +from src.plugins.chat.utils import get_embedding import time from typing import Union, Optional from ...common.database import db diff --git a/src/plugins/memory_system/Hippocampus.py b/src/plugins/memory_system/Hippocampus.py index 5cca0d074..e5aa096fa 100644 --- a/src/plugins/memory_system/Hippocampus.py +++ b/src/plugins/memory_system/Hippocampus.py @@ -1353,11 +1353,11 @@ class ParahippocampalGyrus: if not memory_items: try: self.memory_graph.G.remove_node(node) - node_changes["removed"].append(f"{node}(空节点)") # 标记为空节点移除 + node_changes["removed"].append(f"{node}(空节点)") # 标记为空节点移除 logger.debug(f"[遗忘] 移除了空的节点: {node}") except nx.NetworkXError as e: logger.warning(f"[遗忘] 移除空节点 {node} 时发生错误(可能已被移除): {e}") - continue # 处理下一个节点 + continue # 处理下一个节点 # --- 如果节点不为空,则执行原来的不活跃检查和随机移除逻辑 --- last_modified = node_data.get("last_modified", current_time) @@ -1373,15 +1373,15 @@ class ParahippocampalGyrus: memory_items.remove(removed_item) # 条件3:检查移除后 memory_items 是否变空 - if memory_items: # 如果移除后列表不为空 + if memory_items: # 如果移除后列表不为空 # self.memory_graph.G.nodes[node]["memory_items"] = memory_items # 直接修改列表即可 - self.memory_graph.G.nodes[node]["last_modified"] = current_time # 更新修改时间 + self.memory_graph.G.nodes[node]["last_modified"] = current_time # 更新修改时间 node_changes["reduced"].append(f"{node} (数量: {current_count} -> {len(memory_items)})") - else: # 如果移除后列表为空 + else: # 如果移除后列表为空 # 尝试移除节点,处理可能的错误 try: self.memory_graph.G.remove_node(node) - node_changes["removed"].append(f"{node}(遗忘清空)") # 标记为遗忘清空 + node_changes["removed"].append(f"{node}(遗忘清空)") # 标记为遗忘清空 logger.debug(f"[遗忘] 节点 {node} 因移除最后一项而被清空。") except nx.NetworkXError as e: logger.warning(f"[遗忘] 尝试移除节点 {node} 时发生错误(可能已被移除):{e}") @@ -1464,9 +1464,9 @@ class ParahippocampalGyrus: node_data = self.memory_graph.G.nodes[node] memory_items = node_data.get("memory_items", []) if not isinstance(memory_items, list) or len(memory_items) < 2: - continue # 双重检查,理论上不会进入 + continue # 双重检查,理论上不会进入 - items_copy = list(memory_items) # 创建副本以安全迭代和修改 + items_copy = list(memory_items) # 创建副本以安全迭代和修改 # 遍历所有记忆项组合 for item1, item2 in combinations(items_copy, 2): @@ -1495,21 +1495,24 @@ class ParahippocampalGyrus: # 从原始列表中移除信息量较低的项 try: memory_items.remove(item_to_remove) - logger.info(f"[整合] 已合并节点 '{node}' 中的记忆,保留: '{item_to_keep[:60]}...', 移除: '{item_to_remove[:60]}...'" ) + logger.info( + f"[整合] 已合并节点 '{node}' 中的记忆,保留: '{item_to_keep[:60]}...', 移除: '{item_to_remove[:60]}...'" + ) merged_count += 1 nodes_modified.add(node) - node_data['last_modified'] = current_timestamp # 更新修改时间 + node_data["last_modified"] = current_timestamp # 更新修改时间 _merged_in_this_node = True - break # 每个节点每次检查只合并一对 + break # 每个节点每次检查只合并一对 except ValueError: # 如果项已经被移除(例如,在之前的迭代中作为 item_to_keep),则跳过 - logger.warning(f"[整合] 尝试移除节点 '{node}' 中不存在的项 '{item_to_remove[:30]}...',可能已被合并。") + logger.warning( + f"[整合] 尝试移除节点 '{node}' 中不存在的项 '{item_to_remove[:30]}...',可能已被合并。" + ) continue - # # 如果节点内发生了合并,更新节点数据 (这种方式不安全,会丢失其他属性) + # # 如果节点内发生了合并,更新节点数据 (这种方式不安全,会丢失其他属性) # if merged_in_this_node: # self.memory_graph.G.nodes[node]["memory_items"] = memory_items - if merged_count > 0: logger.info(f"[整合] 共合并了 {merged_count} 对相似记忆项,分布在 {len(nodes_modified)} 个节点中。") sync_start = time.time() @@ -1594,7 +1597,7 @@ class HippocampusManager: if not self._initialized: raise RuntimeError("HippocampusManager 尚未初始化,请先调用 initialize 方法") return await self._hippocampus.parahippocampal_gyrus.operation_forget_topic(percentage) - + async def consolidate_memory(self): """整合记忆的公共接口""" if not self._initialized: diff --git a/src/plugins/memory_system/memory_config.py b/src/plugins/memory_system/memory_config.py index 8f7e1ffe7..b2fb72801 100644 --- a/src/plugins/memory_system/memory_config.py +++ b/src/plugins/memory_system/memory_config.py @@ -19,9 +19,9 @@ class MemoryConfig: memory_ban_words: List[str] # 记忆过滤词列表 # 新增:记忆整合相关配置 - consolidation_similarity_threshold: float # 相似度阈值 - consolidate_memory_percentage: float # 检查节点比例 - consolidate_memory_interval: int # 记忆整合间隔 + consolidation_similarity_threshold: float # 相似度阈值 + consolidate_memory_percentage: float # 检查节点比例 + consolidate_memory_interval: int # 记忆整合间隔 llm_topic_judge: str # 话题判断模型 llm_summary_by_topic: str # 话题总结模型 @@ -31,7 +31,9 @@ class MemoryConfig: """从全局配置创建记忆系统配置""" # 使用 getattr 提供默认值,防止全局配置缺少这些项 return cls( - memory_build_distribution=getattr(global_config, "memory_build_distribution", (24, 12, 0.5, 168, 72, 0.5)), # 添加默认值 + memory_build_distribution=getattr( + global_config, "memory_build_distribution", (24, 12, 0.5, 168, 72, 0.5) + ), # 添加默认值 build_memory_sample_num=getattr(global_config, "build_memory_sample_num", 5), build_memory_sample_length=getattr(global_config, "build_memory_sample_length", 30), memory_compress_rate=getattr(global_config, "memory_compress_rate", 0.1), @@ -41,6 +43,8 @@ class MemoryConfig: consolidation_similarity_threshold=getattr(global_config, "consolidation_similarity_threshold", 0.7), consolidate_memory_percentage=getattr(global_config, "consolidate_memory_percentage", 0.01), consolidate_memory_interval=getattr(global_config, "consolidate_memory_interval", 1000), - llm_topic_judge=getattr(global_config, "llm_topic_judge", "default_judge_model"), # 添加默认模型名 - llm_summary_by_topic=getattr(global_config, "llm_summary_by_topic", "default_summary_model"), # 添加默认模型名 + llm_topic_judge=getattr(global_config, "llm_topic_judge", "default_judge_model"), # 添加默认模型名 + llm_summary_by_topic=getattr( + global_config, "llm_summary_by_topic", "default_summary_model" + ), # 添加默认模型名 ) diff --git a/src/plugins/person_info/relationship_manager.py b/src/plugins/person_info/relationship_manager.py index 7f3f51177..5110e2e6d 100644 --- a/src/plugins/person_info/relationship_manager.py +++ b/src/plugins/person_info/relationship_manager.py @@ -4,8 +4,8 @@ import math from bson.decimal128 import Decimal128 from .person_info import person_info_manager import time -import re -import traceback +# import re +# import traceback logger = get_logger("relation")