From 7789050a45d5931eb53bd94ab2403a22bc44f5f4 Mon Sep 17 00:00:00 2001 From: 114514 <2514624910@qq.com> Date: Wed, 30 Apr 2025 14:45:28 +0800 Subject: [PATCH 01/20] =?UTF-8?q?=E6=8F=92=E5=85=A5=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E5=BA=93=E5=92=8C=E8=AE=B0=E5=BF=86=E6=A3=80=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 39 +++++++++++++++++++++++++- src/plugins/PFC/reply_generator.py | 45 ++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index a80e96b1..dd7355fa 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -21,6 +21,7 @@ PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊, 【当前对话目标】 {goals_str} +{knowledge_info_str} 【最近行动历史概要】 {action_history_summary} @@ -33,7 +34,7 @@ PROMPT_INITIAL_REPLY = """{persona_text}。现在你在参与一场QQ私聊, ------ 可选行动类型以及解释: -fetch_knowledge: 需要调取知识,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择 +fetch_knowledge: 需要调取知识或记忆,当需要专业知识或特定信息时选择,对方若提到你不太认识的人名或实体也可以尝试选择 listening: 倾听对方发言,当你认为对方话才说到一半,发言明显未结束时选择 direct_reply: 直接回复对方 rethink_goal: 思考一个对话目标,当你觉得目前对话需要目标,或当前目标不再适用,或话题卡住时选择。注意私聊的环境是灵活的,有可能需要经常选择 @@ -53,6 +54,7 @@ PROMPT_FOLLOW_UP = """{persona_text}。现在你在参与一场QQ私聊,刚刚 【当前对话目标】 {goals_str} +{knowledge_info_str} 【最近行动历史概要】 {action_history_summary} @@ -224,6 +226,40 @@ class ActionPlanner: 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: + # 最多只显示最近的 3 条知识,防止 Prompt 过长 + recent_knowledge = conversation_info.knowledge_list[-3:] + 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', '未知来源') + # 只取知识内容的前 150 个字,避免太长 + knowledge_snippet = knowledge[:150] + "..." if len(knowledge) > 150 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) chat_history_text = "" try: @@ -349,6 +385,7 @@ class ActionPlanner: 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------") diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index 9b497ef2..f813e140 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -17,6 +17,9 @@ logger = get_module_logger("reply_generator") PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请根据以下信息生成一条回复: 当前对话目标:{goals_str} + +{knowledge_info_str} + 最近的聊天记录: {chat_history_text} @@ -25,7 +28,7 @@ PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请 1. 符合对话目标,以"你"的角度发言(不要自己与自己对话!) 2. 符合你的性格特征和身份细节 3. 通俗易懂,自然流畅,像正常聊天一样,简短(通常20字以内,除非特殊情况) -4. 适当利用相关知识,但不要生硬引用 +4. 可以适当利用相关知识,但不要生硬引用 5. 自然、得体,结合聊天记录逻辑合理,且没有重复表达同质内容 请注意把握聊天内容,不要回复的太有条理,可以有个性。请分清"你"和对方说的话,不要把"你"说的话当做对方说的话,这是你自己说的话。 @@ -39,6 +42,9 @@ PROMPT_DIRECT_REPLY = """{persona_text}。现在你在参与一场QQ私聊,请 PROMPT_SEND_NEW_MESSAGE = """{persona_text}。现在你在参与一场QQ私聊,**刚刚你已经发送了一条或多条消息**,现在请根据以下信息再发一条新消息: 当前对话目标:{goals_str} + +{knowledge_info_str} + 最近的聊天记录: {chat_history_text} @@ -47,7 +53,7 @@ PROMPT_SEND_NEW_MESSAGE = """{persona_text}。现在你在参与一场QQ私聊 1. 符合对话目标,以"你"的角度发言(不要自己与自己对话!) 2. 符合你的性格特征和身份细节 3. 通俗易懂,自然流畅,像正常聊天一样,简短(通常20字以内,除非特殊情况) -4. 适当利用相关知识,但不要生硬引用 +4. 可以适当利用相关知识,但不要生硬引用 5. 跟之前你发的消息自然的衔接,逻辑合理,且没有重复表达同质内容或部分重叠内容 请注意把握聊天内容,不用太有条理,可以有个性。请分清"你"和对方说的话,不要把"你"说的话当做对方说的话,这是你自己说的话。 @@ -131,6 +137,36 @@ class ReplyGenerator: else: goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况 + # --- 新增:构建知识信息字符串 --- + knowledge_info_str = "【供参考的相关知识和记忆】\n" # 稍微改下标题,表明是供参考 + try: + # 检查 conversation_info 是否有 knowledge_list 并且不为空 + if hasattr(conversation_info, "knowledge_list") and conversation_info.knowledge_list: + # 最多只显示最近的 3 条知识 + recent_knowledge = conversation_info.knowledge_list[-3:] + 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', '未知来源') + # 只取知识内容的前 150 个字 + knowledge_snippet = knowledge[:150] + "..." if len(knowledge) > 150 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: @@ -162,7 +198,10 @@ class ReplyGenerator: # --- 格式化最终的 Prompt --- prompt = prompt_template.format( - persona_text=persona_text, goals_str=goals_str, chat_history_text=chat_history_text + persona_text=persona_text, + goals_str=goals_str, + chat_history_text=chat_history_text, + knowledge_info_str=knowledge_info_str ) # --- 调用 LLM 生成 --- From 837b0b36889976e3a55689024fd2ee9c1eefc226 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Apr 2025 06:49:22 +0000 Subject: [PATCH 02/20] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 19 ++++++++++--------- src/plugins/PFC/reply_generator.py | 24 +++++++++++++----------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index dd7355fa..036d4780 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -235,18 +235,20 @@ class ActionPlanner: recent_knowledge = conversation_info.knowledge_list[-3:] 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', '未知来源') + query = knowledge_item.get("query", "未知查询") + knowledge = knowledge_item.get("knowledge", "无知识内容") + source = knowledge_item.get("source", "未知来源") # 只取知识内容的前 150 个字,避免太长 knowledge_snippet = knowledge[:150] + "..." if len(knowledge) > 150 else knowledge - knowledge_info_str += f"{i+1}. 关于 '{query}' 的知识 (来源: {source}):\n {knowledge_snippet}\n" + knowledge_info_str += ( + f"{i + 1}. 关于 '{query}' 的知识 (来源: {source}):\n {knowledge_snippet}\n" + ) else: # 处理列表里不是字典的异常情况 - knowledge_info_str += f"{i+1}. 发现一条格式不正确的知识记录。\n" + knowledge_info_str += f"{i + 1}. 发现一条格式不正确的知识记录。\n" - if not recent_knowledge: # 如果 knowledge_list 存在但为空 - knowledge_info_str += "- 暂无相关知识和记忆。\n" + if not recent_knowledge: # 如果 knowledge_list 存在但为空 + knowledge_info_str += "- 暂无相关知识和记忆。\n" else: # 如果 conversation_info 没有 knowledge_list 属性,或者列表为空 @@ -259,7 +261,6 @@ class ActionPlanner: knowledge_info_str += "- 处理知识列表时出错。\n" # --- 知识信息字符串构建结束 --- - # 获取聊天历史记录 (chat_history_text) chat_history_text = "" try: @@ -385,7 +386,7 @@ class ActionPlanner: 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 + knowledge_info_str=knowledge_info_str, ) logger.debug(f"[私聊][{self.private_name}]发送到LLM的最终提示词:\n------\n{prompt}\n------") diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index f813e140..134b2740 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -137,8 +137,8 @@ class ReplyGenerator: else: goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况 - # --- 新增:构建知识信息字符串 --- - knowledge_info_str = "【供参考的相关知识和记忆】\n" # 稍微改下标题,表明是供参考 + # --- 新增:构建知识信息字符串 --- + knowledge_info_str = "【供参考的相关知识和记忆】\n" # 稍微改下标题,表明是供参考 try: # 检查 conversation_info 是否有 knowledge_list 并且不为空 if hasattr(conversation_info, "knowledge_list") and conversation_info.knowledge_list: @@ -146,17 +146,19 @@ class ReplyGenerator: recent_knowledge = conversation_info.knowledge_list[-3:] 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', '未知来源') + query = knowledge_item.get("query", "未知查询") + knowledge = knowledge_item.get("knowledge", "无知识内容") + source = knowledge_item.get("source", "未知来源") # 只取知识内容的前 150 个字 knowledge_snippet = knowledge[:150] + "..." if len(knowledge) > 150 else knowledge - knowledge_info_str += f"{i+1}. 关于 '{query}' (来源: {source}): {knowledge_snippet}\n" # 格式微调,更简洁 + knowledge_info_str += ( + f"{i + 1}. 关于 '{query}' (来源: {source}): {knowledge_snippet}\n" # 格式微调,更简洁 + ) else: - knowledge_info_str += f"{i+1}. 发现一条格式不正确的知识记录。\n" + knowledge_info_str += f"{i + 1}. 发现一条格式不正确的知识记录。\n" if not recent_knowledge: - knowledge_info_str += "- 暂无。\n" # 更简洁的提示 + knowledge_info_str += "- 暂无。\n" # 更简洁的提示 else: knowledge_info_str += "- 暂无。\n" @@ -165,7 +167,7 @@ class ReplyGenerator: knowledge_info_str += "- 获取知识列表时出错。\n" except Exception as e: logger.error(f"[私聊][{self.private_name}]构建知识信息字符串时出错: {e}") - knowledge_info_str += "- 处理知识列表时出错。\n" + knowledge_info_str += "- 处理知识列表时出错。\n" # 获取聊天历史记录 (chat_history_text) chat_history_text = observation_info.chat_history_str @@ -199,9 +201,9 @@ class ReplyGenerator: # --- 格式化最终的 Prompt --- prompt = prompt_template.format( persona_text=persona_text, - goals_str=goals_str, + goals_str=goals_str, chat_history_text=chat_history_text, - knowledge_info_str=knowledge_info_str + knowledge_info_str=knowledge_info_str, ) # --- 调用 LLM 生成 --- From d09965571f432f50a4c907c33aaee0538649e930 Mon Sep 17 00:00:00 2001 From: 114514 <2514624910@qq.com> Date: Wed, 30 Apr 2025 15:06:01 +0800 Subject: [PATCH 03/20] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AD=97=E6=95=B0?= =?UTF-8?q?=E4=B8=8A=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index dd7355fa..ec5eb8a8 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -238,8 +238,8 @@ class ActionPlanner: query = knowledge_item.get('query', '未知查询') knowledge = knowledge_item.get('knowledge', '无知识内容') source = knowledge_item.get('source', '未知来源') - # 只取知识内容的前 150 个字,避免太长 - knowledge_snippet = knowledge[:150] + "..." if len(knowledge) > 150 else knowledge + # 只取知识内容的前 2000 个字,避免太长 + knowledge_snippet = knowledge[:2000] + "..." if len(knowledge) > 200 else knowledge knowledge_info_str += f"{i+1}. 关于 '{query}' 的知识 (来源: {source}):\n {knowledge_snippet}\n" else: # 处理列表里不是字典的异常情况 From 7ef65cadc24e1d7c94ae65ea2672094a0b95e4b0 Mon Sep 17 00:00:00 2001 From: Bakadax Date: Wed, 30 Apr 2025 15:07:34 +0800 Subject: [PATCH 04/20] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A9=BA=E6=9D=A5?= =?UTF-8?q?=E6=BA=90=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/pfc_KnowledgeFetcher.py | 3 ++- src/plugins/PFC/reply_generator.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/PFC/pfc_KnowledgeFetcher.py b/src/plugins/PFC/pfc_KnowledgeFetcher.py index 7453aa0e..171ba705 100644 --- a/src/plugins/PFC/pfc_KnowledgeFetcher.py +++ b/src/plugins/PFC/pfc_KnowledgeFetcher.py @@ -69,8 +69,9 @@ class KnowledgeFetcher: fast_retrieval=False, ) knowledge_text = "" - sources = [] + sources_text = "无记忆匹配" # 默认值 if related_memory: + sources = [] for memory in related_memory: knowledge_text += memory[1] + "\n" sources.append(f"记忆片段{memory[0]}") diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index f813e140..3de22fcf 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -137,7 +137,7 @@ class ReplyGenerator: else: goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况 - # --- 新增:构建知识信息字符串 --- + # --- 新增:构建知识信息字符串 --- knowledge_info_str = "【供参考的相关知识和记忆】\n" # 稍微改下标题,表明是供参考 try: # 检查 conversation_info 是否有 knowledge_list 并且不为空 @@ -156,7 +156,7 @@ class ReplyGenerator: knowledge_info_str += f"{i+1}. 发现一条格式不正确的知识记录。\n" if not recent_knowledge: - knowledge_info_str += "- 暂无。\n" # 更简洁的提示 + knowledge_info_str += "- 暂无。\n" # 更简洁的提示 else: knowledge_info_str += "- 暂无。\n" From 94dd06561e88d7d5ffbf60bb44ac7785cf16cbec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Apr 2025 07:08:58 +0000 Subject: [PATCH 05/20] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/action_planner.py | 10 ++++++---- src/plugins/PFC/reply_generator.py | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index 7082e281..78f41936 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -235,12 +235,14 @@ class ActionPlanner: recent_knowledge = conversation_info.knowledge_list[-3:] 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', '未知来源') + query = knowledge_item.get("query", "未知查询") + knowledge = knowledge_item.get("knowledge", "无知识内容") + source = knowledge_item.get("source", "未知来源") # 只取知识内容的前 2000 个字,避免太长 knowledge_snippet = knowledge[:2000] + "..." if len(knowledge) > 200 else knowledge - knowledge_info_str += f"{i+1}. 关于 '{query}' 的知识 (来源: {source}):\n {knowledge_snippet}\n" + knowledge_info_str += ( + f"{i + 1}. 关于 '{query}' 的知识 (来源: {source}):\n {knowledge_snippet}\n" + ) else: # 处理列表里不是字典的异常情况 knowledge_info_str += f"{i + 1}. 发现一条格式不正确的知识记录。\n" diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index f5e7c1e6..134b2740 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -138,7 +138,7 @@ class ReplyGenerator: goals_str = "- 目前没有明确对话目标\n" # 简化无目标情况 # --- 新增:构建知识信息字符串 --- - knowledge_info_str = "【供参考的相关知识和记忆】\n" # 稍微改下标题,表明是供参考 + knowledge_info_str = "【供参考的相关知识和记忆】\n" # 稍微改下标题,表明是供参考 try: # 检查 conversation_info 是否有 knowledge_list 并且不为空 if hasattr(conversation_info, "knowledge_list") and conversation_info.knowledge_list: @@ -158,7 +158,7 @@ class ReplyGenerator: knowledge_info_str += f"{i + 1}. 发现一条格式不正确的知识记录。\n" if not recent_knowledge: - knowledge_info_str += "- 暂无。\n" # 更简洁的提示 + knowledge_info_str += "- 暂无。\n" # 更简洁的提示 else: knowledge_info_str += "- 暂无。\n" From 03a9f50128b458130de0804a13afc3c47062dd04 Mon Sep 17 00:00:00 2001 From: 114514 <2514624910@qq.com> Date: Wed, 30 Apr 2025 15:10:26 +0800 Subject: [PATCH 06/20] 1 --- src/plugins/PFC/action_planner.py | 6 +++--- src/plugins/PFC/reply_generator.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/PFC/action_planner.py b/src/plugins/PFC/action_planner.py index ec5eb8a8..63677b04 100644 --- a/src/plugins/PFC/action_planner.py +++ b/src/plugins/PFC/action_planner.py @@ -231,15 +231,15 @@ class ActionPlanner: try: # 检查 conversation_info 是否有 knowledge_list 并且不为空 if hasattr(conversation_info, "knowledge_list") and conversation_info.knowledge_list: - # 最多只显示最近的 3 条知识,防止 Prompt 过长 - recent_knowledge = conversation_info.knowledge_list[-3:] + # 最多只显示最近的 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) > 200 else knowledge + knowledge_snippet = knowledge[:2000] + "..." if len(knowledge) > 2000 else knowledge knowledge_info_str += f"{i+1}. 关于 '{query}' 的知识 (来源: {source}):\n {knowledge_snippet}\n" else: # 处理列表里不是字典的异常情况 diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index f813e140..438a21ac 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -142,15 +142,15 @@ class ReplyGenerator: try: # 检查 conversation_info 是否有 knowledge_list 并且不为空 if hasattr(conversation_info, "knowledge_list") and conversation_info.knowledge_list: - # 最多只显示最近的 3 条知识 - recent_knowledge = conversation_info.knowledge_list[-3:] + # 最多只显示最近的 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', '未知来源') # 只取知识内容的前 150 个字 - knowledge_snippet = knowledge[:150] + "..." if len(knowledge) > 150 else knowledge + 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" From 0caaec69c19b6f98cc8747e18427056427fbcea5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Apr 2025 08:20:16 +0000 Subject: [PATCH 07/20] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/PFC/reply_generator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/PFC/reply_generator.py b/src/plugins/PFC/reply_generator.py index b577872f..890f807c 100644 --- a/src/plugins/PFC/reply_generator.py +++ b/src/plugins/PFC/reply_generator.py @@ -151,7 +151,9 @@ class ReplyGenerator: 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" # 格式微调,更简洁 + knowledge_info_str += ( + f"{i + 1}. 关于 '{query}' (来源: {source}): {knowledge_snippet}\n" # 格式微调,更简洁 + ) else: knowledge_info_str += f"{i + 1}. 发现一条格式不正确的知识记录。\n" From b8736e4299b0d1a39138aa252dc0503cdef05c55 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 30 Apr 2025 17:18:14 +0800 Subject: [PATCH 08/20] =?UTF-8?q?better=EF=BC=9A=E6=95=B4=E7=90=86config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelogs/changelog_config.md | 2 +- scripts/interest_monitor_gui.py | 3 - src/api/config_api.py | 2 +- src/config/config.py | 180 +++++----- src/do_tool/tool_use.py | 1 - src/heart_flow/0.6.3TODO.md | 48 --- src/heart_flow/README.md | 12 +- src/heart_flow/background_tasks.py | 107 +++--- src/heart_flow/heartflow.py | 14 - src/heart_flow/mai_state_manager.py | 28 +- src/heart_flow/observation.py | 2 +- src/heart_flow/sub_heartflow.py | 45 +-- src/heart_flow/sub_mind.py | 4 +- src/heart_flow/subheartflow_manager.py | 321 +++++++++++------- src/individuality/individuality.py | 2 +- src/plugins/chat/bot.py | 7 + src/plugins/chat/utils.py | 3 + src/plugins/heartFC_chat/heartFC_chat.py | 30 +- .../heartFC_chat/heartflow_prompt_builder.py | 8 +- src/plugins/heartFC_chat/normal_chat.py | 4 + src/plugins/models/utils_model.py | 2 +- .../person_info/relationship_manager.py | 4 +- .../respon_info_catcher/info_catcher.py | 63 ++-- template/bot_config_template.toml | 54 +-- 24 files changed, 484 insertions(+), 462 deletions(-) delete mode 100644 src/heart_flow/0.6.3TODO.md diff --git a/changelogs/changelog_config.md b/changelogs/changelog_config.md index e438ea31..5aa5fb92 100644 --- a/changelogs/changelog_config.md +++ b/changelogs/changelog_config.md @@ -33,7 +33,7 @@ - 调整了部分配置项的默认值 - 调整了配置项的顺序,将 `groups` 配置项移到了更靠前的位置 - 在 `message` 配置项中: - - 新增了 `max_response_length` 参数 + - 新增了 `model_max_output_length` 参数 - 在 `willing` 配置项中新增了 `emoji_response_penalty` 参数 - 将 `personality` 配置项中的 `prompt_schedule` 重命名为 `prompt_schedule_gen` diff --git a/scripts/interest_monitor_gui.py b/scripts/interest_monitor_gui.py index adb83f72..1f03b969 100644 --- a/scripts/interest_monitor_gui.py +++ b/scripts/interest_monitor_gui.py @@ -344,9 +344,6 @@ class InterestMonitorApp: self.stream_last_active[stream_id] = subflow_entry.get( "chat_state_changed_time" ) # 存储原始时间戳 - self.stream_last_interaction[stream_id] = subflow_entry.get( - "last_interaction_time" - ) # 存储原始时间戳 # 添加数据点 (使用顶层时间戳) new_stream_history[stream_id].append((entry_timestamp, interest_level_float)) diff --git a/src/api/config_api.py b/src/api/config_api.py index e3934617..fbc31433 100644 --- a/src/api/config_api.py +++ b/src/api/config_api.py @@ -47,7 +47,7 @@ class BotConfig: MAX_CONTEXT_SIZE: int # 上下文最大消息数 emoji_chance: float # 发送表情包的基础概率 thinking_timeout: int # 思考时间 - max_response_length: int # 最大回复长度 + model_max_output_length: int # 最大回复长度 message_buffer: bool # 消息缓冲器 ban_words: set diff --git a/src/config/config.py b/src/config/config.py index f09da9a7..f7e57542 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -20,9 +20,9 @@ from src.common.logger_manager import get_logger logger = get_logger("config") # 考虑到,实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码 -is_test = True +is_test = False mai_version_main = "0.6.3" -mai_version_fix = "snapshot-5" +mai_version_fix = "" if mai_version_fix: if is_test: @@ -170,32 +170,38 @@ class BotConfig: SCHEDULE_TEMPERATURE: float = 0.5 # 日程表温度,建议0.5-1.0 TIME_ZONE: str = "Asia/Shanghai" # 时区 - # message - MAX_CONTEXT_SIZE: int = 15 # 上下文最大消息数 - emoji_chance: float = 0.2 # 发送表情包的基础概率 - thinking_timeout: int = 120 # 思考时间 - max_response_length: int = 1024 # 最大回复长度 + + # chat + allow_focus_mode: bool = True # 是否允许专注聊天状态 + + base_normal_chat_num: int = 3 # 最多允许多少个群进行普通聊天 + base_focused_chat_num: int = 2 # 最多允许多少个群进行专注聊天 + + observation_context_size: int = 12 # 心流观察到的最长上下文大小,超过这个值的上下文会被压缩 + message_buffer: bool = True # 消息缓冲器 + ban_words = set() ban_msgs_regex = set() - # [heartflow] # 启用启用heart_flowC(心流聊天)模式时生效, 需要填写token消耗量巨大的相关模型 - # 启用后麦麦会自主选择进入heart_flowC模式(持续一段时间), 进行长时间高质量的聊天 + # focus_chat reply_trigger_threshold: float = 3.0 # 心流聊天触发阈值,越低越容易触发 - probability_decay_factor_per_second: float = 0.2 # 概率衰减因子,越大衰减越快 default_decay_rate_per_second: float = 0.98 # 默认衰减率,越大衰减越慢 - allow_focus_mode: bool = True # 是否允许子心流进入 FOCUSED 状态 - - # sub_heart_flow_update_interval: int = 60 # 子心流更新频率,间隔 单位秒 - # sub_heart_flow_freeze_time: int = 120 # 子心流冻结时间,超过这个时间没有回复,子心流会冻结,间隔 单位秒 - sub_heart_flow_stop_time: int = 600 # 子心流停止时间,超过这个时间没有回复,子心流会停止,间隔 单位秒 - # heart_flow_update_interval: int = 300 # 心流更新频率,间隔 单位秒 - observation_context_size: int = 20 # 心流观察到的最长上下文大小,超过这个值的上下文会被压缩 + consecutive_no_reply_threshold = 3 + + compressed_length: int = 5 # 不能大于observation_context_size,心流上下文压缩的最短压缩长度,超过心流观察到的上下文长度,会压缩,最短压缩长度为5 compress_length_limit: int = 5 # 最多压缩份数,超过该数值的压缩上下文会被删除 - # willing + + # normal_chat + model_reasoning_probability: float = 0.7 # 麦麦回答时选择推理模型(主要)模型概率 + model_normal_probability: float = 0.3 # 麦麦回答时选择一般模型(次要)模型概率 + + emoji_chance: float = 0.2 # 发送表情包的基础概率 + thinking_timeout: int = 120 # 思考时间 + willing_mode: str = "classical" # 意愿模式 response_willing_amplifier: float = 1.0 # 回复意愿放大系数 response_interested_rate_amplifier: float = 1.0 # 回复兴趣度放大系数 @@ -204,12 +210,6 @@ class BotConfig: mentioned_bot_inevitable_reply: bool = False # 提及 bot 必然回复 at_bot_inevitable_reply: bool = False # @bot 必然回复 - # response - response_mode: str = "heart_flow" # 回复策略 - model_reasoning_probability: float = 0.7 # 麦麦回答时选择推理模型(主要)模型概率 - model_normal_probability: float = 0.3 # 麦麦回答时选择一般模型(次要)模型概率 - # MODEL_R1_DISTILL_PROBABILITY: float = 0.1 # R1蒸馏模型概率 - # emoji max_emoji_num: int = 200 # 表情包最大数量 max_reach_deletion: bool = True # 开启则在达到最大数量时删除表情包,关闭则不会继续收集表情包 @@ -263,6 +263,8 @@ class BotConfig: enable_response_splitter = True # 是否启用回复分割器 response_max_length = 100 # 回复允许的最大长度 response_max_sentence_num = 3 # 回复允许的最大句子数 + + model_max_output_length: int = 800 # 最大回复长度 # remote remote_enable: bool = True # 是否启用远程控制 @@ -408,64 +410,67 @@ class BotConfig: config.BOT_QQ = str(bot_qq) config.BOT_NICKNAME = bot_config.get("nickname", config.BOT_NICKNAME) config.BOT_ALIAS_NAMES = bot_config.get("alias_names", config.BOT_ALIAS_NAMES) + + def chat(parent: dict): + chat_config = parent["chat"] + config.allow_focus_mode = chat_config.get("allow_focus_mode", config.allow_focus_mode) + config.base_normal_chat_num = chat_config.get("base_normal_chat_num", config.base_normal_chat_num) + config.base_focused_chat_num = chat_config.get("base_focused_chat_num", config.base_focused_chat_num) + config.observation_context_size = chat_config.get( + "observation_context_size", config.observation_context_size + ) + config.message_buffer = chat_config.get("message_buffer", config.message_buffer) + config.ban_words = chat_config.get("ban_words", config.ban_words) + for r in chat_config.get("ban_msgs_regex", config.ban_msgs_regex): + config.ban_msgs_regex.add(re.compile(r)) - def response(parent: dict): - response_config = parent["response"] - config.model_reasoning_probability = response_config.get( + def normal_chat(parent: dict): + normal_chat_config = parent["normal_chat"] + config.model_reasoning_probability = normal_chat_config.get( "model_reasoning_probability", config.model_reasoning_probability ) - config.model_normal_probability = response_config.get( + config.model_normal_probability = normal_chat_config.get( "model_normal_probability", config.model_normal_probability ) + config.emoji_chance = normal_chat_config.get("emoji_chance", config.emoji_chance) + config.thinking_timeout = normal_chat_config.get("thinking_timeout", config.thinking_timeout) + - def heartflow(parent: dict): - heartflow_config = parent["heartflow"] - config.sub_heart_flow_stop_time = heartflow_config.get( - "sub_heart_flow_stop_time", config.sub_heart_flow_stop_time + config.willing_mode = normal_chat_config.get("willing_mode", config.willing_mode) + config.response_willing_amplifier = normal_chat_config.get( + "response_willing_amplifier", config.response_willing_amplifier + ) + config.response_interested_rate_amplifier = normal_chat_config.get( + "response_interested_rate_amplifier", config.response_interested_rate_amplifier + ) + config.down_frequency_rate = normal_chat_config.get("down_frequency_rate", config.down_frequency_rate) + config.emoji_response_penalty = normal_chat_config.get( + "emoji_response_penalty", config.emoji_response_penalty ) - if config.INNER_VERSION in SpecifierSet(">=1.3.0"): - config.observation_context_size = heartflow_config.get( - "observation_context_size", config.observation_context_size - ) - config.compressed_length = heartflow_config.get("compressed_length", config.compressed_length) - config.compress_length_limit = heartflow_config.get( - "compress_length_limit", config.compress_length_limit - ) - if config.INNER_VERSION in SpecifierSet(">=1.4.0"): - config.reply_trigger_threshold = heartflow_config.get( - "reply_trigger_threshold", config.reply_trigger_threshold - ) - config.probability_decay_factor_per_second = heartflow_config.get( - "probability_decay_factor_per_second", config.probability_decay_factor_per_second - ) - config.default_decay_rate_per_second = heartflow_config.get( - "default_decay_rate_per_second", config.default_decay_rate_per_second - ) - if config.INNER_VERSION in SpecifierSet(">=1.5.1"): - config.allow_focus_mode = heartflow_config.get("allow_focus_mode", config.allow_focus_mode) - def willing(parent: dict): - willing_config = parent["willing"] - config.willing_mode = willing_config.get("willing_mode", config.willing_mode) + config.mentioned_bot_inevitable_reply = normal_chat_config.get( + "mentioned_bot_inevitable_reply", config.mentioned_bot_inevitable_reply + ) + config.at_bot_inevitable_reply = normal_chat_config.get( + "at_bot_inevitable_reply", config.at_bot_inevitable_reply + ) - if config.INNER_VERSION in SpecifierSet(">=0.0.11"): - config.response_willing_amplifier = willing_config.get( - "response_willing_amplifier", config.response_willing_amplifier - ) - config.response_interested_rate_amplifier = willing_config.get( - "response_interested_rate_amplifier", config.response_interested_rate_amplifier - ) - config.down_frequency_rate = willing_config.get("down_frequency_rate", config.down_frequency_rate) - config.emoji_response_penalty = willing_config.get( - "emoji_response_penalty", config.emoji_response_penalty - ) - if config.INNER_VERSION in SpecifierSet(">=1.2.5"): - config.mentioned_bot_inevitable_reply = willing_config.get( - "mentioned_bot_inevitable_reply", config.mentioned_bot_inevitable_reply - ) - config.at_bot_inevitable_reply = willing_config.get( - "at_bot_inevitable_reply", config.at_bot_inevitable_reply - ) + def focus_chat(parent: dict): + focus_chat_config = parent["focus_chat"] + config.compressed_length = focus_chat_config.get("compressed_length", config.compressed_length) + config.compress_length_limit = focus_chat_config.get( + "compress_length_limit", config.compress_length_limit + ) + config.reply_trigger_threshold = focus_chat_config.get( + "reply_trigger_threshold", config.reply_trigger_threshold + ) + config.default_decay_rate_per_second = focus_chat_config.get( + "default_decay_rate_per_second", config.default_decay_rate_per_second + ) + config.consecutive_no_reply_threshold = focus_chat_config.get( + "consecutive_no_reply_threshold", config.consecutive_no_reply_threshold + ) + def model(parent: dict): # 加载模型配置 @@ -556,26 +561,6 @@ class BotConfig: logger.error(f"模型 {item} 在config中不存在,请检查,或尝试更新配置文件") raise KeyError(f"模型 {item} 在config中不存在,请检查,或尝试更新配置文件") - def message(parent: dict): - msg_config = parent["message"] - config.MAX_CONTEXT_SIZE = msg_config.get("max_context_size", config.MAX_CONTEXT_SIZE) - config.emoji_chance = msg_config.get("emoji_chance", config.emoji_chance) - config.ban_words = msg_config.get("ban_words", config.ban_words) - config.thinking_timeout = msg_config.get("thinking_timeout", config.thinking_timeout) - config.response_willing_amplifier = msg_config.get( - "response_willing_amplifier", config.response_willing_amplifier - ) - config.response_interested_rate_amplifier = msg_config.get( - "response_interested_rate_amplifier", config.response_interested_rate_amplifier - ) - config.down_frequency_rate = msg_config.get("down_frequency_rate", config.down_frequency_rate) - for r in msg_config.get("ban_msgs_regex", config.ban_msgs_regex): - config.ban_msgs_regex.add(re.compile(r)) - if config.INNER_VERSION in SpecifierSet(">=0.0.11"): - config.max_response_length = msg_config.get("max_response_length", config.max_response_length) - if config.INNER_VERSION in SpecifierSet(">=1.1.4"): - config.message_buffer = msg_config.get("message_buffer", config.message_buffer) - def memory(parent: dict): memory_config = parent["memory"] config.build_memory_interval = memory_config.get("build_memory_interval", config.build_memory_interval) @@ -650,6 +635,8 @@ class BotConfig: config.enable_kaomoji_protection = response_splitter_config.get( "enable_kaomoji_protection", config.enable_kaomoji_protection ) + if config.INNER_VERSION in SpecifierSet(">=1.6.0"): + config.model_max_output_length = response_splitter_config.get("model_max_output_length", config.model_max_output_length) def groups(parent: dict): groups_config = parent["groups"] @@ -695,10 +682,7 @@ class BotConfig: "personality": {"func": personality, "support": ">=0.0.0"}, "identity": {"func": identity, "support": ">=1.2.4"}, "schedule": {"func": schedule, "support": ">=0.0.11", "necessary": False}, - "message": {"func": message, "support": ">=0.0.0"}, - "willing": {"func": willing, "support": ">=0.0.9", "necessary": False}, "emoji": {"func": emoji, "support": ">=0.0.0"}, - "response": {"func": response, "support": ">=0.0.0"}, "model": {"func": model, "support": ">=0.0.0"}, "memory": {"func": memory, "support": ">=0.0.0", "necessary": False}, "mood": {"func": mood, "support": ">=0.0.0"}, @@ -708,7 +692,9 @@ class BotConfig: "platforms": {"func": platforms, "support": ">=1.0.0"}, "response_splitter": {"func": response_splitter, "support": ">=0.0.11", "necessary": False}, "experimental": {"func": experimental, "support": ">=0.0.11", "necessary": False}, - "heartflow": {"func": heartflow, "support": ">=1.0.2", "necessary": False}, + "chat": {"func": chat, "support": ">=1.6.0", "necessary": False}, + "normal_chat": {"func": normal_chat, "support": ">=1.6.0", "necessary": False}, + "focus_chat": {"func": focus_chat, "support": ">=1.6.0", "necessary": False}, } # 原地修改,将 字符串版本表达式 转换成 版本对象 diff --git a/src/do_tool/tool_use.py b/src/do_tool/tool_use.py index 5b886cb4..88289fe0 100644 --- a/src/do_tool/tool_use.py +++ b/src/do_tool/tool_use.py @@ -129,7 +129,6 @@ class ToolUser: payload = { "model": self.llm_model_tool.model_name, "messages": [{"role": "user", "content": prompt}], - "max_tokens": global_config.max_response_length, "tools": tools, "temperature": 0.2, } diff --git a/src/heart_flow/0.6.3TODO.md b/src/heart_flow/0.6.3TODO.md deleted file mode 100644 index 5e9e96ab..00000000 --- a/src/heart_flow/0.6.3TODO.md +++ /dev/null @@ -1,48 +0,0 @@ -# 0.6.3 版本发布前待办事项 - -- [0.6.3]**统一化人格配置:** - - 检查代码中是否存在硬编码的人格相关配置。 - - 将所有硬编码的人格配置替换为使用 `individual` 模块进行管理。 - -- [0.6.3]**在 Planner 中添加回复计数信息:** - - 修改 `HeartFlowChatInstance` 的 `Plan` 阶段逻辑。 - - 将当前周期的回复计数(或其他相关统计信息)作为输入提供给 Planner。 - - 目的是为 Planner 提供负反馈,减少连续回复或不当回复的可能性。 - -- [0.6.3]**恢复/检查被停止的功能:** - - 全面审查代码,特别是对比之前的版本或设计文档。 - - 识别并重新启用那些暂时被禁用但应该恢复的功能。 - - 确认没有核心功能意外丢失。 - -- [0.6.3]**参数提取与配置化:** - - 识别代码中散落的各种可调参数(例如:概率阈值、时间间隔、次数限制、LLM 模型名称等)。 - - 将这些参数统一提取到模块或类的顶部。 - - 最终将这些参数移至外部配置文件(如 YAML 或 JSON 文件),方便用户自定义。 - -- **[0.6.3]提供 HFC (HeartFlowChatInstance) 开启/关闭选项:** - - 增加一个全局或针对特定子心流的配置选项。 - - 允许用户控制是否启用 `FOCUSED` 状态以及关联的 `HeartFlowChatInstance`。 - - 如果禁用 HFC,子心流可能只会在 `ABSENT` 和 `CHAT` 状态间切换。 - -- [0.6.3]**添加防破线机制 (针对接收消息):** - - 在消息处理流程的早期阶段 (例如 `HeartHC_processor` 或类似模块),增加对接收到的消息文本长度的检查。 - - 对超过预设长度阈值的*接收*消息进行截断处理。 - - 目的是防止过长的输入(可能包含"破限"提示词)影响后续的兴趣计算、LLM 回复生成等环节。 - -- [0.6.3]**NormalChat 模式下的记忆与 Prompt 优化:** - - 重点审视 `NormalChatInstance` (闲聊/推理模式) 中记忆调用 (例如 `HippocampusManager` 的使用) 的方式。 - - 评估在该模式下引入工具调用 (Tool Calling) 机制以更结构化访问记忆的必要性。 - - 优化 `NormalChatInstance` 中与记忆检索、应用相关的 Prompt。 - -- [0.6.3]**完善简易兴趣监控 GUI:** - - 改进现有的、用于监控聊天兴趣度 (`InterestChatting`?) 的简单 GUI 界面。 - - 使其能更清晰地展示关键参数和状态,作为查看日志之外的更直观的监控方式。 - - 作为完整外部 UI 开发完成前的临时替代方案。 - -- [0.6.3]**修复/完善中期记忆 (Midterm Memory):** - - 检查当前中期记忆模块的状态。 - - 修复已知问题,使其能够稳定运行。 - - (优先级视开发时间而定) - - -对于有些群频繁激活HFC,却不回复,需要处理一下 diff --git a/src/heart_flow/README.md b/src/heart_flow/README.md index fdb3a192..a55f1c97 100644 --- a/src/heart_flow/README.md +++ b/src/heart_flow/README.md @@ -106,8 +106,8 @@ c HeartFChatting工作方式 - 负责所有 `SubHeartflow` 实例的生命周期管理,包括: - 创建和获取 (`get_or_create_subheartflow`)。 - 停止和清理 (`sleep_subheartflow`, `cleanup_inactive_subheartflows`)。 - - 根据 `Heartflow` 的状态 (`self.mai_state_info`) 和限制条件,激活、停用或调整子心流的状态(例如 `enforce_subheartflow_limits`, `randomly_deactivate_subflows`, `evaluate_interest_and_promote`)。 - - **新增**: 通过调用 `evaluate_and_transition_subflows_by_llm` 方法,使用 LLM (配置与 `Heartflow` 主 LLM 相同) 评估处于 `ABSENT` 或 `CHAT` 状态的子心流,根据观察到的活动摘要和 `Heartflow` 的当前状态,判断是否应在 `ABSENT` 和 `CHAT` 之间进行转换 (同样受限于 `CHAT` 状态的数量上限)。 + - 根据 `Heartflow` 的状态 (`self.mai_state_info`) 和限制条件,激活、停用或调整子心流的状态(例如 `enforce_subheartflow_limits`, `randomly_deactivate_subflows`, `sbhf_absent_into_focus`)。 + - **新增**: 通过调用 `sbhf_absent_into_chat` 方法,使用 LLM (配置与 `Heartflow` 主 LLM 相同) 评估处于 `ABSENT` 或 `CHAT` 状态的子心流,根据观察到的活动摘要和 `Heartflow` 的当前状态,判断是否应在 `ABSENT` 和 `CHAT` 之间进行转换 (同样受限于 `CHAT` 状态的数量上限)。 - **清理机制**: 通过后台任务 (`BackgroundTaskManager`) 定期调用 `cleanup_inactive_subheartflows` 方法,此方法会识别并**删除**那些处于 `ABSENT` 状态超过一小时 (`INACTIVE_THRESHOLD_SECONDS`) 的子心流实例。 ### 1.5. 消息处理与回复流程 (Message Processing vs. Replying Flow) @@ -155,20 +155,20 @@ c HeartFChatting工作方式 - **初始状态**: 新创建的 `SubHeartflow` 默认为 `ABSENT` 状态。 - **`ABSENT` -> `CHAT` (激活闲聊)**: - **触发条件**: `Heartflow` 的主状态 (`MaiState`) 允许 `CHAT` 模式,且当前 `CHAT` 状态的子心流数量未达上限。 - - **判定机制**: `SubHeartflowManager` 中的 `evaluate_and_transition_subflows_by_llm` 方法调用大模型(LLM)。LLM 读取该群聊的近期内容和结合自身个性信息,判断是否"想"在该群开始聊天。 + - **判定机制**: `SubHeartflowManager` 中的 `sbhf_absent_into_chat` 方法调用大模型(LLM)。LLM 读取该群聊的近期内容和结合自身个性信息,判断是否"想"在该群开始聊天。 - **执行**: 若 LLM 判断为是,且名额未满,`SubHeartflowManager` 调用 `change_chat_state(ChatState.CHAT)`。 - **`CHAT` -> `FOCUSED` (激活专注)**: - **触发条件**: 子心流处于 `CHAT` 状态,其内部维护的"开屎热聊"概率 (`InterestChatting.start_hfc_probability`) 达到预设阈值(表示对当前聊天兴趣浓厚),同时 `Heartflow` 的主状态允许 `FOCUSED` 模式,且 `FOCUSED` 名额未满。 - - **判定机制**: `SubHeartflowManager` 中的 `evaluate_interest_and_promote` 方法定期检查满足条件的 `CHAT` 子心流。 + - **判定机制**: `SubHeartflowManager` 中的 `sbhf_absent_into_focus` 方法定期检查满足条件的 `CHAT` 子心流。 - **执行**: 若满足所有条件,`SubHeartflowManager` 调用 `change_chat_state(ChatState.FOCUSED)`。 - **注意**: 无法从 `ABSENT` 直接跳到 `FOCUSED`,必须先经过 `CHAT`。 - **`FOCUSED` -> `ABSENT` (退出专注)**: - - **主要途径 (内部驱动)**: 在 `FOCUSED` 状态下运行的 `HeartFlowChatInstance` 连续多次决策为 `no_reply` (例如达到 5 次,次数可配),它会通过回调函数 (`request_absent_transition`) 请求 `SubHeartflowManager` 将其状态**直接**设置为 `ABSENT`。 + - **主要途径 (内部驱动)**: 在 `FOCUSED` 状态下运行的 `HeartFlowChatInstance` 连续多次决策为 `no_reply` (例如达到 5 次,次数可配),它会通过回调函数 (`sbhf_focus_into_absent`) 请求 `SubHeartflowManager` 将其状态**直接**设置为 `ABSENT`。 - **其他途径 (外部驱动)**: - `Heartflow` 主状态变为 `OFFLINE`,`SubHeartflowManager` 强制所有子心流变为 `ABSENT`。 - `SubHeartflowManager` 因 `FOCUSED` 名额超限 (`enforce_subheartflow_limits`) 或随机停用 (`randomly_deactivate_subflows`) 而将其设置为 `ABSENT`。 - **`CHAT` -> `ABSENT` (退出闲聊)**: - - **主要途径 (内部驱动)**: `SubHeartflowManager` 中的 `evaluate_and_transition_subflows_by_llm` 方法调用 LLM。LLM 读取群聊内容和结合自身状态,判断是否"不想"继续在此群闲聊。 + - **主要途径 (内部驱动)**: `SubHeartflowManager` 中的 `sbhf_absent_into_chat` 方法调用 LLM。LLM 读取群聊内容和结合自身状态,判断是否"不想"继续在此群闲聊。 - **执行**: 若 LLM 判断为是,`SubHeartflowManager` 调用 `change_chat_state(ChatState.ABSENT)`。 - **其他途径 (外部驱动)**: - `Heartflow` 主状态变为 `OFFLINE`。 diff --git a/src/heart_flow/background_tasks.py b/src/heart_flow/background_tasks.py index fd016308..dcce26d0 100644 --- a/src/heart_flow/background_tasks.py +++ b/src/heart_flow/background_tasks.py @@ -12,11 +12,17 @@ from src.heart_flow.interest_logger import InterestLogger logger = get_logger("background_tasks") -# 新增随机停用间隔 (5 分钟) -RANDOM_DEACTIVATION_INTERVAL_SECONDS = 300 + # 新增兴趣评估间隔 INTEREST_EVAL_INTERVAL_SECONDS = 5 +# 新增聊天超时检查间隔 +NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS = 60 +# 新增状态评估间隔 +HF_JUDGE_STATE_UPDATE_INTERVAL_SECONDS = 60 +CLEANUP_INTERVAL_SECONDS = 1200 +STATE_UPDATE_INTERVAL_SECONDS = 60 +LOG_INTERVAL_SECONDS = 3 class BackgroundTaskManager: """管理 Heartflow 的后台周期性任务。""" @@ -26,34 +32,21 @@ class BackgroundTaskManager: mai_state_info: MaiStateInfo, # Needs current state info mai_state_manager: MaiStateManager, subheartflow_manager: SubHeartflowManager, - interest_logger: InterestLogger, - update_interval: int, - cleanup_interval: int, - log_interval: int, - # 新增兴趣评估间隔参数 - interest_eval_interval: int = INTEREST_EVAL_INTERVAL_SECONDS, - # 新增随机停用间隔参数 - random_deactivation_interval: int = RANDOM_DEACTIVATION_INTERVAL_SECONDS, + interest_logger: InterestLogger ): self.mai_state_info = mai_state_info self.mai_state_manager = mai_state_manager self.subheartflow_manager = subheartflow_manager self.interest_logger = interest_logger - # Intervals - self.update_interval = update_interval - self.cleanup_interval = cleanup_interval - self.log_interval = log_interval - self.interest_eval_interval = interest_eval_interval # 存储兴趣评估间隔 - self.random_deactivation_interval = random_deactivation_interval # 存储随机停用间隔 # Task references self._state_update_task: Optional[asyncio.Task] = None self._cleanup_task: Optional[asyncio.Task] = None self._logging_task: Optional[asyncio.Task] = None - self._interest_eval_task: Optional[asyncio.Task] = None # 新增兴趣评估任务引用 - self._random_deactivation_task: Optional[asyncio.Task] = None # 新增随机停用任务引用 - self._hf_judge_state_update_task: Optional[asyncio.Task] = None # 新增状态评估任务引用 + self._normal_chat_timeout_check_task: Optional[asyncio.Task] = None # Nyaa~ 添加聊天超时检查任务的引用 + self._hf_judge_state_update_task: Optional[asyncio.Task] = None # Nyaa~ 添加状态评估任务的引用 + self._into_focus_task: Optional[asyncio.Task] = None # Nyaa~ 添加兴趣评估任务的引用 self._tasks: List[Optional[asyncio.Task]] = [] # Keep track of all tasks async def start_tasks(self): @@ -65,57 +58,53 @@ class BackgroundTaskManager: - 将任务引用保存到任务列表 """ - # 任务配置列表: (任务变量名, 任务函数, 任务名称, 日志级别, 额外日志信息, 任务对象引用属性名) + # 任务配置列表: (任务函数, 任务名称, 日志级别, 额外日志信息, 任务对象引用属性名) task_configs = [ ( - self._state_update_task, - lambda: self._run_state_update_cycle(self.update_interval), - "hf_state_update", + lambda: self._run_state_update_cycle(STATE_UPDATE_INTERVAL_SECONDS), "debug", - f"聊天状态更新任务已启动 间隔:{self.update_interval}s", + f"聊天状态更新任务已启动 间隔:{STATE_UPDATE_INTERVAL_SECONDS}s", "_state_update_task", ), ( - self._hf_judge_state_update_task, - lambda: self._run_hf_judge_state_update_cycle(60), - "hf_judge_state_update", + lambda: self._run_normal_chat_timeout_check_cycle(NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS), "debug", - f"状态评估任务已启动 间隔:{60}s", + f"聊天超时检查任务已启动 间隔:{NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS}s", + "_normal_chat_timeout_check_task", + ), + ( + lambda: self._run_absent_into_chat(HF_JUDGE_STATE_UPDATE_INTERVAL_SECONDS), + "debug", + f"状态评估任务已启动 间隔:{HF_JUDGE_STATE_UPDATE_INTERVAL_SECONDS}s", "_hf_judge_state_update_task", ), ( - self._cleanup_task, self._run_cleanup_cycle, - "hf_cleanup", "info", - f"清理任务已启动 间隔:{self.cleanup_interval}s", + f"清理任务已启动 间隔:{CLEANUP_INTERVAL_SECONDS}s", "_cleanup_task", ), ( - self._logging_task, self._run_logging_cycle, - "hf_logging", "info", - f"日志任务已启动 间隔:{self.log_interval}s", + f"日志任务已启动 间隔:{LOG_INTERVAL_SECONDS}s", "_logging_task", ), # 新增兴趣评估任务配置 ( - self._interest_eval_task, - self._run_interest_eval_cycle, - "hf_interest_eval", + self._run_into_focus_cycle, "debug", # 设为debug,避免过多日志 - f"兴趣评估任务已启动 间隔:{self.interest_eval_interval}s", - "_interest_eval_task", + f"专注评估任务已启动 间隔:{INTEREST_EVAL_INTERVAL_SECONDS}s", + "_into_focus_task", ), ] # 统一启动所有任务 - for _task_var, task_func, task_name, log_level, log_msg, task_attr_name in task_configs: + for task_func,log_level, log_msg, task_attr_name in task_configs: # 检查任务变量是否存在且未完成 current_task_var = getattr(self, task_attr_name) if current_task_var is None or current_task_var.done(): - new_task = asyncio.create_task(task_func(), name=task_name) + new_task = asyncio.create_task(task_func()) setattr(self, task_attr_name, new_task) # 更新任务变量 if new_task not in self._tasks: # 避免重复添加 self._tasks.append(new_task) @@ -123,7 +112,7 @@ class BackgroundTaskManager: # 根据配置记录不同级别的日志 getattr(logger, log_level)(log_msg) else: - logger.warning(f"{task_name}任务已在运行") + logger.warning(f"{task_attr_name}任务已在运行") async def stop_tasks(self): """停止所有后台任务。 @@ -209,10 +198,15 @@ class BackgroundTaskManager: logger.info("检测到离线,停用所有子心流") await self.subheartflow_manager.deactivate_all_subflows() - async def _perform_hf_judge_state_update_work(self): + async def _perform_absent_into_chat(self): """调用llm检测是否转换ABSENT-CHAT状态""" logger.info("[状态评估任务] 开始基于LLM评估子心流状态...") - await self.subheartflow_manager.evaluate_and_transition_subflows_by_llm() + await self.subheartflow_manager.sbhf_absent_into_chat() + + async def _normal_chat_timeout_check_work(self): + """检查处于CHAT状态的子心流是否因长时间未发言而超时,并将其转为ABSENT""" + logger.info("[聊天超时检查] 开始检查处于CHAT状态的子心流...") + await self.subheartflow_manager.sbhf_chat_into_absent() async def _perform_cleanup_work(self): """执行子心流清理任务 @@ -244,10 +238,10 @@ class BackgroundTaskManager: await self.interest_logger.log_all_states() # --- 新增兴趣评估工作函数 --- - async def _perform_interest_eval_work(self): + async def _perform_into_focus_work(self): """执行一轮子心流兴趣评估与提升检查。""" # 直接调用 subheartflow_manager 的方法,并传递当前状态信息 - await self.subheartflow_manager.evaluate_interest_and_promote() + await self.subheartflow_manager.sbhf_absent_into_focus() # --- 结束新增 --- @@ -259,25 +253,30 @@ class BackgroundTaskManager: task_name="State Update", interval=interval, task_func=self._perform_state_update_work ) - async def _run_hf_judge_state_update_cycle(self, interval: int): + async def _run_absent_into_chat(self, interval: int): await self._run_periodic_loop( - task_name="State Update", interval=interval, task_func=self._perform_hf_judge_state_update_work + task_name="Into Chat", interval=interval, task_func=self._perform_absent_into_chat + ) + + async def _run_normal_chat_timeout_check_cycle(self, interval: int): + await self._run_periodic_loop( + task_name="Normal Chat Timeout Check", interval=interval, task_func=self._normal_chat_timeout_check_work ) async def _run_cleanup_cycle(self): await self._run_periodic_loop( - task_name="Subflow Cleanup", interval=self.cleanup_interval, task_func=self._perform_cleanup_work + task_name="Subflow Cleanup", interval=CLEANUP_INTERVAL_SECONDS, task_func=self._perform_cleanup_work ) async def _run_logging_cycle(self): await self._run_periodic_loop( - task_name="State Logging", interval=self.log_interval, task_func=self._perform_logging_work + task_name="State Logging", interval=LOG_INTERVAL_SECONDS, task_func=self._perform_logging_work ) # --- 新增兴趣评估任务运行器 --- - async def _run_interest_eval_cycle(self): + async def _run_into_focus_cycle(self): await self._run_periodic_loop( - task_name="Interest Evaluation", - interval=self.interest_eval_interval, - task_func=self._perform_interest_eval_work, + task_name="Into Focus", + interval=INTEREST_EVAL_INTERVAL_SECONDS, + task_func=self._perform_into_focus_work, ) diff --git a/src/heart_flow/heartflow.py b/src/heart_flow/heartflow.py index f37b7f81..30151fdf 100644 --- a/src/heart_flow/heartflow.py +++ b/src/heart_flow/heartflow.py @@ -11,20 +11,9 @@ from src.heart_flow.subheartflow_manager import SubHeartflowManager from src.heart_flow.mind import Mind from src.heart_flow.interest_logger import InterestLogger # Import InterestLogger from src.heart_flow.background_tasks import BackgroundTaskManager # Import BackgroundTaskManager -# --- End import --- logger = get_logger("heartflow") - -# Task Intervals (should be in BackgroundTaskManager or config) -CLEANUP_INTERVAL_SECONDS = 1200 -STATE_UPDATE_INTERVAL_SECONDS = 60 - -# Thresholds (should be in SubHeartflowManager or config) -INACTIVE_THRESHOLD_SECONDS = 1200 -# --- End Constants --- # - - class Heartflow: """主心流协调器,负责初始化并协调各个子系统: - 状态管理 (MaiState) @@ -65,9 +54,6 @@ class Heartflow: mai_state_manager=self.mai_state_manager, subheartflow_manager=self.subheartflow_manager, interest_logger=self.interest_logger, - update_interval=STATE_UPDATE_INTERVAL_SECONDS, - cleanup_interval=CLEANUP_INTERVAL_SECONDS, - log_interval=3, # Example: Using value directly, ideally get from config ) async def get_or_create_subheartflow(self, subheartflow_id: Any) -> Optional["SubHeartflow"]: diff --git a/src/heart_flow/mai_state_manager.py b/src/heart_flow/mai_state_manager.py index 48eead1a..5f5a2d32 100644 --- a/src/heart_flow/mai_state_manager.py +++ b/src/heart_flow/mai_state_manager.py @@ -4,24 +4,30 @@ import random from typing import List, Tuple, Optional from src.common.logger_manager import get_logger from src.plugins.moods.moods import MoodManager - +from src.config.config import global_config logger = get_logger("mai_state") # -- 状态相关的可配置参数 (可以从 glocal_config 加载) -- -enable_unlimited_hfc_chat = True # 调试用:无限专注聊天 -# enable_unlimited_hfc_chat = False -prevent_offline_state = True # 调试用:防止进入离线状态 +# enable_unlimited_hfc_chat = True # 调试用:无限专注聊天 +enable_unlimited_hfc_chat = False +prevent_offline_state = True +# 目前默认不启用OFFLINE状态 # 不同状态下普通聊天的最大消息数 -MAX_NORMAL_CHAT_NUM_PEEKING = 30 -MAX_NORMAL_CHAT_NUM_NORMAL = 40 -MAX_NORMAL_CHAT_NUM_FOCUSED = 30 +base_normal_chat_num = global_config.base_normal_chat_num +base_focused_chat_num = global_config.base_focused_chat_num + + + +MAX_NORMAL_CHAT_NUM_PEEKING = int(base_normal_chat_num/2) +MAX_NORMAL_CHAT_NUM_NORMAL = base_normal_chat_num +MAX_NORMAL_CHAT_NUM_FOCUSED = base_normal_chat_num + 1 # 不同状态下专注聊天的最大消息数 -MAX_FOCUSED_CHAT_NUM_PEEKING = 20 -MAX_FOCUSED_CHAT_NUM_NORMAL = 30 -MAX_FOCUSED_CHAT_NUM_FOCUSED = 40 +MAX_FOCUSED_CHAT_NUM_PEEKING = int(base_focused_chat_num/2) +MAX_FOCUSED_CHAT_NUM_NORMAL = base_focused_chat_num +MAX_FOCUSED_CHAT_NUM_FOCUSED = base_focused_chat_num + 2 # -- 状态定义 -- @@ -164,7 +170,7 @@ class MaiStateManager: if random.random() < 0.03: # 3% 概率切换到 OFFLINE potential_next = MaiState.OFFLINE resolved_next = _resolve_offline(potential_next) - logger.debug(f"规则1:概率触发下线,resolve 为 {resolved_next.value}") + logger.debug(f"概率触发下线,resolve 为 {resolved_next.value}") # 只有当解析后的状态与当前状态不同时才设置 next_state if resolved_next != current_status: next_state = resolved_next diff --git a/src/heart_flow/observation.py b/src/heart_flow/observation.py index 5793e772..e34f37d3 100644 --- a/src/heart_flow/observation.py +++ b/src/heart_flow/observation.py @@ -146,7 +146,7 @@ class ChattingObservation(Observation): self.talking_message_str = await build_readable_messages( messages=self.talking_message, - timestamp_mode="normal", + timestamp_mode="lite", read_mark=last_obs_time_mark, ) self.talking_message_str_truncate = await build_readable_messages( diff --git a/src/heart_flow/sub_heartflow.py b/src/heart_flow/sub_heartflow.py index f4016068..9b9ec96d 100644 --- a/src/heart_flow/sub_heartflow.py +++ b/src/heart_flow/sub_heartflow.py @@ -15,20 +15,15 @@ from src.heart_flow.mai_state_manager import MaiStateInfo from src.heart_flow.chat_state_info import ChatState, ChatStateInfo from src.heart_flow.sub_mind import SubMind -# # --- REMOVE: Conditional import --- # -# if TYPE_CHECKING: -# from src.heart_flow.subheartflow_manager import SubHeartflowManager -# # --- END REMOVE --- # - # 定义常量 (从 interest.py 移动过来) MAX_INTEREST = 15.0 logger = get_logger("subheartflow") -base_reply_probability = 0.05 -probability_increase_rate_per_second = 0.08 -max_reply_probability = 1 +PROBABILITY_INCREASE_RATE_PER_SECOND = 0.1 +PROBABILITY_DECREASE_RATE_PER_SECOND = 0.1 +MAX_REPLY_PROBABILITY = 1 class InterestChatting: @@ -37,24 +32,15 @@ class InterestChatting: decay_rate=global_config.default_decay_rate_per_second, max_interest=MAX_INTEREST, trigger_threshold=global_config.reply_trigger_threshold, - base_reply_probability=base_reply_probability, - increase_rate=probability_increase_rate_per_second, - decay_factor=global_config.probability_decay_factor_per_second, - max_probability=max_reply_probability, + max_probability=MAX_REPLY_PROBABILITY, ): # 基础属性初始化 self.interest_level: float = 0.0 - self.last_update_time: float = time.time() self.decay_rate_per_second: float = decay_rate self.max_interest: float = max_interest - self.last_interaction_time: float = self.last_update_time self.trigger_threshold: float = trigger_threshold - self.base_reply_probability: float = base_reply_probability - self.probability_increase_rate: float = increase_rate - self.probability_decay_factor: float = decay_factor self.max_reply_probability: float = max_probability - self.current_reply_probability: float = 0.0 self.is_above_threshold: bool = False # 任务相关属性初始化 @@ -100,7 +86,6 @@ class InterestChatting: """ # 添加新消息 self.interest_dict[message.message_info.message_id] = (message, interest_value, is_mentioned) - self.last_interaction_time = time.time() # 如果字典长度超过10,删除最旧的消息 if len(self.interest_dict) > 10: @@ -144,10 +129,10 @@ class InterestChatting: async def _update_reply_probability(self): self.above_threshold = self.interest_level >= self.trigger_threshold if self.above_threshold: - self.start_hfc_probability += 0.1 + self.start_hfc_probability += PROBABILITY_INCREASE_RATE_PER_SECOND else: if self.start_hfc_probability > 0: - self.start_hfc_probability = max(0, self.start_hfc_probability - 0.1) + self.start_hfc_probability = max(0, self.start_hfc_probability - PROBABILITY_DECREASE_RATE_PER_SECOND) async def increase_interest(self, value: float): self.interest_level += value @@ -168,12 +153,7 @@ class InterestChatting: "above_threshold": self.above_threshold, } - async def should_evaluate_reply(self) -> bool: - if self.current_reply_probability > 0: - trigger = random.random() < self.current_reply_probability - return trigger - else: - return False + # --- 新增后台更新任务相关方法 --- async def _run_update_loop(self, update_interval: float = 1.0): @@ -492,12 +472,11 @@ class SubHeartflow: async def get_interest_state(self) -> dict: return await self.interest_chatting.get_state() - - async def get_interest_level(self) -> float: - return await self.interest_chatting.get_interest() - - async def should_evaluate_reply(self) -> bool: - return await self.interest_chatting.should_evaluate_reply() + + def get_normal_chat_last_speak_time(self) -> float: + if self.normal_chat_instance: + return self.normal_chat_instance.last_speak_time + return 0 def get_interest_dict(self) -> Dict[str, tuple[MessageRecv, float, bool]]: return self.interest_chatting.interest_dict diff --git a/src/heart_flow/sub_mind.py b/src/heart_flow/sub_mind.py index e59ee855..fbf1be87 100644 --- a/src/heart_flow/sub_mind.py +++ b/src/heart_flow/sub_mind.py @@ -140,11 +140,11 @@ class SubMind: individuality = Individuality.get_instance() relation_prompt = "" - print(f"person_list: {person_list}") + # print(f"person_list: {person_list}") for person in person_list: relation_prompt += await relationship_manager.build_relationship_info(person, is_id=True) - print(f"relat22222ion_prompt: {relation_prompt}") + # print(f"relat22222ion_prompt: {relation_prompt}") # 构建个性部分 prompt_personality = individuality.get_prompt(x_person=2, level=2) diff --git a/src/heart_flow/subheartflow_manager.py b/src/heart_flow/subheartflow_manager.py index afa0328e..ebb70f44 100644 --- a/src/heart_flow/subheartflow_manager.py +++ b/src/heart_flow/subheartflow_manager.py @@ -29,7 +29,7 @@ logger = get_logger("subheartflow_manager") # 子心流管理相关常量 INACTIVE_THRESHOLD_SECONDS = 3600 # 子心流不活跃超时时间(秒) - +NORMAL_CHAT_TIMEOUT_SECONDS = 30 * 60 # 30分钟 class SubHeartflowManager: """管理所有活跃的 SubHeartflow 实例。""" @@ -256,7 +256,7 @@ class SubHeartflowManager: f"{log_prefix} 完成,共处理 {processed_count} 个子心流,成功将 {changed_count} 个非 ABSENT 子心流的状态更改为 ABSENT。" ) - async def evaluate_interest_and_promote(self): + async def sbhf_absent_into_focus(self): """评估子心流兴趣度,满足条件且未达上限则提升到FOCUSED状态(基于start_hfc_probability)""" try: log_prefix = "[兴趣评估]" @@ -271,10 +271,7 @@ class SubHeartflowManager: return # 如果不允许,直接返回 # --- 结束新增 --- - logger.debug(f"{log_prefix} 当前状态 ({current_state.value}) 开始尝试提升到FOCUSED状态") - - if int(time.time()) % 20 == 0: # 每20秒输出一次 - logger.debug(f"{log_prefix} 当前状态 ({current_state.value}) 可以在{focused_limit}个群激情聊天") + logger.debug(f"{log_prefix} 当前状态 ({current_state.value}) 可以在{focused_limit}个群激情聊天") if focused_limit <= 0: # logger.debug(f"{log_prefix} 当前状态 ({current_state.value}) 不允许 FOCUSED 子心流") @@ -333,137 +330,187 @@ class SubHeartflowManager: except Exception as e: logger.error(f"启动HFC 兴趣评估失败: {e}", exc_info=True) - async def evaluate_and_transition_subflows_by_llm(self): + async def sbhf_absent_into_chat(self): """ - 使用LLM评估每个子心流的状态,并根据LLM的判断执行状态转换(ABSENT <-> CHAT)。 - 注意:此函数包含对假设的LLM函数的调用。 + 随机选一个 ABSENT 状态的子心流,评估是否应转换为 CHAT 状态。 + 每次调用最多转换一个。 """ - # 获取当前状态和限制,用于CHAT激活检查 current_mai_state = self.mai_state_info.get_current_state() chat_limit = current_mai_state.get_normal_chat_max_num() - transitioned_to_chat = 0 - transitioned_to_absent = 0 + async with self._lock: + # 1. 筛选出所有 ABSENT 状态的子心流 + absent_subflows = [ + hf for hf in self.subheartflows.values() + if hf.chat_state.chat_status == ChatState.ABSENT + ] - async with self._lock: # 在锁内获取快照并迭代 - subflows_snapshot = list(self.subheartflows.values()) - # 使用不上锁的版本,因为我们已经在锁内 + if not absent_subflows: + logger.debug("没有摸鱼的子心流可以评估。") # 日志太频繁,注释掉 + return # 没有目标,直接返回 + + # 2. 随机选一个幸运儿 + sub_hf_to_evaluate = random.choice(absent_subflows) + flow_id = sub_hf_to_evaluate.subheartflow_id + stream_name = chat_manager.get_stream_name(flow_id) or flow_id + log_prefix = f"[{stream_name}]" + + # 3. 检查 CHAT 上限 current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT) + if current_chat_count >= chat_limit: + logger.debug(f"{log_prefix} 想看看能不能聊,但是聊天太多了, ({current_chat_count}/{chat_limit}) 满了。") + return # 满了,这次就算了 + + # --- 获取 FOCUSED 计数 --- + current_focused_count = self.count_subflows_by_state_nolock(ChatState.FOCUSED) + focused_limit = current_mai_state.get_focused_chat_max_num() + + # --- 新增:获取聊天和专注群名 --- + chatting_group_names = [] + focused_group_names = [] + for flow_id, hf in self.subheartflows.items(): + stream_name = chat_manager.get_stream_name(flow_id) or str(flow_id) # 保证有名字 + if hf.chat_state.chat_status == ChatState.CHAT: + chatting_group_names.append(stream_name) + elif hf.chat_state.chat_status == ChatState.FOCUSED: + focused_group_names.append(stream_name) + # --- 结束新增 --- + + # --- 获取观察信息和构建 Prompt --- + first_observation = sub_hf_to_evaluate.observations[0] # 喵~第一个观察者肯定存在的说 + await first_observation.observe() + current_chat_log = first_observation.talking_message_str or "当前没啥聊天内容。" + _observation_summary = f"最近聊了这些:\n{current_chat_log}" + + mai_state_description = f"你当前状态: {current_mai_state.value}。" + individuality = Individuality.get_instance() + personality_prompt = individuality.get_prompt(x_person=2, level = 2) + prompt_personality = f"你正在扮演名为{individuality.name}的人类,{personality_prompt}" + + # --- 修改:在 prompt 中加入当前聊天计数和群名信息 (条件显示) --- + chat_status_lines = [] + if chatting_group_names: + chat_status_lines.append(f"正在闲聊 ({current_chat_count}/{chat_limit}): {', '.join(chatting_group_names)}") + if focused_group_names: + chat_status_lines.append(f"正在专注 ({current_focused_count}/{focused_limit}): {', '.join(focused_group_names)}") + + chat_status_prompt = "当前没有在任何群聊中。" # 默认消息喵~ + if chat_status_lines: + chat_status_prompt = "当前聊天情况:\n" + "\n".join(chat_status_lines) # 拼接状态信息 + + prompt = ( + f"{prompt_personality}\\n" + f"你当前没在 [{stream_name}] 群聊天。\\n" + f"{mai_state_description}\\n" + f"{chat_status_prompt}\\n" # <-- 喵!用了新的状态信息~ + f"{_observation_summary}\\n---\\n" + f"基于以上信息,你想不想开始在这个群闲聊?\\n" + f"请说明理由,并以 JSON 格式回答,包含 'decision' (布尔值) 和 'reason' (字符串)。\\n" + f'例如:{{\"decision\": true, \"reason\": \"看起来挺热闹的,插个话\"}}\\n' + f'例如:{{\"decision\": false, \"reason\": \"已经聊了好多,休息一下\"}}\\n' + f"请只输出有效的 JSON 对象。" + ) + # --- 结束修改 --- + + # --- 4. LLM 评估是否想聊 --- + yao_kai_shi_liao_ma = await self._llm_evaluate_state_transition(prompt) + + if yao_kai_shi_liao_ma is None: + logger.debug(f"{log_prefix} 问AI想不想聊失败了,这次算了。") + return # 评估失败,结束 + + if not yao_kai_shi_liao_ma: + logger.info(f"{log_prefix} 现在不想聊这个群。") + return # 不想聊,结束 + + # --- 5. AI想聊,再次检查额度并尝试转换 --- + # 再次检查以防万一 + current_chat_count_before_change = self.count_subflows_by_state_nolock(ChatState.CHAT) + if current_chat_count_before_change < chat_limit: + logger.info( + f"{log_prefix} 想聊,而且还有空位 ({current_chat_count_before_change}/{chat_limit}),这就去聊!" + ) + await sub_hf_to_evaluate.change_chat_state(ChatState.CHAT) + # 确认转换成功 + if sub_hf_to_evaluate.chat_state.chat_status == ChatState.CHAT: + logger.debug(f"{log_prefix} 成功进入聊天状态!本次评估圆满结束。") + else: + logger.warning(f"{log_prefix} 奇怪,尝试进入聊天状态失败了。当前状态: {sub_hf_to_evaluate.chat_state.chat_status.value}") + else: + logger.warning( + f"{log_prefix} AI说想聊,但是刚问完就没空位了 ({current_chat_count_before_change}/{chat_limit})。真不巧,下次再说吧。" + ) + # 无论转换成功与否,本次评估都结束了 + + # 锁在这里自动释放 + + # --- 新增:单独检查 CHAT 状态超时的任务 --- + async def sbhf_chat_into_absent(self): + """定期检查处于 CHAT 状态的子心流是否因长时间未发言而超时,并将其转为 ABSENT。""" + log_prefix_task = "[聊天超时检查]" + transitioned_to_absent = 0 + checked_count = 0 + + async with self._lock: + subflows_snapshot = list(self.subheartflows.values()) + checked_count = len(subflows_snapshot) if not subflows_snapshot: - logger.info("当前没有子心流需要评估。") + # logger.debug(f"{log_prefix_task} 没有子心流需要检查超时。") return for sub_hf in subflows_snapshot: + # 只检查 CHAT 状态的子心流 + if sub_hf.chat_state.chat_status != ChatState.CHAT: + continue + flow_id = sub_hf.subheartflow_id stream_name = chat_manager.get_stream_name(flow_id) or flow_id - log_prefix = f"[{stream_name}]" - current_subflow_state = sub_hf.chat_state.chat_status + log_prefix = f"[{stream_name}]({log_prefix_task})" - _observation_summary = "没有可用的观察信息。" # 默认值 + should_deactivate = False + reason = "" - first_observation = sub_hf.observations[0] - if isinstance(first_observation, ChattingObservation): - # 组合中期记忆和当前聊天内容 - await first_observation.observe() - current_chat = first_observation.talking_message_str or "当前无聊天内容。" - combined_summary = f"当前聊天内容:\n{current_chat}" - else: - logger.warning(f"{log_prefix} [{stream_name}] 第一个观察者不是 ChattingObservation 类型。") + try: + # 使用变量名 last_bot_dong_zuo_time 替代 last_bot_activity_time + last_bot_dong_zuo_time = sub_hf.get_normal_chat_last_speak_time() - # --- 获取麦麦状态 --- - mai_state_description = f"你当前状态: {current_mai_state.value}。" + if last_bot_dong_zuo_time > 0: + current_time = time.time() + # 使用变量名 time_since_last_bb 替代 time_since_last_reply + time_since_last_bb = current_time - last_bot_dong_zuo_time - # 获取个性化信息 - individuality = Individuality.get_instance() + if time_since_last_bb > NORMAL_CHAT_TIMEOUT_SECONDS: + should_deactivate = True + reason = f"超过 {NORMAL_CHAT_TIMEOUT_SECONDS / 60:.0f} 分钟没 BB" + logger.info(f"{log_prefix} 检测到超时 ({reason}),准备转为 ABSENT。上次活动时间: {last_bot_dong_zuo_time:.0f}") + # else: + # logger.debug(f"{log_prefix} Bot活动时间未超时 ({time_since_last_bb:.0f}s < {NORMAL_CHAT_TIMEOUT_SECONDS}s),保持 CHAT 状态。") + # else: + # 如果没有记录到Bot的活动时间,暂时不因为超时而转换状态 + # logger.debug(f"{log_prefix} 未找到有效的 Bot 最后活动时间记录,不执行超时检查。") - # 构建个性部分 - prompt_personality = f"你正在扮演名为{individuality.personality.bot_nickname}的人类,你" - prompt_personality += individuality.personality.personality_core + except AttributeError: + logger.error(f"{log_prefix} 无法获取 Bot 最后 BB 时间,请确保 SubHeartflow 相关实现正确。跳过超时检查。") + except Exception as e: + logger.error(f"{log_prefix} 检查 Bot 超时状态时出错: {e}", exc_info=True) - # 随机添加个性侧面 - if individuality.personality.personality_sides: - random_side = random.choice(individuality.personality.personality_sides) - prompt_personality += f",{random_side}" - - # 随机添加身份细节 - if individuality.identity.identity_detail: - random_detail = random.choice(individuality.identity.identity_detail) - prompt_personality += f",{random_detail}" - - # --- 针对 ABSENT 状态 --- - if current_subflow_state == ChatState.ABSENT: - # 构建Prompt - prompt = ( - f"{prompt_personality}\n" - f"你当前没有在: [{stream_name}] 群中聊天。\n" - f"{mai_state_description}\n" - f"这个群里最近的聊天内容是:\n---\n{combined_summary}\n---\n" - f"基于以上信息,请判断你是否愿意在这个群开始闲聊," - f"进入常规聊天(CHAT)状态?\n" - f"给出你的判断,和理由,然后以 JSON 格式回答" - f"包含键 'decision',如果要开始聊天,值为 true ,否则为 false.\n" - f"包含键 'reason',其值为你的理由。\n" - f'例如:{{"decision": true, "reason": "因为我想聊天"}}\n' - f"请只输出有效的 JSON 对象。" - ) - - # 调用LLM评估 - should_activate = await self._llm_evaluate_state_transition(prompt) - if should_activate is None: # 处理解析失败或意外情况 - logger.warning(f"{log_prefix}LLM评估返回无效结果,跳过。") - continue - - if should_activate: - # 检查CHAT限额 - # 使用不上锁的版本,因为我们已经在锁内 - current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT) - if current_chat_count < chat_limit: - logger.info( - f"{log_prefix}LLM建议激活到CHAT状态,且未达上限({current_chat_count}/{chat_limit})。正在尝试转换..." - ) - await sub_hf.change_chat_state(ChatState.CHAT) - if sub_hf.chat_state.chat_status == ChatState.CHAT: - transitioned_to_chat += 1 - else: - logger.warning(f"{log_prefix}尝试激活到CHAT失败。") - else: - logger.info( - f"{log_prefix}LLM建议激活到CHAT状态,但已达到上限({current_chat_count}/{chat_limit})。跳过转换。" - ) + # --- 执行状态转换(如果超时) --- + if should_deactivate: + logger.info(f"{log_prefix} 因超时 ({reason}),尝试转换为 ABSENT 状态。") + await sub_hf.change_chat_state(ChatState.ABSENT) + # 再次检查确保状态已改变 + if sub_hf.chat_state.chat_status == ChatState.ABSENT: + transitioned_to_absent += 1 + logger.info(f"{log_prefix} 已成功转换为 ABSENT 状态。") else: - logger.info(f"{log_prefix}LLM建议不激活到CHAT状态。") + logger.warning(f"{log_prefix} 尝试因超时转换为 ABSENT 失败。") - # --- 针对 CHAT 状态 --- - elif current_subflow_state == ChatState.CHAT: - # 构建Prompt - prompt = ( - f"{prompt_personality}\n" - f"你正在在: [{stream_name}] 群中聊天。\n" - f"{mai_state_description}\n" - f"这个群里最近的聊天内容是:\n---\n{combined_summary}\n---\n" - f"基于以上信息,请判断你是否愿意在这个群继续闲聊," - f"还是暂时离开聊天,进入休眠状态?\n" - f"给出你的判断,和理由,然后以 JSON 格式回答" - f"包含键 'decision',如果要离开聊天,值为 true ,否则为 false.\n" - f"包含键 'reason',其值为你的理由。\n" - f'例如:{{"decision": true, "reason": "因为我想休息"}}\n' - f"请只输出有效的 JSON 对象。" - ) - - # 调用LLM评估 - should_deactivate = await self._llm_evaluate_state_transition(prompt) - if should_deactivate is None: # 处理解析失败或意外情况 - logger.warning(f"{log_prefix}LLM评估返回无效结果,跳过。") - continue - - if should_deactivate: - logger.info(f"{log_prefix}LLM建议进入ABSENT状态。正在尝试转换...") - await sub_hf.change_chat_state(ChatState.ABSENT) - if sub_hf.chat_state.chat_status == ChatState.ABSENT: - transitioned_to_absent += 1 - else: - logger.info(f"{log_prefix}LLM建议不进入ABSENT状态。") + if transitioned_to_absent > 0: + logger.info(f"{log_prefix_task} 完成,共检查 {checked_count} 个子心流,{transitioned_to_absent} 个因超时转为 ABSENT。") + # else: + # logger.debug(f"{log_prefix_task} 完成,共检查 {checked_count} 个子心流,无超时转换。") + # --- 结束新增 --- async def _llm_evaluate_state_transition(self, prompt: str) -> Optional[bool]: """ @@ -579,14 +626,14 @@ class SubHeartflowManager: # --- 新增:处理 HFC 无回复回调的专用方法 --- # async def _handle_hfc_no_reply(self, subheartflow_id: Any): """处理来自 HeartFChatting 的连续无回复信号 (通过 partial 绑定 ID)""" - # 注意:这里不需要再获取锁,因为 request_absent_transition 内部会处理锁 + # 注意:这里不需要再获取锁,因为 sbhf_focus_into_absent 内部会处理锁 logger.debug(f"[管理器 HFC 处理器] 接收到来自 {subheartflow_id} 的 HFC 无回复信号") - await self.request_absent_transition(subheartflow_id) + await self.sbhf_focus_into_absent(subheartflow_id) # --- 结束新增 --- # # --- 新增:处理来自 HeartFChatting 的状态转换请求 --- # - async def request_absent_transition(self, subflow_id: Any): + async def sbhf_focus_into_absent(self, subflow_id: Any): """ 接收来自 HeartFChatting 的请求,将特定子心流的状态转换为 ABSENT。 通常在连续多次 "no_reply" 后被调用。 @@ -606,12 +653,42 @@ class SubHeartflowManager: # 仅当子心流处于 FOCUSED 状态时才进行转换 # 因为 HeartFChatting 只在 FOCUSED 状态下运行 if current_state == ChatState.FOCUSED: - logger.info(f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 转换为 ABSENT") + target_state = ChatState.ABSENT # 默认目标状态 + log_reason = "默认转换" + + # 决定是去 ABSENT 还是 CHAT + if random.random() < 0.5: + target_state = ChatState.ABSENT + log_reason = "随机选择 ABSENT" + logger.debug(f"[状态转换请求] {stream_name} ({current_state.value}) 随机决定进入 ABSENT") + else: + # 尝试进入 CHAT,先检查限制 + current_mai_state = self.mai_state_info.get_current_state() + chat_limit = current_mai_state.get_normal_chat_max_num() + # 使用不上锁的版本,因为我们已经在锁内 + current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT) + + if current_chat_count < chat_limit: + target_state = ChatState.CHAT + log_reason = f"随机选择 CHAT (当前 {current_chat_count}/{chat_limit})" + logger.debug(f"[状态转换请求] {stream_name} ({current_state.value}) 随机决定进入 CHAT,未达上限 ({current_chat_count}/{chat_limit})") + else: + target_state = ChatState.ABSENT + log_reason = f"随机选择 CHAT 但已达上限 ({current_chat_count}/{chat_limit}),转为 ABSENT" + logger.debug(f"[状态转换请求] {stream_name} ({current_state.value}) 随机决定进入 CHAT,但已达上限 ({current_chat_count}/{chat_limit}),改为进入 ABSENT") + + #开始转换 + logger.info(f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 尝试转换为 {target_state.value} ({log_reason})") try: - await subflow.change_chat_state(ChatState.ABSENT) - logger.info(f"[状态转换请求] {stream_name} 状态已成功转换为 ABSENT") + await subflow.change_chat_state(target_state) + # 检查最终状态 + final_state = subflow.chat_state.chat_status + if final_state == target_state: + logger.debug(f"[状态转换请求] {stream_name} 状态已成功转换为 {final_state.value}") + else: + logger.warning(f"[状态转换请求] 尝试将 {stream_name} 转换为 {target_state.value} 后,状态实际为 {final_state.value}") except Exception as e: - logger.error(f"[状态转换请求] 转换 {stream_name} 到 ABSENT 时出错: {e}", exc_info=True) + logger.error(f"[状态转换请求] 转换 {stream_name} 到 {target_state.value} 时出错: {e}", exc_info=True) elif current_state == ChatState.ABSENT: logger.debug(f"[状态转换请求] {stream_name} 已处于 ABSENT 状态,无需转换") else: diff --git a/src/individuality/individuality.py b/src/individuality/individuality.py index 65e9937c..86e5b63e 100644 --- a/src/individuality/individuality.py +++ b/src/individuality/individuality.py @@ -191,7 +191,7 @@ class Individuality: 获取合并的个体特征prompt Args: - level (int): 详细程度 (1: 核心/随机细节, 2: 核心+侧面/细节+其他, 3: 全部) + level (int): 详细程度 (1: 核心/随机细节, 2: 核心+随机侧面/全部细节, 3: 全部) x_person (int, optional): 人称代词 (0: 无人称, 1: 我, 2: 你). 默认为 2. Returns: diff --git a/src/plugins/chat/bot.py b/src/plugins/chat/bot.py index f89b3898..8051d0a8 100644 --- a/src/plugins/chat/bot.py +++ b/src/plugins/chat/bot.py @@ -99,15 +99,20 @@ class ChatBot: template_group_name = None async def preprocess(): + logger.trace("开始预处理消息...") # 如果在私聊中 if groupinfo is None: + logger.trace("检测到私聊消息") # 是否在配置信息中开启私聊模式 if global_config.enable_friend_chat: + logger.trace("私聊模式已启用") # 是否进入PFC if global_config.enable_pfc_chatting: + logger.trace("进入PFC私聊处理流程") userinfo = message.message_info.user_info messageinfo = message.message_info # 创建聊天流 + logger.trace(f"为{userinfo.user_id}创建/获取聊天流") chat = await chat_manager.get_or_create_stream( platform=messageinfo.platform, user_info=userinfo, @@ -118,9 +123,11 @@ class ChatBot: await self._create_pfc_chat(message) # 禁止PFC,进入普通的心流消息处理逻辑 else: + logger.trace("进入普通心流私聊处理") await self.heartflow_processor.process_message(message_data) # 群聊默认进入心流消息处理逻辑 else: + logger.trace(f"检测到群聊消息,群ID: {groupinfo.group_id}") await self.heartflow_processor.process_message(message_data) if template_group_name: diff --git a/src/plugins/chat/utils.py b/src/plugins/chat/utils.py index 4eb3d93d..71980f48 100644 --- a/src/plugins/chat/utils.py +++ b/src/plugins/chat/utils.py @@ -732,6 +732,9 @@ def translate_timestamp_to_human_readable(timestamp: float, mode: str = "normal" return f"{int(diff / 86400)}天前:\n" else: return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)) + ":\n" + elif mode == "lite": + # 只返回时分秒格式,喵~ + return time.strftime("%H:%M:%S", time.localtime(timestamp)) return None diff --git a/src/plugins/heartFC_chat/heartFC_chat.py b/src/plugins/heartFC_chat/heartFC_chat.py index c15c4f83..15518a77 100644 --- a/src/plugins/heartFC_chat/heartFC_chat.py +++ b/src/plugins/heartFC_chat/heartFC_chat.py @@ -30,10 +30,12 @@ from src.plugins.moods.moods import MoodManager from src.individuality.individuality import Individuality -INITIAL_DURATION = 60.0 - WAITING_TIME_THRESHOLD = 300 # 等待新消息时间阈值,单位秒 +EMOJI_SEND_PRO = 0.3 # 设置一个概率,比如 30% 才真的发 + +CONSECUTIVE_NO_REPLY_THRESHOLD = 3 # 连续不回复的阈值 + logger = get_logger("interest") # Logger Name Changed @@ -179,8 +181,6 @@ class HeartFChatting: 其生命周期现在由其关联的 SubHeartflow 的 FOCUSED 状态控制。 """ - CONSECUTIVE_NO_REPLY_THRESHOLD = 3 # 连续不回复的阈值 - def __init__( self, chat_id: str, @@ -644,14 +644,14 @@ class HeartFChatting: self._lian_xu_bu_hui_fu_ci_shu += 1 self._lian_xu_deng_dai_shi_jian += dang_qian_deng_dai # 累加等待时间 logger.debug( - f"{self.log_prefix} 连续不回复计数增加: {self._lian_xu_bu_hui_fu_ci_shu}/{self.CONSECUTIVE_NO_REPLY_THRESHOLD}, " + f"{self.log_prefix} 连续不回复计数增加: {self._lian_xu_bu_hui_fu_ci_shu}/{CONSECUTIVE_NO_REPLY_THRESHOLD}, " f"本次等待: {dang_qian_deng_dai:.2f}秒, 累计等待: {self._lian_xu_deng_dai_shi_jian:.2f}秒" ) # 检查是否同时达到次数和时间阈值 - time_threshold = 0.66 * WAITING_TIME_THRESHOLD * self.CONSECUTIVE_NO_REPLY_THRESHOLD + time_threshold = 0.66 * WAITING_TIME_THRESHOLD * CONSECUTIVE_NO_REPLY_THRESHOLD if ( - self._lian_xu_bu_hui_fu_ci_shu >= self.CONSECUTIVE_NO_REPLY_THRESHOLD + self._lian_xu_bu_hui_fu_ci_shu >= CONSECUTIVE_NO_REPLY_THRESHOLD and self._lian_xu_deng_dai_shi_jian >= time_threshold ): logger.info( @@ -661,7 +661,7 @@ class HeartFChatting: ) # 调用回调。注意:这里不重置计数器和时间,依赖回调函数成功改变状态来隐式重置上下文。 await self.on_consecutive_no_reply_callback() - elif self._lian_xu_bu_hui_fu_ci_shu >= self.CONSECUTIVE_NO_REPLY_THRESHOLD: + elif self._lian_xu_bu_hui_fu_ci_shu >= CONSECUTIVE_NO_REPLY_THRESHOLD: # 仅次数达到阈值,但时间未达到 logger.debug( f"{self.log_prefix} 连续不回复次数达到阈值 ({self._lian_xu_bu_hui_fu_ci_shu}次) " @@ -979,6 +979,20 @@ class HeartFChatting: f"{self.log_prefix}[Planner] 恢复了原始动作集, 当前可用: {list(self.action_manager.get_available_actions().keys())}" ) # --- 结束:确保动作恢复 --- + + # --- 新增:概率性忽略文本回复附带的表情(正确的位置)--- + + + if action == "text_reply" and emoji_query: + logger.debug(f"{self.log_prefix}[Planner] 大模型想让麦麦发文字时带表情: '{emoji_query}'") + # 掷骰子看看要不要听它的 + if random.random() > EMOJI_SEND_PRO: + logger.info(f"{self.log_prefix}[Planner] 但是麦麦这次不想加表情 ({1-EMOJI_SEND_PRO:.0%}),忽略表情 '{emoji_query}'") + emoji_query = "" # 把表情请求清空,就不发了 + else: + logger.info(f"{self.log_prefix}[Planner] 好吧,加上表情 '{emoji_query}'") + # --- 结束:概率性忽略 --- + # --- 结束 LLM 决策 --- # return { diff --git a/src/plugins/heartFC_chat/heartflow_prompt_builder.py b/src/plugins/heartFC_chat/heartflow_prompt_builder.py index a0f266d6..c4958a9f 100644 --- a/src/plugins/heartFC_chat/heartflow_prompt_builder.py +++ b/src/plugins/heartFC_chat/heartflow_prompt_builder.py @@ -174,7 +174,7 @@ class PromptBuilder: message_list_before_now = get_raw_msg_before_timestamp_with_chat( chat_id=chat_stream.stream_id, timestamp=time.time(), - limit=global_config.MAX_CONTEXT_SIZE, + limit=global_config.observation_context_size, ) chat_talking_prompt = await build_readable_messages( @@ -241,6 +241,8 @@ class PromptBuilder: prompt_ger=prompt_ger, moderation_prompt=await global_prompt_manager.get_prompt_async("moderation_prompt"), ) + + logger.debug(f"focus_chat_prompt: \n{prompt}") return prompt @@ -255,7 +257,7 @@ class PromptBuilder: who_chat_in_group += get_recent_group_speaker( chat_stream.stream_id, (chat_stream.user_info.platform, chat_stream.user_info.user_id), - limit=global_config.MAX_CONTEXT_SIZE, + limit=global_config.observation_context_size, ) relation_prompt = "" @@ -314,7 +316,7 @@ class PromptBuilder: message_list_before_now = get_raw_msg_before_timestamp_with_chat( chat_id=chat_stream.stream_id, timestamp=time.time(), - limit=global_config.MAX_CONTEXT_SIZE, + limit=global_config.observation_context_size, ) chat_talking_prompt = await build_readable_messages( diff --git a/src/plugins/heartFC_chat/normal_chat.py b/src/plugins/heartFC_chat/normal_chat.py index e97cbcd2..95250652 100644 --- a/src/plugins/heartFC_chat/normal_chat.py +++ b/src/plugins/heartFC_chat/normal_chat.py @@ -43,6 +43,8 @@ class NormalChat: self.mood_manager = MoodManager.get_instance() # MoodManager 保持单例 # 存储此实例的兴趣监控任务 self.start_time = time.time() + + self.last_speak_time = 0 self._chat_task: Optional[asyncio.Task] = None logger.info(f"[{self.stream_name}] NormalChat 实例初始化完成。") @@ -119,6 +121,8 @@ class NormalChat: await message_manager.add_message(message_set) + self.last_speak_time = time.time() + return first_bot_msg # 改为实例方法 diff --git a/src/plugins/models/utils_model.py b/src/plugins/models/utils_model.py index e8ccf1ca..e421641c 100644 --- a/src/plugins/models/utils_model.py +++ b/src/plugins/models/utils_model.py @@ -632,7 +632,7 @@ class LLMRequest: **params_copy, } if "max_tokens" not in payload and "max_completion_tokens" not in payload: - payload["max_tokens"] = global_config.max_response_length + payload["max_tokens"] = global_config.model_max_output_length # 如果 payload 中依然存在 max_tokens 且需要转换,在这里进行再次检查 if self.model_name.lower() in self.MODELS_NEEDING_TRANSFORMATION and "max_tokens" in payload: payload["max_completion_tokens"] = payload.pop("max_tokens") diff --git a/src/plugins/person_info/relationship_manager.py b/src/plugins/person_info/relationship_manager.py index a25dd731..fc8cf548 100644 --- a/src/plugins/person_info/relationship_manager.py +++ b/src/plugins/person_info/relationship_manager.py @@ -282,10 +282,10 @@ class RelationshipManager: if is_id: person_id = person else: - print(f"person: {person}") + # print(f"person: {person}") person_id = person_info_manager.get_person_id(person[0], person[1]) person_name = await person_info_manager.get_value(person_id, "person_name") - print(f"person_name: {person_name}") + # print(f"person_name: {person_name}") relationship_value = await person_info_manager.get_value(person_id, "relationship_value") level_num = self.calculate_level_num(relationship_value) diff --git a/src/plugins/respon_info_catcher/info_catcher.py b/src/plugins/respon_info_catcher/info_catcher.py index 4b19844a..5cb67a16 100644 --- a/src/plugins/respon_info_catcher/info_catcher.py +++ b/src/plugins/respon_info_catcher/info_catcher.py @@ -8,13 +8,12 @@ from typing import List class InfoCatcher: def __init__(self): - self.chat_history = [] # 聊天历史,长度为三倍使用的上下文 - self.context_length = global_config.MAX_CONTEXT_SIZE - self.chat_history_in_thinking = [] # 思考期间的聊天内容 - self.chat_history_after_response = [] # 回复后的聊天内容,长度为一倍上下文 + self.chat_history = [] # 聊天历史,长度为三倍使用的上下文喵~ + self.context_length = global_config.observation_context_size + self.chat_history_in_thinking = [] # 思考期间的聊天内容喵~ + self.chat_history_after_response = [] # 回复后的聊天内容,长度为一倍上下文喵~ self.chat_id = "" - self.response_mode = global_config.response_mode self.trigger_response_text = "" self.response_text = "" @@ -36,10 +35,10 @@ class InfoCatcher: "model": "", } - # 使用字典来存储 reasoning 模式的数据 + # 使用字典来存储 reasoning 模式的数据喵~ self.reasoning_data = {"thinking_log": "", "prompt": "", "response": "", "model": ""} - # 耗时 + # 耗时喵~ self.timing_results = { "interested_rate_time": 0, "sub_heartflow_observe_time": 0, @@ -73,15 +72,25 @@ class InfoCatcher: self.heartflow_data["sub_heartflow_now"] = current_mind def catch_after_llm_generated(self, prompt: str, response: str, reasoning_content: str = "", model_name: str = ""): - if self.response_mode == "heart_flow": - self.heartflow_data["prompt"] = prompt - self.heartflow_data["response"] = response - self.heartflow_data["model"] = model_name - elif self.response_mode == "reasoning": - self.reasoning_data["thinking_log"] = reasoning_content - self.reasoning_data["prompt"] = prompt - self.reasoning_data["response"] = response - self.reasoning_data["model"] = model_name + # if self.response_mode == "heart_flow": # 条件判断不需要了喵~ + # self.heartflow_data["prompt"] = prompt + # self.heartflow_data["response"] = response + # self.heartflow_data["model"] = model_name + # elif self.response_mode == "reasoning": # 条件判断不需要了喵~ + # self.reasoning_data["thinking_log"] = reasoning_content + # self.reasoning_data["prompt"] = prompt + # self.reasoning_data["response"] = response + # self.reasoning_data["model"] = model_name + + # 直接记录信息喵~ + self.reasoning_data["thinking_log"] = reasoning_content + self.reasoning_data["prompt"] = prompt + self.reasoning_data["response"] = response + self.reasoning_data["model"] = model_name + # 如果 heartflow 数据也需要通用字段,可以取消下面的注释喵~ + # self.heartflow_data["prompt"] = prompt + # self.heartflow_data["response"] = response + # self.heartflow_data["model"] = model_name self.response_text = response @@ -172,13 +181,13 @@ class InfoCatcher: } def done_catch(self): - """将收集到的信息存储到数据库的 thinking_log 集合中""" + """将收集到的信息存储到数据库的 thinking_log 集合中喵~""" try: - # 将消息对象转换为可序列化的字典 + # 将消息对象转换为可序列化的字典喵~ thinking_log_data = { "chat_id": self.chat_id, - "response_mode": self.response_mode, + # "response_mode": self.response_mode, # 这个也删掉喵~ "trigger_text": self.trigger_response_text, "response_text": self.response_text, "trigger_info": { @@ -195,18 +204,20 @@ class InfoCatcher: "chat_history_after_response": self.message_list_to_dict(self.chat_history_after_response), } - # 根据不同的响应模式添加相应的数据 - if self.response_mode == "heart_flow": - thinking_log_data["mode_specific_data"] = self.heartflow_data - elif self.response_mode == "reasoning": - thinking_log_data["mode_specific_data"] = self.reasoning_data + # 根据不同的响应模式添加相应的数据喵~ # 现在直接都加上去好了喵~ + # if self.response_mode == "heart_flow": + # thinking_log_data["mode_specific_data"] = self.heartflow_data + # elif self.response_mode == "reasoning": + # thinking_log_data["mode_specific_data"] = self.reasoning_data + thinking_log_data["heartflow_data"] = self.heartflow_data + thinking_log_data["reasoning_data"] = self.reasoning_data - # 将数据插入到 thinking_log 集合中 + # 将数据插入到 thinking_log 集合中喵~ db.thinking_log.insert_one(thinking_log_data) return True except Exception as e: - print(f"存储思考日志时出错: {str(e)}") + print(f"存储思考日志时出错: {str(e)} 喵~") print(traceback.format_exc()) return False diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 8ca6aaa3..2be1ca4f 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "1.5.1" +version = "1.6.0" #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #如果你想要修改配置文件,请在修改后将version的值进行变更 @@ -65,33 +65,14 @@ time_zone = "Asia/Shanghai" # 给你的机器人设置时区,可以解决运 [platforms] # 必填项目,填写每个平台适配器提供的链接 nonebot-qq="http://127.0.0.1:18002/api/message" -[response] #群聊的回复策略 -#一般回复参数 -model_reasoning_probability = 0.7 # 麦麦回答时选择推理模型 模型的概率 -model_normal_probability = 0.3 # 麦麦回答时选择一般模型 模型的概率 - -[heartflow] -allow_focus_mode = true # 是否允许进入FOCUSED状态 -# 是否启用heart_flowC(心流聊天,HFC)模式 +[chat] #麦麦的聊天通用设置 +allow_focus_mode = true # 是否允许专注聊天状态 +# 是否启用heart_flowC(HFC)模式 # 启用后麦麦会自主选择进入heart_flowC模式(持续一段时间),进行主动的观察和回复,并给出回复,比较消耗token -reply_trigger_threshold = 3.0 # 心流聊天触发阈值,越低越容易进入心流聊天 -probability_decay_factor_per_second = 0.2 # 概率衰减因子,越大衰减越快,越高越容易退出心流聊天 -default_decay_rate_per_second = 0.98 # 默认衰减率,越大衰减越快,越高越难进入心流聊天 +base_normal_chat_num = 3 # 最多允许多少个群进行普通聊天 +base_focused_chat_num = 2 # 最多允许多少个群进行专注聊天 - - -sub_heart_flow_stop_time = 500 # 子心流停止时间,超过这个时间没有回复,子心流会停止,间隔 单位秒 - -observation_context_size = 20 # 心流观察到的最长上下文大小,超过这个值的上下文会被压缩 -compressed_length = 5 # 不能大于observation_context_size,心流上下文压缩的最短压缩长度,超过心流观察到的上下文长度,会压缩,最短压缩长度为5 -compress_length_limit = 5 #最多压缩份数,超过该数值的压缩上下文会被删除 - - -[message] -max_context_size = 12 # 麦麦回复时获得的上文数量,建议12,太短太长都会导致脑袋尖尖 -emoji_chance = 0.2 # 麦麦一般回复时使用表情包的概率,设置为1让麦麦自己决定发不发 -thinking_timeout = 100 # 麦麦最长思考时间,超过这个时间的思考会放弃(往往是api反应太慢) -max_response_length = 256 # 麦麦单次回答的最大token数 +observation_context_size = 15 # 观察到的最长上下文大小,建议15,太短太长都会导致脑袋尖尖 message_buffer = true # 启用消息缓冲器?启用此项以解决消息的拆分问题,但会使麦麦的回复延迟 # 以下是消息过滤,可以根据规则过滤特定消息,将不会读取这些消息 @@ -106,7 +87,14 @@ ban_msgs_regex = [ # "\\[CQ:at,qq=\\d+\\]" # 匹配@ ] -[willing] # 一般回复模式的回复意愿设置 +[normal_chat] #普通聊天 +#一般回复参数 +model_reasoning_probability = 0.7 # 麦麦回答时选择推理模型 模型的概率 +model_normal_probability = 0.3 # 麦麦回答时选择一般模型 模型的概率 + +emoji_chance = 0.2 # 麦麦一般回复时使用表情包的概率,设置为1让麦麦自己决定发不发 +thinking_timeout = 100 # 麦麦最长思考时间,超过这个时间的思考会放弃(往往是api反应太慢) + willing_mode = "classical" # 回复意愿模式 —— 经典模式:classical,动态模式:dynamic,mxp模式:mxp,自定义模式:custom(需要你自己实现) response_willing_amplifier = 1 # 麦麦回复意愿放大系数,一般为1 response_interested_rate_amplifier = 1 # 麦麦回复兴趣度放大系数,听到记忆里的内容时放大系数 @@ -115,6 +103,16 @@ emoji_response_penalty = 0 # 表情包回复惩罚系数,设为0为不回复 mentioned_bot_inevitable_reply = false # 提及 bot 必然回复 at_bot_inevitable_reply = false # @bot 必然回复 +[focus_chat] #专注聊天 +reply_trigger_threshold = 3.5 # 专注聊天触发阈值,越低越容易进入专注聊天 +default_decay_rate_per_second = 0.98 # 默认衰减率,越大衰减越快,越高越难进入专注聊天 +consecutive_no_reply_threshold = 3 # 连续不回复的阈值,越低越容易结束专注聊天 + +# 以下选项暂时无效 +compressed_length = 5 # 不能大于observation_context_size,心流上下文压缩的最短压缩长度,超过心流观察到的上下文长度,会压缩,最短压缩长度为5 +compress_length_limit = 5 #最多压缩份数,超过该数值的压缩上下文会被删除 + + [emoji] max_emoji_num = 40 # 表情包最大数量 max_reach_deletion = true # 开启则在达到最大数量时删除表情包,关闭则达到最大数量时不删除,只是不会继续收集表情包 @@ -181,6 +179,8 @@ response_max_length = 256 # 回复允许的最大长度 response_max_sentence_num = 4 # 回复允许的最大句子数 enable_kaomoji_protection = false # 是否启用颜文字保护 +model_max_output_length = 256 # 模型单次返回的最大token数 + [remote] #发送统计信息,主要是看全球有多少只麦麦 enable = true From 4c51b6a09c68e4bef3c7f6a5a25a3ebca2a1670f Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 30 Apr 2025 17:47:05 +0800 Subject: [PATCH 09/20] =?UTF-8?q?better=EF=BC=9A=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=A1=A8=E6=83=85=E5=8C=85=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/chat/utils_image.py | 99 ++++++++++++++++++----- src/plugins/emoji_system/emoji_manager.py | 14 ++-- 2 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/plugins/chat/utils_image.py b/src/plugins/chat/utils_image.py index 7053e384..95423c7a 100644 --- a/src/plugins/chat/utils_image.py +++ b/src/plugins/chat/utils_image.py @@ -5,6 +5,7 @@ import hashlib from typing import Optional from PIL import Image import io +import numpy as np from ...common.database import db @@ -231,14 +232,16 @@ class ImageManager: return "[图片]" @staticmethod - def transform_gif(gif_base64: str) -> str: - """将GIF转换为水平拼接的静态图像 + def transform_gif(gif_base64: str, similarity_threshold: float = 1000.0, max_frames: int = 15) -> Optional[str]: + """将GIF转换为水平拼接的静态图像, 跳过相似的帧 Args: gif_base64: GIF的base64编码字符串 + similarity_threshold: 判定帧相似的阈值 (MSE),越小表示要求差异越大才算不同帧,默认1000.0 + max_frames: 最大抽取的帧数,默认15 Returns: - str: 拼接后的JPG图像的base64编码字符串 + Optional[str]: 拼接后的JPG图像的base64编码字符串, 或者在失败时返回None """ try: # 解码base64 @@ -246,41 +249,90 @@ class ImageManager: gif = Image.open(io.BytesIO(gif_data)) # 收集所有帧 - frames = [] + all_frames = [] try: while True: - gif.seek(len(frames)) + gif.seek(len(all_frames)) + # 确保是RGB格式方便比较 frame = gif.convert("RGB") - frames.append(frame.copy()) + all_frames.append(frame.copy()) except EOFError: - pass + pass # 读完啦 - if not frames: - raise ValueError("No frames found in GIF") + if not all_frames: + logger.warning("GIF中没有找到任何帧") + return None # 空的GIF直接返回None - # 计算需要抽取的帧的索引 - total_frames = len(frames) - if total_frames <= 15: - selected_frames = frames - else: - # 均匀抽取10帧 - indices = [int(i * (total_frames - 1) / 14) for i in range(15)] - selected_frames = [frames[i] for i in indices] + # --- 新的帧选择逻辑 --- + selected_frames = [] + last_selected_frame_np = None - # 获取单帧的尺寸 + for i, current_frame in enumerate(all_frames): + current_frame_np = np.array(current_frame) + + # 第一帧总是要选的 + if i == 0: + selected_frames.append(current_frame) + last_selected_frame_np = current_frame_np + continue + + # 计算和上一张选中帧的差异(均方误差 MSE) + if last_selected_frame_np is not None: + mse = np.mean((current_frame_np - last_selected_frame_np) ** 2) + # logger.trace(f"帧 {i} 与上一选中帧的 MSE: {mse}") # 可以取消注释来看差异值 + + # 如果差异够大,就选它! + if mse > similarity_threshold: + selected_frames.append(current_frame) + last_selected_frame_np = current_frame_np + # 检查是不是选够了 + if len(selected_frames) >= max_frames: + # logger.debug(f"已选够 {max_frames} 帧,停止选择。") + break + # 如果差异不大就跳过这一帧啦 + + # --- 帧选择逻辑结束 --- + + # 如果选择后连一帧都没有(比如GIF只有一帧且后续处理失败?)或者原始GIF就没帧,也返回None + if not selected_frames: + logger.warning("处理后没有选中任何帧") + return None + + # logger.debug(f"总帧数: {len(all_frames)}, 选中帧数: {len(selected_frames)}") + + # 获取选中的第一帧的尺寸(假设所有帧尺寸一致) frame_width, frame_height = selected_frames[0].size # 计算目标尺寸,保持宽高比 target_height = 200 # 固定高度 + # 防止除以零 + if frame_height == 0: + logger.error("帧高度为0,无法计算缩放尺寸") + return None target_width = int((target_height / frame_height) * frame_width) + # 宽度也不能是0 + if target_width == 0: + logger.warning(f"计算出的目标宽度为0 (原始尺寸 {frame_width}x{frame_height}),调整为1") + target_width = 1 - # 调整所有帧的大小 + + # 调整所有选中帧的大小 resized_frames = [ frame.resize((target_width, target_height), Image.Resampling.LANCZOS) for frame in selected_frames ] # 创建拼接图像 total_width = target_width * len(resized_frames) + # 防止总宽度为0 + if total_width == 0 and len(resized_frames) > 0: + logger.warning("计算出的总宽度为0,但有选中帧,可能目标宽度太小") + # 至少给点宽度吧 + total_width = len(resized_frames) + elif total_width == 0: + logger.error("计算出的总宽度为0且无选中帧") + return None + + combined_image = Image.new("RGB", (total_width, target_height)) # 水平拼接图像 @@ -289,14 +341,17 @@ class ImageManager: # 转换为base64 buffer = io.BytesIO() - combined_image.save(buffer, format="JPEG", quality=85) + combined_image.save(buffer, format="JPEG", quality=85) # 保存为JPEG result_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8") return result_base64 + except MemoryError: + logger.error("GIF转换失败: 内存不足,可能是GIF太大或帧数太多") + return None # 内存不够啦 except Exception as e: - logger.error(f"GIF转换失败: {str(e)}") - return None + logger.error(f"GIF转换失败: {str(e)}", exc_info=True) # 记录详细错误信息 + return None # 其他错误也返回None # 创建全局单例 diff --git a/src/plugins/emoji_system/emoji_manager.py b/src/plugins/emoji_system/emoji_manager.py index bcabf104..501e4e89 100644 --- a/src/plugins/emoji_system/emoji_manager.py +++ b/src/plugins/emoji_system/emoji_manager.py @@ -195,7 +195,7 @@ class EmojiManager: self._scan_task = None self.vlm = LLMRequest(model=global_config.vlm, temperature=0.3, max_tokens=1000, request_type="emoji") self.llm_emotion_judge = LLMRequest( - model=global_config.llm_emotion_judge, max_tokens=600, temperature=0.8, request_type="emoji" + model=global_config.llm_summary, max_tokens=600, temperature=0.8, request_type="emoji" ) # 更高的温度,更少的token(后续可以根据情绪来调整温度) self.emoji_num = 0 @@ -719,10 +719,10 @@ class EmojiManager: # 调用AI获取描述 if image_format == "gif" or image_format == "GIF": image_base64 = image_manager.transform_gif(image_base64) - prompt = "这是一个动态图表情包,每一张图代表了动态图的某一帧,黑色背景代表透明,详细描述一下表情包表达的情感和内容,请关注其幽默和讽刺意味" + prompt = "这是一个动态图表情包,每一张图代表了动态图的某一帧,黑色背景代表透明,描述一下表情包表达的情感和内容,你可以关注其幽默和讽刺意味,必须从互联网梗,meme的角度去分析" description, _ = await self.vlm.generate_response_for_image(prompt, image_base64, "jpg") else: - prompt = "这是一个表情包,请详细描述一下表情包所表达的情感和内容,请关注其幽默和讽刺意味" + prompt = "这是一个表情包,请详细描述一下表情包所表达的情感和内容,你可以关注其幽默和讽刺意味,必须从互联网梗,meme的角度去分析" description, _ = await self.vlm.generate_response_for_image(prompt, image_base64, image_format) # 审核表情包 @@ -742,10 +742,10 @@ class EmojiManager: # 分析情感含义 emotion_prompt = f""" 基于这个表情包的描述:'{description}',请列出1-2个可能的情感标签,每个标签用一个词组表示,格式如下: - 幽默的讽刺 - 悲伤的无奈 - 愤怒的抗议 - 愤怒的讽刺 + 幽默的讽刺,适用于调侃或吐槽场景 + 悲伤的无奈,适用于表达无力感或失望 + 愤怒的抗议,适用于表达不满或反对 + 愤怒的讽刺,适用于尖锐批评或反讽 直接输出词组,词组检用逗号分隔。""" emotions_text, _ = await self.llm_emotion_judge.generate_response_async(emotion_prompt, temperature=0.7) From 7d19a6728f80b353f830049f13e94e23cc76a416 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 30 Apr 2025 17:50:20 +0800 Subject: [PATCH 10/20] =?UTF-8?q?update=EF=BC=9A=E5=8F=91=E5=B8=830.6.3?= =?UTF-8?q?=E5=89=8D=E7=9A=84=E5=87=86=E5=A4=87=E5=B7=A5=E4=BD=9C=EF=BC=8C?= =?UTF-8?q?changlog=EF=BC=8Creadme=EF=BC=8C=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 31 +++----- src/api/config_api.py | 2 +- src/config/config.py | 6 +- .../tool_can_use/lpmm_get_knowledge.py | 2 +- src/heart_flow/0.6Bing.md | 12 ++- .../heartFC_chat/normal_chat_generator.py | 2 +- src/plugins/memory_system/Hippocampus.py | 6 +- src/plugins/memory_system/memory_config.py | 6 +- template/bot_config_template.toml | 74 ++++++++----------- (临时版)聊天兴趣监控.bat.bat | 26 +++++++ ...(测试版)麦麦生成人格 copy.bat | 0 11 files changed, 90 insertions(+), 77 deletions(-) create mode 100644 (临时版)聊天兴趣监控.bat.bat rename (测试版)麦麦生成人格.bat => (测试版)麦麦生成人格 copy.bat (100%) diff --git a/README.md b/README.md index 820d7d62..58cb82c7 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ ### 📢 版本信息 -**最新版本: v0.6.2** ([查看更新日志](changelogs/changelog.md)) +**最新版本: v0.6.3** ([查看更新日志](changelogs/changelog.md)) > [!WARNING] > 请阅读教程后更新!!!!!!! > 请阅读教程后更新!!!!!!! @@ -110,19 +110,20 @@ - [🚀 最新版本部署教程](https://docs.mai-mai.org/manual/deployment/mmc_deploy_windows.html) - 基于MaiCore的新版本部署方式(与旧版本不兼容) -## 🎯 功能介绍 +## 🎯 0.6.3 功能介绍 | 模块 | 主要功能 | 特点 | |----------|------------------------------------------------------------------|-------| -| 💬 聊天系统 | • 心流/推理聊天
• 关键词主动发言
• 多模型支持
• 动态prompt构建
• 私聊功能(PFC) | 拟人化交互 | -| 🧠 心流系统 | • 实时思考生成
• 自动启停机制
• 日程系统联动
• 工具调用能力 | 智能化决策 | -| 🧠 记忆系统 | • 优化记忆抽取
• 海马体记忆机制
• 聊天记录概括 | 持久化记忆 | -| 😊 表情系统 | • 情绪匹配发送
• GIF支持
• 自动收集与审查 | 丰富表达 | +| 💬 聊天系统 | • **统一调控不同回复逻辑**
• 智能交互模式 (普通聊天/专注聊天)
• 关键词主动发言
• 多模型支持
• 动态prompt构建
• 私聊功能(PFC)增强 | 拟人化交互 | +| 🧠 心流系统 | • 实时思考生成
• **智能状态管理**
• **概率回复机制**
• 自动启停机制
• 日程系统联动
• **上下文感知工具调用** | 智能化决策 | +| 🧠 记忆系统 | • **记忆整合与提取**
• 海马体记忆机制
• 聊天记录概括 | 持久化记忆 | +| 😊 表情系统 | • **全新表情包系统**
• **优化选择逻辑**
• 情绪匹配发送
• GIF支持
• 自动收集与审查 | 丰富表达 | | 📅 日程系统 | • 动态日程生成
• 自定义想象力
• 思维流联动 | 智能规划 | -| 👥 关系系统 | • 关系管理优化
• 丰富接口支持
• 个性化交互 | 深度社交 | +| 👥 关系系统 | • **工具调用动态更新**
• 关系管理优化
• 丰富接口支持
• 个性化交互 | 深度社交 | | 📊 统计系统 | • 使用数据统计
• LLM调用记录
• 实时控制台显示 | 数据可视 | -| 🔧 系统功能 | • 优雅关闭机制
• 自动数据保存
• 异常处理完善 | 稳定可靠 | -| 🛠️ 工具系统 | • 知识获取工具
• 自动注册机制
• 多工具支持 | 扩展功能 | +| 🛠️ 工具系统 | • **LPMM知识库集成**
• **上下文感知调用**
• 知识获取工具
• 自动注册机制
• 多工具支持 | 扩展功能 | +| 📚 **知识库(LPMM)** | • **全新LPMM系统**
• **强大的信息检索能力** | 知识增强 | +| ✨ **昵称系统** | • **自动为群友取昵称**
• **降低认错人概率** (早期阶段) | 身份识别 | ## 📐 项目架构 @@ -142,18 +143,6 @@ graph TD E --> M[情绪识别] ``` - -## 开发计划TODO:LIST - -- 人格功能:WIP -- 对特定对象的侧写功能 -- 图片发送,转发功能:WIP -- 幽默和meme功能:WIP -- 兼容gif的解析和保存 -- 小程序转发链接解析 -- 修复已知bug -- 自动生成的回复逻辑,例如自生成的回复方向,回复风格 - ## ✍️如何给本项目报告BUG/提交建议/做贡献 MaiCore是一个开源项目,我们非常欢迎你的参与。你的贡献,无论是提交bug报告、功能需求还是代码pr,都对项目非常宝贵。我们非常感谢你的支持!🎉 但无序的讨论会降低沟通效率,进而影响问题的解决速度,因此在提交任何贡献前,请务必先阅读本项目的[贡献指南](depends-data/CONTRIBUTE.md)(待补完) diff --git a/src/api/config_api.py b/src/api/config_api.py index fbc31433..025888d8 100644 --- a/src/api/config_api.py +++ b/src/api/config_api.py @@ -132,7 +132,7 @@ class BotConfig: # llm_reasoning_minor: Dict[str, str] llm_normal: Dict[str, str] # LLM普通 llm_topic_judge: Dict[str, str] # LLM话题判断 - llm_summary_by_topic: Dict[str, str] # LLM话题总结 + llm_summary: Dict[str, str] # LLM话题总结 llm_emotion_judge: Dict[str, str] # LLM情感判断 embedding: Dict[str, str] # 嵌入 vlm: Dict[str, str] # VLM diff --git a/src/config/config.py b/src/config/config.py index f7e57542..388ee55b 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -279,8 +279,7 @@ class BotConfig: # llm_reasoning_minor: Dict[str, str] = field(default_factory=lambda: {}) llm_normal: Dict[str, str] = field(default_factory=lambda: {}) llm_topic_judge: Dict[str, str] = field(default_factory=lambda: {}) - llm_summary_by_topic: Dict[str, str] = field(default_factory=lambda: {}) - llm_emotion_judge: Dict[str, str] = field(default_factory=lambda: {}) + llm_summary: Dict[str, str] = field(default_factory=lambda: {}) embedding: Dict[str, str] = field(default_factory=lambda: {}) vlm: Dict[str, str] = field(default_factory=lambda: {}) moderation: Dict[str, str] = field(default_factory=lambda: {}) @@ -481,8 +480,7 @@ class BotConfig: # "llm_reasoning_minor", "llm_normal", "llm_topic_judge", - "llm_summary_by_topic", - "llm_emotion_judge", + "llm_summary", "vlm", "embedding", "llm_tool_use", diff --git a/src/do_tool/tool_can_use/lpmm_get_knowledge.py b/src/do_tool/tool_can_use/lpmm_get_knowledge.py index 7aab8495..4dba1bc7 100644 --- a/src/do_tool/tool_can_use/lpmm_get_knowledge.py +++ b/src/do_tool/tool_can_use/lpmm_get_knowledge.py @@ -14,7 +14,7 @@ class SearchKnowledgeFromLPMMTool(BaseTool): """从LPMM知识库中搜索相关信息的工具""" name = "lpmm_search_knowledge" - description = "从知识库中搜索相关信息" + description = "从知识库中搜索相关信息,如果你需要知识,就使用这个工具" parameters = { "type": "object", "properties": { diff --git a/src/heart_flow/0.6Bing.md b/src/heart_flow/0.6Bing.md index 6af057e2..de5628e7 100644 --- a/src/heart_flow/0.6Bing.md +++ b/src/heart_flow/0.6Bing.md @@ -81,4 +81,14 @@ - **基于人格生成预设知识:** - 开发利用 LLM 和人格配置生成背景知识的功能。 - 这些知识应符合角色的行为风格和可能的经历。 - - 作为一种"冷启动"或丰富角色深度的方式。 \ No newline at end of file + - 作为一种"冷启动"或丰富角色深度的方式。 + + +## 开发计划TODO:LIST + +- 人格功能:WIP +- 对特定对象的侧写功能 +- 图片发送,转发功能:WIP +- 幽默和meme功能:WIP +- 小程序转发链接解析 +- 自动生成的回复逻辑,例如自生成的回复方向,回复风格 \ No newline at end of file diff --git a/src/plugins/heartFC_chat/normal_chat_generator.py b/src/plugins/heartFC_chat/normal_chat_generator.py index 0ad9707f..02baf94d 100644 --- a/src/plugins/heartFC_chat/normal_chat_generator.py +++ b/src/plugins/heartFC_chat/normal_chat_generator.py @@ -29,7 +29,7 @@ class NormalChatGenerator: ) self.model_sum = LLMRequest( - model=global_config.llm_summary_by_topic, temperature=0.7, max_tokens=3000, request_type="relation" + model=global_config.llm_summary, temperature=0.7, max_tokens=3000, request_type="relation" ) self.current_model_type = "r1" # 默认使用 R1 self.current_model_name = "unknown model" diff --git a/src/plugins/memory_system/Hippocampus.py b/src/plugins/memory_system/Hippocampus.py index e5aa096f..7a5fc1a8 100644 --- a/src/plugins/memory_system/Hippocampus.py +++ b/src/plugins/memory_system/Hippocampus.py @@ -189,7 +189,7 @@ class Hippocampus: def __init__(self): self.memory_graph = MemoryGraph() self.llm_topic_judge = None - self.llm_summary_by_topic = None + self.llm_summary = None self.entorhinal_cortex = None self.parahippocampal_gyrus = None self.config = None @@ -203,7 +203,7 @@ class Hippocampus: # 从数据库加载记忆图 self.entorhinal_cortex.sync_memory_from_db() self.llm_topic_judge = LLMRequest(self.config.llm_topic_judge, request_type="memory") - self.llm_summary_by_topic = LLMRequest(self.config.llm_summary_by_topic, request_type="memory") + self.llm_summary = LLMRequest(self.config.llm_summary, request_type="memory") def get_all_node_names(self) -> list: """获取记忆图中所有节点的名字列表""" @@ -1169,7 +1169,7 @@ class ParahippocampalGyrus: # 调用修改后的 topic_what,不再需要 time_info topic_what_prompt = self.hippocampus.topic_what(input_text, topic) try: - task = self.hippocampus.llm_summary_by_topic.generate_response_async(topic_what_prompt) + task = self.hippocampus.llm_summary.generate_response_async(topic_what_prompt) tasks.append((topic.strip(), task)) except Exception as e: logger.error(f"生成话题 '{topic}' 的摘要时发生错误: {e}") diff --git a/src/plugins/memory_system/memory_config.py b/src/plugins/memory_system/memory_config.py index b2fb7280..aa8850d3 100644 --- a/src/plugins/memory_system/memory_config.py +++ b/src/plugins/memory_system/memory_config.py @@ -24,7 +24,7 @@ class MemoryConfig: consolidate_memory_interval: int # 记忆整合间隔 llm_topic_judge: str # 话题判断模型 - llm_summary_by_topic: str # 话题总结模型 + llm_summary: str # 话题总结模型 @classmethod def from_global_config(cls, global_config): @@ -44,7 +44,7 @@ class MemoryConfig: 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_summary=getattr( + global_config, "llm_summary", "default_summary_model" ), # 添加默认模型名 ) diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 2be1ca4f..23368cca 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -197,55 +197,44 @@ pfc_chatting = false # 是否启用PFC聊天,该功能仅作用于私聊,与 # stream = : 用于指定模型是否是使用流式输出 # 如果不指定,则该项是 False -[model.llm_reasoning] #只在回复模式为reasoning时启用 +#这个模型必须是推理模型 +[model.llm_reasoning] # 一般聊天模式的推理回复模型 name = "Pro/deepseek-ai/DeepSeek-R1" -# name = "Qwen/QwQ-32B" provider = "SILICONFLOW" -pri_in = 4 #模型的输入价格(非必填,可以记录消耗) -pri_out = 16 #模型的输出价格(非必填,可以记录消耗) +pri_in = 1.0 #模型的输入价格(非必填,可以记录消耗) +pri_out = 4.0 #模型的输出价格(非必填,可以记录消耗) -#非推理模型 - -[model.llm_normal] #V3 回复模型1 主要回复模型,默认temp 0.2 如果你使用的是老V3或者其他模型,请自己修改temp参数 +[model.llm_normal] #V3 回复模型 专注和一般聊天模式共用的回复模型 name = "Pro/deepseek-ai/DeepSeek-V3" provider = "SILICONFLOW" pri_in = 2 #模型的输入价格(非必填,可以记录消耗) pri_out = 8 #模型的输出价格(非必填,可以记录消耗) +#默认temp 0.2 如果你使用的是老V3或者其他模型,请自己修改temp参数 temp = 0.2 #模型的温度,新V3建议0.1-0.3 -[model.llm_emotion_judge] #表情包判断 -name = "Qwen/Qwen2.5-14B-Instruct" -provider = "SILICONFLOW" -pri_in = 0.7 -pri_out = 0.7 - -[model.llm_topic_judge] #记忆主题判断:建议使用qwen2.5 7b +[model.llm_topic_judge] #主题判断模型:建议使用qwen2.5 7b name = "Pro/Qwen/Qwen2.5-7B-Instruct" provider = "SILICONFLOW" -pri_in = 0 -pri_out = 0 +pri_in = 0.35 +pri_out = 0.35 -[model.llm_summary_by_topic] #概括模型,建议使用qwen2.5 32b 及以上 +[model.llm_summary] #概括模型,建议使用qwen2.5 32b 及以上 name = "Qwen/Qwen2.5-32B-Instruct" provider = "SILICONFLOW" pri_in = 1.26 pri_out = 1.26 -[model.llm_tool_use] #工具调用模型,需要使用支持工具调用的模型,建议使用qwen2.5 32b -name = "Qwen/Qwen2.5-32B-Instruct" -provider = "SILICONFLOW" -pri_in = 1.26 -pri_out = 1.26 - -# 识图模型 - -[model.vlm] #图像识别 +[model.vlm] # 图像识别模型 name = "Pro/Qwen/Qwen2.5-VL-7B-Instruct" provider = "SILICONFLOW" pri_in = 0.35 pri_out = 0.35 - +[model.llm_heartflow] # 用于控制麦麦是否参与聊天的模型 +name = "Qwen/Qwen2.5-32B-Instruct" +provider = "SILICONFLOW" +pri_in = 1.26 +pri_out = 1.26 [model.llm_observation] #观察模型,压缩聊天内容,建议用免费的 # name = "Pro/Qwen/Qwen2.5-7B-Instruct" @@ -254,19 +243,18 @@ provider = "SILICONFLOW" pri_in = 0 pri_out = 0 -[model.llm_sub_heartflow] #子心流:认真水群时,生成麦麦的内心想法 -name = "Qwen/Qwen2.5-72B-Instruct" +[model.llm_sub_heartflow] #心流:认真水群时,生成麦麦的内心想法,必须使用具有工具调用能力的模型 +name = "Pro/deepseek-ai/DeepSeek-V3" provider = "SILICONFLOW" -pri_in = 4.13 -pri_out = 4.13 -temp = 0.7 #模型的温度,新V3建议0.1-0.3 +pri_in = 2 +pri_out = 8 +temp = 0.3 #模型的温度,新V3建议0.1-0.3 - -[model.llm_plan] #决策模型:认真水群时,负责决定麦麦该做什么 -name = "Qwen/Qwen2.5-32B-Instruct" +[model.llm_plan] #决策:认真水群时,负责决定麦麦该做什么 +name = "Pro/deepseek-ai/DeepSeek-V3" provider = "SILICONFLOW" -pri_in = 1.26 -pri_out = 1.26 +pri_in = 2 +pri_out = 8 #嵌入模型 @@ -303,11 +291,13 @@ pri_in = 2 pri_out = 8 -#此模型暂时没有使用!! -#此模型暂时没有使用!! -#此模型暂时没有使用!! -[model.llm_heartflow] #心流 -# name = "Pro/Qwen/Qwen2.5-7B-Instruct" +#以下模型暂时没有使用!! +#以下模型暂时没有使用!! +#以下模型暂时没有使用!! +#以下模型暂时没有使用!! +#以下模型暂时没有使用!! + +[model.llm_tool_use] #工具调用模型,需要使用支持工具调用的模型,建议使用qwen2.5 32b name = "Qwen/Qwen2.5-32B-Instruct" provider = "SILICONFLOW" pri_in = 1.26 diff --git a/(临时版)聊天兴趣监控.bat.bat b/(临时版)聊天兴趣监控.bat.bat new file mode 100644 index 00000000..f26d14de --- /dev/null +++ b/(临时版)聊天兴趣监控.bat.bat @@ -0,0 +1,26 @@ +@echo off +CHCP 65001 > nul +setlocal enabledelayedexpansion + +REM 查找venv虚拟环境 +set "venv_path=%~dp0venv\Scripts\activate.bat" +if not exist "%venv_path%" ( + echo 错误: 未找到虚拟环境,请确保venv目录存在 + pause + exit /b 1 +) + +REM 激活虚拟环境 +call "%venv_path%" +if %ERRORLEVEL% neq 0 ( + echo 错误: 虚拟环境激活失败 + pause + exit /b 1 +) + +echo 虚拟环境已激活,正在启动 GUI... + +REM 运行 Python 脚本 +python scripts/interest_monitor_gui.py + +pause \ No newline at end of file diff --git a/(测试版)麦麦生成人格.bat b/(测试版)麦麦生成人格 copy.bat similarity index 100% rename from (测试版)麦麦生成人格.bat rename to (测试版)麦麦生成人格 copy.bat From 5963214d95346e1080af74c2352af26796401f26 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 30 Apr 2025 17:50:47 +0800 Subject: [PATCH 11/20] ruff fix --- src/config/config.py | 36 ++++---- src/heart_flow/background_tasks.py | 10 +-- src/heart_flow/heartflow.py | 1 + src/heart_flow/mai_state_manager.py | 8 +- src/heart_flow/sub_heartflow.py | 5 +- src/heart_flow/subheartflow_manager.py | 87 ++++++++++++------- src/plugins/chat/utils_image.py | 36 ++++---- src/plugins/heartFC_chat/heartFC_chat.py | 9 +- .../heartFC_chat/heartflow_prompt_builder.py | 2 +- src/plugins/heartFC_chat/normal_chat.py | 4 +- src/plugins/memory_system/memory_config.py | 4 +- 11 files changed, 108 insertions(+), 94 deletions(-) diff --git a/src/config/config.py b/src/config/config.py index 388ee55b..e6cf16d4 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -170,38 +170,34 @@ class BotConfig: SCHEDULE_TEMPERATURE: float = 0.5 # 日程表温度,建议0.5-1.0 TIME_ZONE: str = "Asia/Shanghai" # 时区 - # chat - allow_focus_mode: bool = True # 是否允许专注聊天状态 + allow_focus_mode: bool = True # 是否允许专注聊天状态 + + base_normal_chat_num: int = 3 # 最多允许多少个群进行普通聊天 + base_focused_chat_num: int = 2 # 最多允许多少个群进行专注聊天 - base_normal_chat_num: int = 3 # 最多允许多少个群进行普通聊天 - base_focused_chat_num: int = 2 # 最多允许多少个群进行专注聊天 - observation_context_size: int = 12 # 心流观察到的最长上下文大小,超过这个值的上下文会被压缩 - + message_buffer: bool = True # 消息缓冲器 - ban_words = set() ban_msgs_regex = set() # focus_chat reply_trigger_threshold: float = 3.0 # 心流聊天触发阈值,越低越容易触发 default_decay_rate_per_second: float = 0.98 # 默认衰减率,越大衰减越慢 - consecutive_no_reply_threshold = 3 - - + consecutive_no_reply_threshold = 3 + compressed_length: int = 5 # 不能大于observation_context_size,心流上下文压缩的最短压缩长度,超过心流观察到的上下文长度,会压缩,最短压缩长度为5 compress_length_limit: int = 5 # 最多压缩份数,超过该数值的压缩上下文会被删除 - # normal_chat model_reasoning_probability: float = 0.7 # 麦麦回答时选择推理模型(主要)模型概率 model_normal_probability: float = 0.3 # 麦麦回答时选择一般模型(次要)模型概率 - + emoji_chance: float = 0.2 # 发送表情包的基础概率 thinking_timeout: int = 120 # 思考时间 - + willing_mode: str = "classical" # 意愿模式 response_willing_amplifier: float = 1.0 # 回复意愿放大系数 response_interested_rate_amplifier: float = 1.0 # 回复兴趣度放大系数 @@ -263,7 +259,7 @@ class BotConfig: enable_response_splitter = True # 是否启用回复分割器 response_max_length = 100 # 回复允许的最大长度 response_max_sentence_num = 3 # 回复允许的最大句子数 - + model_max_output_length: int = 800 # 最大回复长度 # remote @@ -409,7 +405,7 @@ class BotConfig: config.BOT_QQ = str(bot_qq) config.BOT_NICKNAME = bot_config.get("nickname", config.BOT_NICKNAME) config.BOT_ALIAS_NAMES = bot_config.get("alias_names", config.BOT_ALIAS_NAMES) - + def chat(parent: dict): chat_config = parent["chat"] config.allow_focus_mode = chat_config.get("allow_focus_mode", config.allow_focus_mode) @@ -433,7 +429,6 @@ class BotConfig: ) config.emoji_chance = normal_chat_config.get("emoji_chance", config.emoji_chance) config.thinking_timeout = normal_chat_config.get("thinking_timeout", config.thinking_timeout) - config.willing_mode = normal_chat_config.get("willing_mode", config.willing_mode) config.response_willing_amplifier = normal_chat_config.get( @@ -457,9 +452,7 @@ class BotConfig: def focus_chat(parent: dict): focus_chat_config = parent["focus_chat"] config.compressed_length = focus_chat_config.get("compressed_length", config.compressed_length) - config.compress_length_limit = focus_chat_config.get( - "compress_length_limit", config.compress_length_limit - ) + config.compress_length_limit = focus_chat_config.get("compress_length_limit", config.compress_length_limit) config.reply_trigger_threshold = focus_chat_config.get( "reply_trigger_threshold", config.reply_trigger_threshold ) @@ -469,7 +462,6 @@ class BotConfig: config.consecutive_no_reply_threshold = focus_chat_config.get( "consecutive_no_reply_threshold", config.consecutive_no_reply_threshold ) - def model(parent: dict): # 加载模型配置 @@ -634,7 +626,9 @@ class BotConfig: "enable_kaomoji_protection", config.enable_kaomoji_protection ) if config.INNER_VERSION in SpecifierSet(">=1.6.0"): - config.model_max_output_length = response_splitter_config.get("model_max_output_length", config.model_max_output_length) + config.model_max_output_length = response_splitter_config.get( + "model_max_output_length", config.model_max_output_length + ) def groups(parent: dict): groups_config = parent["groups"] diff --git a/src/heart_flow/background_tasks.py b/src/heart_flow/background_tasks.py index dcce26d0..93bc5025 100644 --- a/src/heart_flow/background_tasks.py +++ b/src/heart_flow/background_tasks.py @@ -24,6 +24,7 @@ CLEANUP_INTERVAL_SECONDS = 1200 STATE_UPDATE_INTERVAL_SECONDS = 60 LOG_INTERVAL_SECONDS = 3 + class BackgroundTaskManager: """管理 Heartflow 的后台周期性任务。""" @@ -32,14 +33,13 @@ class BackgroundTaskManager: mai_state_info: MaiStateInfo, # Needs current state info mai_state_manager: MaiStateManager, subheartflow_manager: SubHeartflowManager, - interest_logger: InterestLogger + interest_logger: InterestLogger, ): self.mai_state_info = mai_state_info self.mai_state_manager = mai_state_manager self.subheartflow_manager = subheartflow_manager self.interest_logger = interest_logger - # Task references self._state_update_task: Optional[asyncio.Task] = None self._cleanup_task: Optional[asyncio.Task] = None @@ -100,7 +100,7 @@ class BackgroundTaskManager: ] # 统一启动所有任务 - for task_func,log_level, log_msg, task_attr_name in task_configs: + for task_func, log_level, log_msg, task_attr_name in task_configs: # 检查任务变量是否存在且未完成 current_task_var = getattr(self, task_attr_name) if current_task_var is None or current_task_var.done(): @@ -202,7 +202,7 @@ class BackgroundTaskManager: """调用llm检测是否转换ABSENT-CHAT状态""" logger.info("[状态评估任务] 开始基于LLM评估子心流状态...") await self.subheartflow_manager.sbhf_absent_into_chat() - + async def _normal_chat_timeout_check_work(self): """检查处于CHAT状态的子心流是否因长时间未发言而超时,并将其转为ABSENT""" logger.info("[聊天超时检查] 开始检查处于CHAT状态的子心流...") @@ -257,7 +257,7 @@ class BackgroundTaskManager: await self._run_periodic_loop( task_name="Into Chat", interval=interval, task_func=self._perform_absent_into_chat ) - + async def _run_normal_chat_timeout_check_cycle(self, interval: int): await self._run_periodic_loop( task_name="Normal Chat Timeout Check", interval=interval, task_func=self._normal_chat_timeout_check_work diff --git a/src/heart_flow/heartflow.py b/src/heart_flow/heartflow.py index 30151fdf..bd8bc6ff 100644 --- a/src/heart_flow/heartflow.py +++ b/src/heart_flow/heartflow.py @@ -14,6 +14,7 @@ from src.heart_flow.background_tasks import BackgroundTaskManager # Import Back logger = get_logger("heartflow") + class Heartflow: """主心流协调器,负责初始化并协调各个子系统: - 状态管理 (MaiState) diff --git a/src/heart_flow/mai_state_manager.py b/src/heart_flow/mai_state_manager.py index 5f5a2d32..cd739344 100644 --- a/src/heart_flow/mai_state_manager.py +++ b/src/heart_flow/mai_state_manager.py @@ -5,13 +5,14 @@ from typing import List, Tuple, Optional from src.common.logger_manager import get_logger from src.plugins.moods.moods import MoodManager from src.config.config import global_config + logger = get_logger("mai_state") # -- 状态相关的可配置参数 (可以从 glocal_config 加载) -- # enable_unlimited_hfc_chat = True # 调试用:无限专注聊天 enable_unlimited_hfc_chat = False -prevent_offline_state = True +prevent_offline_state = True # 目前默认不启用OFFLINE状态 # 不同状态下普通聊天的最大消息数 @@ -19,13 +20,12 @@ base_normal_chat_num = global_config.base_normal_chat_num base_focused_chat_num = global_config.base_focused_chat_num - -MAX_NORMAL_CHAT_NUM_PEEKING = int(base_normal_chat_num/2) +MAX_NORMAL_CHAT_NUM_PEEKING = int(base_normal_chat_num / 2) MAX_NORMAL_CHAT_NUM_NORMAL = base_normal_chat_num MAX_NORMAL_CHAT_NUM_FOCUSED = base_normal_chat_num + 1 # 不同状态下专注聊天的最大消息数 -MAX_FOCUSED_CHAT_NUM_PEEKING = int(base_focused_chat_num/2) +MAX_FOCUSED_CHAT_NUM_PEEKING = int(base_focused_chat_num / 2) MAX_FOCUSED_CHAT_NUM_NORMAL = base_focused_chat_num MAX_FOCUSED_CHAT_NUM_FOCUSED = base_focused_chat_num + 2 diff --git a/src/heart_flow/sub_heartflow.py b/src/heart_flow/sub_heartflow.py index 9b9ec96d..6b9ccd64 100644 --- a/src/heart_flow/sub_heartflow.py +++ b/src/heart_flow/sub_heartflow.py @@ -5,7 +5,6 @@ import time from typing import Optional, List, Dict, Tuple, Callable, Coroutine import traceback from src.common.logger_manager import get_logger -import random from src.plugins.chat.message import MessageRecv from src.plugins.chat.chat_stream import chat_manager import math @@ -153,8 +152,6 @@ class InterestChatting: "above_threshold": self.above_threshold, } - - # --- 新增后台更新任务相关方法 --- async def _run_update_loop(self, update_interval: float = 1.0): """后台循环,定期更新兴趣和回复概率。""" @@ -472,7 +469,7 @@ class SubHeartflow: async def get_interest_state(self) -> dict: return await self.interest_chatting.get_state() - + def get_normal_chat_last_speak_time(self) -> float: if self.normal_chat_instance: return self.normal_chat_instance.last_speak_time diff --git a/src/heart_flow/subheartflow_manager.py b/src/heart_flow/subheartflow_manager.py index ebb70f44..b084d038 100644 --- a/src/heart_flow/subheartflow_manager.py +++ b/src/heart_flow/subheartflow_manager.py @@ -29,7 +29,8 @@ logger = get_logger("subheartflow_manager") # 子心流管理相关常量 INACTIVE_THRESHOLD_SECONDS = 3600 # 子心流不活跃超时时间(秒) -NORMAL_CHAT_TIMEOUT_SECONDS = 30 * 60 # 30分钟 +NORMAL_CHAT_TIMEOUT_SECONDS = 30 * 60 # 30分钟 + class SubHeartflowManager: """管理所有活跃的 SubHeartflow 实例。""" @@ -341,13 +342,12 @@ class SubHeartflowManager: async with self._lock: # 1. 筛选出所有 ABSENT 状态的子心流 absent_subflows = [ - hf for hf in self.subheartflows.values() - if hf.chat_state.chat_status == ChatState.ABSENT + hf for hf in self.subheartflows.values() if hf.chat_state.chat_status == ChatState.ABSENT ] if not absent_subflows: - logger.debug("没有摸鱼的子心流可以评估。") # 日志太频繁,注释掉 - return # 没有目标,直接返回 + logger.debug("没有摸鱼的子心流可以评估。") # 日志太频繁,注释掉 + return # 没有目标,直接返回 # 2. 随机选一个幸运儿 sub_hf_to_evaluate = random.choice(absent_subflows) @@ -358,8 +358,10 @@ class SubHeartflowManager: # 3. 检查 CHAT 上限 current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT) if current_chat_count >= chat_limit: - logger.debug(f"{log_prefix} 想看看能不能聊,但是聊天太多了, ({current_chat_count}/{chat_limit}) 满了。") - return # 满了,这次就算了 + logger.debug( + f"{log_prefix} 想看看能不能聊,但是聊天太多了, ({current_chat_count}/{chat_limit}) 满了。" + ) + return # 满了,这次就算了 # --- 获取 FOCUSED 计数 --- current_focused_count = self.count_subflows_by_state_nolock(ChatState.FOCUSED) @@ -369,7 +371,7 @@ class SubHeartflowManager: chatting_group_names = [] focused_group_names = [] for flow_id, hf in self.subheartflows.items(): - stream_name = chat_manager.get_stream_name(flow_id) or str(flow_id) # 保证有名字 + stream_name = chat_manager.get_stream_name(flow_id) or str(flow_id) # 保证有名字 if hf.chat_state.chat_status == ChatState.CHAT: chatting_group_names.append(stream_name) elif hf.chat_state.chat_status == ChatState.FOCUSED: @@ -384,30 +386,34 @@ class SubHeartflowManager: mai_state_description = f"你当前状态: {current_mai_state.value}。" individuality = Individuality.get_instance() - personality_prompt = individuality.get_prompt(x_person=2, level = 2) + personality_prompt = individuality.get_prompt(x_person=2, level=2) prompt_personality = f"你正在扮演名为{individuality.name}的人类,{personality_prompt}" # --- 修改:在 prompt 中加入当前聊天计数和群名信息 (条件显示) --- chat_status_lines = [] if chatting_group_names: - chat_status_lines.append(f"正在闲聊 ({current_chat_count}/{chat_limit}): {', '.join(chatting_group_names)}") + chat_status_lines.append( + f"正在闲聊 ({current_chat_count}/{chat_limit}): {', '.join(chatting_group_names)}" + ) if focused_group_names: - chat_status_lines.append(f"正在专注 ({current_focused_count}/{focused_limit}): {', '.join(focused_group_names)}") + chat_status_lines.append( + f"正在专注 ({current_focused_count}/{focused_limit}): {', '.join(focused_group_names)}" + ) - chat_status_prompt = "当前没有在任何群聊中。" # 默认消息喵~ + chat_status_prompt = "当前没有在任何群聊中。" # 默认消息喵~ if chat_status_lines: - chat_status_prompt = "当前聊天情况:\n" + "\n".join(chat_status_lines) # 拼接状态信息 + chat_status_prompt = "当前聊天情况:\n" + "\n".join(chat_status_lines) # 拼接状态信息 prompt = ( f"{prompt_personality}\\n" f"你当前没在 [{stream_name}] 群聊天。\\n" f"{mai_state_description}\\n" - f"{chat_status_prompt}\\n" # <-- 喵!用了新的状态信息~ + f"{chat_status_prompt}\\n" # <-- 喵!用了新的状态信息~ f"{_observation_summary}\\n---\\n" f"基于以上信息,你想不想开始在这个群闲聊?\\n" f"请说明理由,并以 JSON 格式回答,包含 'decision' (布尔值) 和 'reason' (字符串)。\\n" - f'例如:{{\"decision\": true, \"reason\": \"看起来挺热闹的,插个话\"}}\\n' - f'例如:{{\"decision\": false, \"reason\": \"已经聊了好多,休息一下\"}}\\n' + f'例如:{{"decision": true, "reason": "看起来挺热闹的,插个话"}}\\n' + f'例如:{{"decision": false, "reason": "已经聊了好多,休息一下"}}\\n' f"请只输出有效的 JSON 对象。" ) # --- 结束修改 --- @@ -417,11 +423,11 @@ class SubHeartflowManager: if yao_kai_shi_liao_ma is None: logger.debug(f"{log_prefix} 问AI想不想聊失败了,这次算了。") - return # 评估失败,结束 + return # 评估失败,结束 if not yao_kai_shi_liao_ma: logger.info(f"{log_prefix} 现在不想聊这个群。") - return # 不想聊,结束 + return # 不想聊,结束 # --- 5. AI想聊,再次检查额度并尝试转换 --- # 再次检查以防万一 @@ -435,7 +441,9 @@ class SubHeartflowManager: if sub_hf_to_evaluate.chat_state.chat_status == ChatState.CHAT: logger.debug(f"{log_prefix} 成功进入聊天状态!本次评估圆满结束。") else: - logger.warning(f"{log_prefix} 奇怪,尝试进入聊天状态失败了。当前状态: {sub_hf_to_evaluate.chat_state.chat_status.value}") + logger.warning( + f"{log_prefix} 奇怪,尝试进入聊天状态失败了。当前状态: {sub_hf_to_evaluate.chat_state.chat_status.value}" + ) else: logger.warning( f"{log_prefix} AI说想聊,但是刚问完就没空位了 ({current_chat_count_before_change}/{chat_limit})。真不巧,下次再说吧。" @@ -483,15 +491,19 @@ class SubHeartflowManager: if time_since_last_bb > NORMAL_CHAT_TIMEOUT_SECONDS: should_deactivate = True reason = f"超过 {NORMAL_CHAT_TIMEOUT_SECONDS / 60:.0f} 分钟没 BB" - logger.info(f"{log_prefix} 检测到超时 ({reason}),准备转为 ABSENT。上次活动时间: {last_bot_dong_zuo_time:.0f}") + logger.info( + f"{log_prefix} 检测到超时 ({reason}),准备转为 ABSENT。上次活动时间: {last_bot_dong_zuo_time:.0f}" + ) # else: # logger.debug(f"{log_prefix} Bot活动时间未超时 ({time_since_last_bb:.0f}s < {NORMAL_CHAT_TIMEOUT_SECONDS}s),保持 CHAT 状态。") # else: - # 如果没有记录到Bot的活动时间,暂时不因为超时而转换状态 - # logger.debug(f"{log_prefix} 未找到有效的 Bot 最后活动时间记录,不执行超时检查。") + # 如果没有记录到Bot的活动时间,暂时不因为超时而转换状态 + # logger.debug(f"{log_prefix} 未找到有效的 Bot 最后活动时间记录,不执行超时检查。") except AttributeError: - logger.error(f"{log_prefix} 无法获取 Bot 最后 BB 时间,请确保 SubHeartflow 相关实现正确。跳过超时检查。") + logger.error( + f"{log_prefix} 无法获取 Bot 最后 BB 时间,请确保 SubHeartflow 相关实现正确。跳过超时检查。" + ) except Exception as e: logger.error(f"{log_prefix} 检查 Bot 超时状态时出错: {e}", exc_info=True) @@ -507,9 +519,12 @@ class SubHeartflowManager: logger.warning(f"{log_prefix} 尝试因超时转换为 ABSENT 失败。") if transitioned_to_absent > 0: - logger.info(f"{log_prefix_task} 完成,共检查 {checked_count} 个子心流,{transitioned_to_absent} 个因超时转为 ABSENT。") + logger.info( + f"{log_prefix_task} 完成,共检查 {checked_count} 个子心流,{transitioned_to_absent} 个因超时转为 ABSENT。" + ) # else: # logger.debug(f"{log_prefix_task} 完成,共检查 {checked_count} 个子心流,无超时转换。") + # --- 结束新增 --- async def _llm_evaluate_state_transition(self, prompt: str) -> Optional[bool]: @@ -653,7 +668,7 @@ class SubHeartflowManager: # 仅当子心流处于 FOCUSED 状态时才进行转换 # 因为 HeartFChatting 只在 FOCUSED 状态下运行 if current_state == ChatState.FOCUSED: - target_state = ChatState.ABSENT # 默认目标状态 + target_state = ChatState.ABSENT # 默认目标状态 log_reason = "默认转换" # 决定是去 ABSENT 还是 CHAT @@ -671,14 +686,20 @@ class SubHeartflowManager: if current_chat_count < chat_limit: target_state = ChatState.CHAT log_reason = f"随机选择 CHAT (当前 {current_chat_count}/{chat_limit})" - logger.debug(f"[状态转换请求] {stream_name} ({current_state.value}) 随机决定进入 CHAT,未达上限 ({current_chat_count}/{chat_limit})") + logger.debug( + f"[状态转换请求] {stream_name} ({current_state.value}) 随机决定进入 CHAT,未达上限 ({current_chat_count}/{chat_limit})" + ) else: target_state = ChatState.ABSENT log_reason = f"随机选择 CHAT 但已达上限 ({current_chat_count}/{chat_limit}),转为 ABSENT" - logger.debug(f"[状态转换请求] {stream_name} ({current_state.value}) 随机决定进入 CHAT,但已达上限 ({current_chat_count}/{chat_limit}),改为进入 ABSENT") + logger.debug( + f"[状态转换请求] {stream_name} ({current_state.value}) 随机决定进入 CHAT,但已达上限 ({current_chat_count}/{chat_limit}),改为进入 ABSENT" + ) - #开始转换 - logger.info(f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 尝试转换为 {target_state.value} ({log_reason})") + # 开始转换 + logger.info( + f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 尝试转换为 {target_state.value} ({log_reason})" + ) try: await subflow.change_chat_state(target_state) # 检查最终状态 @@ -686,9 +707,13 @@ class SubHeartflowManager: if final_state == target_state: logger.debug(f"[状态转换请求] {stream_name} 状态已成功转换为 {final_state.value}") else: - logger.warning(f"[状态转换请求] 尝试将 {stream_name} 转换为 {target_state.value} 后,状态实际为 {final_state.value}") + logger.warning( + f"[状态转换请求] 尝试将 {stream_name} 转换为 {target_state.value} 后,状态实际为 {final_state.value}" + ) except Exception as e: - logger.error(f"[状态转换请求] 转换 {stream_name} 到 {target_state.value} 时出错: {e}", exc_info=True) + logger.error( + f"[状态转换请求] 转换 {stream_name} 到 {target_state.value} 时出错: {e}", exc_info=True + ) elif current_state == ChatState.ABSENT: logger.debug(f"[状态转换请求] {stream_name} 已处于 ABSENT 状态,无需转换") else: diff --git a/src/plugins/chat/utils_image.py b/src/plugins/chat/utils_image.py index 95423c7a..f567c527 100644 --- a/src/plugins/chat/utils_image.py +++ b/src/plugins/chat/utils_image.py @@ -257,11 +257,11 @@ class ImageManager: frame = gif.convert("RGB") all_frames.append(frame.copy()) except EOFError: - pass # 读完啦 + pass # 读完啦 if not all_frames: logger.warning("GIF中没有找到任何帧") - return None # 空的GIF直接返回None + return None # 空的GIF直接返回None # --- 新的帧选择逻辑 --- selected_frames = [] @@ -295,8 +295,8 @@ class ImageManager: # 如果选择后连一帧都没有(比如GIF只有一帧且后续处理失败?)或者原始GIF就没帧,也返回None if not selected_frames: - logger.warning("处理后没有选中任何帧") - return None + logger.warning("处理后没有选中任何帧") + return None # logger.debug(f"总帧数: {len(all_frames)}, 选中帧数: {len(selected_frames)}") @@ -307,14 +307,13 @@ class ImageManager: target_height = 200 # 固定高度 # 防止除以零 if frame_height == 0: - logger.error("帧高度为0,无法计算缩放尺寸") - return None + logger.error("帧高度为0,无法计算缩放尺寸") + return None target_width = int((target_height / frame_height) * frame_width) # 宽度也不能是0 if target_width == 0: - logger.warning(f"计算出的目标宽度为0 (原始尺寸 {frame_width}x{frame_height}),调整为1") - target_width = 1 - + logger.warning(f"计算出的目标宽度为0 (原始尺寸 {frame_width}x{frame_height}),调整为1") + target_width = 1 # 调整所有选中帧的大小 resized_frames = [ @@ -325,13 +324,12 @@ class ImageManager: total_width = target_width * len(resized_frames) # 防止总宽度为0 if total_width == 0 and len(resized_frames) > 0: - logger.warning("计算出的总宽度为0,但有选中帧,可能目标宽度太小") - # 至少给点宽度吧 - total_width = len(resized_frames) + logger.warning("计算出的总宽度为0,但有选中帧,可能目标宽度太小") + # 至少给点宽度吧 + total_width = len(resized_frames) elif total_width == 0: - logger.error("计算出的总宽度为0且无选中帧") - return None - + logger.error("计算出的总宽度为0且无选中帧") + return None combined_image = Image.new("RGB", (total_width, target_height)) @@ -341,17 +339,17 @@ class ImageManager: # 转换为base64 buffer = io.BytesIO() - combined_image.save(buffer, format="JPEG", quality=85) # 保存为JPEG + combined_image.save(buffer, format="JPEG", quality=85) # 保存为JPEG result_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8") return result_base64 except MemoryError: logger.error("GIF转换失败: 内存不足,可能是GIF太大或帧数太多") - return None # 内存不够啦 + return None # 内存不够啦 except Exception as e: - logger.error(f"GIF转换失败: {str(e)}", exc_info=True) # 记录详细错误信息 - return None # 其他错误也返回None + logger.error(f"GIF转换失败: {str(e)}", exc_info=True) # 记录详细错误信息 + return None # 其他错误也返回None # 创建全局单例 diff --git a/src/plugins/heartFC_chat/heartFC_chat.py b/src/plugins/heartFC_chat/heartFC_chat.py index 15518a77..99b76f03 100644 --- a/src/plugins/heartFC_chat/heartFC_chat.py +++ b/src/plugins/heartFC_chat/heartFC_chat.py @@ -32,7 +32,7 @@ from src.individuality.individuality import Individuality WAITING_TIME_THRESHOLD = 300 # 等待新消息时间阈值,单位秒 -EMOJI_SEND_PRO = 0.3 # 设置一个概率,比如 30% 才真的发 +EMOJI_SEND_PRO = 0.3 # 设置一个概率,比如 30% 才真的发 CONSECUTIVE_NO_REPLY_THRESHOLD = 3 # 连续不回复的阈值 @@ -982,13 +982,14 @@ class HeartFChatting: # --- 新增:概率性忽略文本回复附带的表情(正确的位置)--- - if action == "text_reply" and emoji_query: logger.debug(f"{self.log_prefix}[Planner] 大模型想让麦麦发文字时带表情: '{emoji_query}'") # 掷骰子看看要不要听它的 if random.random() > EMOJI_SEND_PRO: - logger.info(f"{self.log_prefix}[Planner] 但是麦麦这次不想加表情 ({1-EMOJI_SEND_PRO:.0%}),忽略表情 '{emoji_query}'") - emoji_query = "" # 把表情请求清空,就不发了 + logger.info( + f"{self.log_prefix}[Planner] 但是麦麦这次不想加表情 ({1 - EMOJI_SEND_PRO:.0%}),忽略表情 '{emoji_query}'" + ) + emoji_query = "" # 把表情请求清空,就不发了 else: logger.info(f"{self.log_prefix}[Planner] 好吧,加上表情 '{emoji_query}'") # --- 结束:概率性忽略 --- diff --git a/src/plugins/heartFC_chat/heartflow_prompt_builder.py b/src/plugins/heartFC_chat/heartflow_prompt_builder.py index c4958a9f..c8d636c5 100644 --- a/src/plugins/heartFC_chat/heartflow_prompt_builder.py +++ b/src/plugins/heartFC_chat/heartflow_prompt_builder.py @@ -241,7 +241,7 @@ class PromptBuilder: prompt_ger=prompt_ger, moderation_prompt=await global_prompt_manager.get_prompt_async("moderation_prompt"), ) - + logger.debug(f"focus_chat_prompt: \n{prompt}") return prompt diff --git a/src/plugins/heartFC_chat/normal_chat.py b/src/plugins/heartFC_chat/normal_chat.py index 95250652..c159a329 100644 --- a/src/plugins/heartFC_chat/normal_chat.py +++ b/src/plugins/heartFC_chat/normal_chat.py @@ -43,7 +43,7 @@ class NormalChat: self.mood_manager = MoodManager.get_instance() # MoodManager 保持单例 # 存储此实例的兴趣监控任务 self.start_time = time.time() - + self.last_speak_time = 0 self._chat_task: Optional[asyncio.Task] = None @@ -122,7 +122,7 @@ class NormalChat: await message_manager.add_message(message_set) self.last_speak_time = time.time() - + return first_bot_msg # 改为实例方法 diff --git a/src/plugins/memory_system/memory_config.py b/src/plugins/memory_system/memory_config.py index aa8850d3..b82e54ec 100644 --- a/src/plugins/memory_system/memory_config.py +++ b/src/plugins/memory_system/memory_config.py @@ -44,7 +44,5 @@ class MemoryConfig: 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=getattr( - global_config, "llm_summary", "default_summary_model" - ), # 添加默认模型名 + llm_summary=getattr(global_config, "llm_summary", "default_summary_model"), # 添加默认模型名 ) From 09b1807132261d5dd6cc85d74e8c1052eb9acd76 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 30 Apr 2025 18:16:38 +0800 Subject: [PATCH 12/20] =?UTF-8?q?better=EF=BC=9A=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=A1=A8=E6=83=85=E5=8C=85=E5=A4=84=E7=90=86=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96logger=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/logger.py | 14 +++---- src/common/logger_manager.py | 4 +- src/do_tool/tool_can_use/base_tool.py | 2 +- src/heart_flow/background_tasks.py | 4 +- src/heart_flow/sub_heartflow.py | 8 ++-- src/heart_flow/subheartflow_manager.py | 50 ++++++++++++++--------- src/plugins/chat/message_sender.py | 6 +-- src/plugins/emoji_system/emoji_manager.py | 31 ++++++++------ src/plugins/heartFC_chat/heartFC_chat.py | 2 +- src/plugins/willing/willing_manager.py | 2 +- 10 files changed, 69 insertions(+), 54 deletions(-) diff --git a/src/common/logger.py b/src/common/logger.py index 43972e1f..b5317d58 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -621,22 +621,22 @@ CHAT_IMAGE_STYLE_CONFIG = { }, } -# 兴趣log -INTEREST_STYLE_CONFIG = { +# HFC log +HFC_STYLE_CONFIG = { "advanced": { "console_format": ( "{time:YYYY-MM-DD HH:mm:ss} | " "{level: <8} | " - "兴趣 | " + "专注聊天 | " "{message}" ), - "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 兴趣 | {message}", + "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 专注聊天 | {message}", }, "simple": { "console_format": ( - "{time:MM-DD HH:mm} | 兴趣 | {message}" + "{time:MM-DD HH:mm} | 专注聊天 | {message}" ), - "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 兴趣 | {message}", + "file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 专注聊天 | {message}", }, } @@ -847,7 +847,7 @@ CONFIG_STYLE_CONFIG = CONFIG_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else CONFIG TOOL_USE_STYLE_CONFIG = TOOL_USE_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else TOOL_USE_STYLE_CONFIG["advanced"] PFC_STYLE_CONFIG = PFC_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else PFC_STYLE_CONFIG["advanced"] LPMM_STYLE_CONFIG = LPMM_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else LPMM_STYLE_CONFIG["advanced"] -INTEREST_STYLE_CONFIG = INTEREST_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else INTEREST_STYLE_CONFIG["advanced"] +HFC_STYLE_CONFIG = HFC_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else HFC_STYLE_CONFIG["advanced"] TIANYI_STYLE_CONFIG = TIANYI_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else TIANYI_STYLE_CONFIG["advanced"] MODEL_UTILS_STYLE_CONFIG = MODEL_UTILS_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else MODEL_UTILS_STYLE_CONFIG["advanced"] PROMPT_STYLE_CONFIG = PROMPT_STYLE_CONFIG["simple"] if SIMPLE_OUTPUT else PROMPT_STYLE_CONFIG["advanced"] diff --git a/src/common/logger_manager.py b/src/common/logger_manager.py index ab1861e2..5c553838 100644 --- a/src/common/logger_manager.py +++ b/src/common/logger_manager.py @@ -23,7 +23,7 @@ from src.common.logger import ( PFC_ACTION_PLANNER_STYLE_CONFIG, MAI_STATE_CONFIG, LPMM_STYLE_CONFIG, - INTEREST_STYLE_CONFIG, + HFC_STYLE_CONFIG, TIANYI_STYLE_CONFIG, REMOTE_STYLE_CONFIG, TOPIC_STYLE_CONFIG, @@ -68,7 +68,7 @@ MODULE_LOGGER_CONFIGS = { "pfc_action_planner": PFC_ACTION_PLANNER_STYLE_CONFIG, # PFC私聊规划 "mai_state": MAI_STATE_CONFIG, # 麦麦状态 "lpmm": LPMM_STYLE_CONFIG, # LPMM - "interest": INTEREST_STYLE_CONFIG, # 兴趣 + "hfc": HFC_STYLE_CONFIG, # HFC "tianyi": TIANYI_STYLE_CONFIG, # 天依 "remote": REMOTE_STYLE_CONFIG, # 远程 "topic": TOPIC_STYLE_CONFIG, # 话题 diff --git a/src/do_tool/tool_can_use/base_tool.py b/src/do_tool/tool_can_use/base_tool.py index 47f4a54d..1dd15baf 100644 --- a/src/do_tool/tool_can_use/base_tool.py +++ b/src/do_tool/tool_can_use/base_tool.py @@ -62,7 +62,7 @@ def register_tool(tool_class: Type[BaseTool]): raise ValueError(f"工具类 {tool_class.__name__} 没有定义 name 属性") TOOL_REGISTRY[tool_name] = tool_class - logger.info(f"已注册工具: {tool_name}") + logger.info(f"已注册: {tool_name}") def discover_tools(): diff --git a/src/heart_flow/background_tasks.py b/src/heart_flow/background_tasks.py index 93bc5025..56fee2a9 100644 --- a/src/heart_flow/background_tasks.py +++ b/src/heart_flow/background_tasks.py @@ -200,12 +200,12 @@ class BackgroundTaskManager: async def _perform_absent_into_chat(self): """调用llm检测是否转换ABSENT-CHAT状态""" - logger.info("[状态评估任务] 开始基于LLM评估子心流状态...") + logger.debug("[状态评估任务] 开始基于LLM评估子心流状态...") await self.subheartflow_manager.sbhf_absent_into_chat() async def _normal_chat_timeout_check_work(self): """检查处于CHAT状态的子心流是否因长时间未发言而超时,并将其转为ABSENT""" - logger.info("[聊天超时检查] 开始检查处于CHAT状态的子心流...") + logger.debug("[聊天超时检查] 开始检查处于CHAT状态的子心流...") await self.subheartflow_manager.sbhf_chat_into_absent() async def _perform_cleanup_work(self): diff --git a/src/heart_flow/sub_heartflow.py b/src/heart_flow/sub_heartflow.py index 6b9ccd64..4a48e977 100644 --- a/src/heart_flow/sub_heartflow.py +++ b/src/heart_flow/sub_heartflow.py @@ -299,7 +299,7 @@ class SubHeartflow: chat_stream = chat_manager.get_stream(self.chat_id) self.normal_chat_instance = NormalChat(chat_stream=chat_stream, interest_dict=self.get_interest_dict()) - logger.info(f"{log_prefix} 启动 NormalChat 随便水群...") + logger.info(f"{log_prefix} 开始普通聊天,随便水群...") await self.normal_chat_instance.start_chat() # <--- 修正:调用 start_chat return True except Exception as e: @@ -311,7 +311,7 @@ class SubHeartflow: async def _stop_heart_fc_chat(self): """停止并清理 HeartFChatting 实例""" if self.heart_fc_instance: - logger.info(f"{self.log_prefix} 关闭 HeartFChatting 实例...") + logger.debug(f"{self.log_prefix} 结束专注聊天...") try: await self.heart_fc_instance.shutdown() except Exception as e: @@ -386,7 +386,7 @@ class SubHeartflow: # 移除限额检查逻辑 logger.debug(f"{log_prefix} 准备进入或保持 聊天 状态") if await self._start_normal_chat(): - logger.info(f"{log_prefix} 成功进入或保持 NormalChat 状态。") + # logger.info(f"{log_prefix} 成功进入或保持 NormalChat 状态。") state_changed = True else: logger.error(f"{log_prefix} 启动 NormalChat 失败,无法进入 CHAT 状态。") @@ -416,7 +416,7 @@ class SubHeartflow: self.history_chat_state.append((current_state, self.chat_state_last_time)) logger.info( - f"{log_prefix} 麦麦的聊天状态从 {current_state.value} (持续了 {self.chat_state_last_time} 秒) 变更为 {new_state.value}" + f"{log_prefix} 麦麦的聊天状态从 {current_state.value} (持续了 {int(self.chat_state_last_time)} 秒) 变更为 {new_state.value}" ) self.chat_state.chat_status = new_state diff --git a/src/heart_flow/subheartflow_manager.py b/src/heart_flow/subheartflow_manager.py index b084d038..fe555ea2 100644 --- a/src/heart_flow/subheartflow_manager.py +++ b/src/heart_flow/subheartflow_manager.py @@ -1,7 +1,7 @@ import asyncio import time import random -from typing import Dict, Any, Optional, List +from typing import Dict, Any, Optional, List, Tuple import json # 导入 json 模块 import functools # <-- 新增导入 @@ -23,6 +23,7 @@ from src.individuality.individuality import Individuality import traceback + # 初始化日志记录器 logger = get_logger("subheartflow_manager") @@ -358,7 +359,7 @@ class SubHeartflowManager: # 3. 检查 CHAT 上限 current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT) if current_chat_count >= chat_limit: - logger.debug( + logger.info( f"{log_prefix} 想看看能不能聊,但是聊天太多了, ({current_chat_count}/{chat_limit}) 满了。" ) return # 满了,这次就算了 @@ -419,14 +420,22 @@ class SubHeartflowManager: # --- 结束修改 --- # --- 4. LLM 评估是否想聊 --- - yao_kai_shi_liao_ma = await self._llm_evaluate_state_transition(prompt) + yao_kai_shi_liao_ma, reason = await self._llm_evaluate_state_transition(prompt) + + if reason: + if yao_kai_shi_liao_ma: + logger.info(f"{log_prefix} 打算开始聊,原因是: {reason}") + else: + logger.info(f"{log_prefix} 不打算聊,原因是: {reason}") + else: + logger.info(f"{log_prefix} 结果: {yao_kai_shi_liao_ma}") if yao_kai_shi_liao_ma is None: logger.debug(f"{log_prefix} 问AI想不想聊失败了,这次算了。") return # 评估失败,结束 if not yao_kai_shi_liao_ma: - logger.info(f"{log_prefix} 现在不想聊这个群。") + # logger.info(f"{log_prefix} 现在不想聊这个群。") return # 不想聊,结束 # --- 5. AI想聊,再次检查额度并尝试转换 --- @@ -434,7 +443,7 @@ class SubHeartflowManager: current_chat_count_before_change = self.count_subflows_by_state_nolock(ChatState.CHAT) if current_chat_count_before_change < chat_limit: logger.info( - f"{log_prefix} 想聊,而且还有空位 ({current_chat_count_before_change}/{chat_limit}),这就去聊!" + f"{log_prefix} 想聊,而且还有精力 ({current_chat_count_before_change}/{chat_limit}),这就去聊!" ) await sub_hf_to_evaluate.change_chat_state(ChatState.CHAT) # 确认转换成功 @@ -492,7 +501,7 @@ class SubHeartflowManager: should_deactivate = True reason = f"超过 {NORMAL_CHAT_TIMEOUT_SECONDS / 60:.0f} 分钟没 BB" logger.info( - f"{log_prefix} 检测到超时 ({reason}),准备转为 ABSENT。上次活动时间: {last_bot_dong_zuo_time:.0f}" + f"{log_prefix} 太久没有发言 ({reason}),不看了。上次活动时间: {last_bot_dong_zuo_time:.0f}" ) # else: # logger.debug(f"{log_prefix} Bot活动时间未超时 ({time_since_last_bb:.0f}s < {NORMAL_CHAT_TIMEOUT_SECONDS}s),保持 CHAT 状态。") @@ -509,25 +518,24 @@ class SubHeartflowManager: # --- 执行状态转换(如果超时) --- if should_deactivate: - logger.info(f"{log_prefix} 因超时 ({reason}),尝试转换为 ABSENT 状态。") + logger.debug(f"{log_prefix} 因超时 ({reason}),尝试转换为 ABSENT 状态。") await sub_hf.change_chat_state(ChatState.ABSENT) # 再次检查确保状态已改变 if sub_hf.chat_state.chat_status == ChatState.ABSENT: transitioned_to_absent += 1 - logger.info(f"{log_prefix} 已成功转换为 ABSENT 状态。") + logger.info(f"{log_prefix} 不看了。") else: logger.warning(f"{log_prefix} 尝试因超时转换为 ABSENT 失败。") if transitioned_to_absent > 0: - logger.info( + logger.debug( f"{log_prefix_task} 完成,共检查 {checked_count} 个子心流,{transitioned_to_absent} 个因超时转为 ABSENT。" ) - # else: - # logger.debug(f"{log_prefix_task} 完成,共检查 {checked_count} 个子心流,无超时转换。") + # --- 结束新增 --- - async def _llm_evaluate_state_transition(self, prompt: str) -> Optional[bool]: + async def _llm_evaluate_state_transition(self, prompt: str) -> Tuple[Optional[bool], Optional[str]]: """ 使用 LLM 评估是否应进行状态转换,期望 LLM 返回 JSON 格式。 @@ -544,7 +552,7 @@ class SubHeartflowManager: response_text, _ = await self.llm_state_evaluator.generate_response_async(prompt) # logger.debug(f"{log_prefix} 使用模型 {self.llm_state_evaluator.model_name} 评估") logger.debug(f"{log_prefix} 原始输入: {prompt}") - logger.debug(f"{log_prefix} 原始响应: {response_text}") + logger.debug(f"{log_prefix} 原始评估结果: {response_text}") # --- 解析 JSON 响应 --- try: @@ -555,34 +563,36 @@ class SubHeartflowManager: data = json.loads(cleaned_response) decision = data.get("decision") # 使用 .get() 避免 KeyError + reason = data.get("reason") if isinstance(decision, bool): logger.debug(f"{log_prefix} LLM评估结果 (来自JSON): {'建议转换' if decision else '建议不转换'}") - return decision + + return decision , reason else: logger.warning( f"{log_prefix} LLM 返回的 JSON 中 'decision' 键的值不是布尔型: {decision}。响应: {response_text}" ) - return None # 值类型不正确 + return None, None # 值类型不正确 except json.JSONDecodeError as json_err: logger.warning(f"{log_prefix} LLM 返回的响应不是有效的 JSON: {json_err}。响应: {response_text}") # 尝试在非JSON响应中查找关键词作为后备方案 (可选) if "true" in response_text.lower(): logger.debug(f"{log_prefix} 在非JSON响应中找到 'true',解释为建议转换") - return True + return True, None if "false" in response_text.lower(): logger.debug(f"{log_prefix} 在非JSON响应中找到 'false',解释为建议不转换") - return False - return None # JSON 解析失败,也未找到关键词 + return False, None + return None, None # JSON 解析失败,也未找到关键词 except Exception as parse_err: # 捕获其他可能的解析错误 logger.warning(f"{log_prefix} 解析 LLM JSON 响应时发生意外错误: {parse_err}。响应: {response_text}") - return None + return None, None except Exception as e: logger.error(f"{log_prefix} 调用 LLM 或处理其响应时出错: {e}", exc_info=True) traceback.print_exc() - return None # LLM 调用或处理失败 + return None, None # LLM 调用或处理失败 def count_subflows_by_state(self, state: ChatState) -> int: """统计指定状态的子心流数量""" diff --git a/src/plugins/chat/message_sender.py b/src/plugins/chat/message_sender.py index 0a2199f3..493397bb 100644 --- a/src/plugins/chat/message_sender.py +++ b/src/plugins/chat/message_sender.py @@ -159,16 +159,16 @@ class MessageManager: logger.warning("Processor task already running.") return self._processor_task = asyncio.create_task(self._start_processor_loop()) - logger.info("MessageManager processor task started.") + logger.debug("MessageManager processor task started.") def stop(self): """停止后台处理器任务。""" self._running = False if hasattr(self, "_processor_task") and not self._processor_task.done(): self._processor_task.cancel() - logger.info("MessageManager processor task stopping.") + logger.debug("MessageManager processor task stopping.") else: - logger.info("MessageManager processor task not running or already stopped.") + logger.debug("MessageManager processor task not running or already stopped.") async def get_container(self, chat_id: str) -> MessageContainer: """获取或创建聊天流的消息容器 (异步,使用锁)""" diff --git a/src/plugins/emoji_system/emoji_manager.py b/src/plugins/emoji_system/emoji_manager.py index 501e4e89..72af602f 100644 --- a/src/plugins/emoji_system/emoji_manager.py +++ b/src/plugins/emoji_system/emoji_manager.py @@ -106,7 +106,7 @@ class MaiEmoji: os.remove(destination_path) os.rename(source_path, destination_path) - logger.info(f"[移动] 文件从 {source_path} 移动到 {destination_path}") + logger.debug(f"[移动] 文件从 {source_path} 移动到 {destination_path}") # 更新实例的路径属性为新目录 self.path = EMOJI_REGISTED_DIR except Exception as move_error: @@ -131,7 +131,7 @@ class MaiEmoji: # 使用upsert确保记录存在或被更新 db["emoji"].update_one({"hash": self.hash}, {"$set": emoji_record}, upsert=True) - logger.success(f"[注册] 表情包信息保存到数据库: {self.description}") + logger.success(f"[注册] 表情包信息保存到数据库: {self.description[:30]}...") return True @@ -158,7 +158,7 @@ class MaiEmoji: if os.path.exists(os.path.join(self.path, self.filename)): try: os.remove(os.path.join(self.path, self.filename)) - logger.info(f"[删除] 文件: {os.path.join(self.path, self.filename)}") + logger.debug(f"[删除] 文件: {os.path.join(self.path, self.filename)}") except Exception as e: logger.error(f"[错误] 删除文件失败 {os.path.join(self.path, self.filename)}: {str(e)}") # 继续执行,即使文件删除失败也尝试删除数据库记录 @@ -168,7 +168,7 @@ class MaiEmoji: deleted_in_db = result.deleted_count > 0 if deleted_in_db: - logger.success(f"[删除] 成功删除表情包记录: {self.description}") + logger.info(f"[删除] 表情包 {self.filename} 无对应文件,已删除") # 3. 标记对象已被删除 self.is_deleted = True @@ -195,7 +195,7 @@ class EmojiManager: self._scan_task = None self.vlm = LLMRequest(model=global_config.vlm, temperature=0.3, max_tokens=1000, request_type="emoji") self.llm_emotion_judge = LLMRequest( - model=global_config.llm_summary, max_tokens=600, temperature=0.8, request_type="emoji" + model=global_config.llm_normal, max_tokens=600, request_type="emoji" ) # 更高的温度,更少的token(后续可以根据情绪来调整温度) self.emoji_num = 0 @@ -682,7 +682,7 @@ class EmojiManager: if register_success: self.emoji_objects.append(new_emoji) self.emoji_num += 1 - logger.success(f"[成功] 注册表情包: {new_emoji.description}") + logger.success(f"[成功] 注册: {new_emoji.filename}") return True else: logger.error(f"[错误] 注册表情包到数据库失败: {new_emoji.filename}") @@ -719,10 +719,10 @@ class EmojiManager: # 调用AI获取描述 if image_format == "gif" or image_format == "GIF": image_base64 = image_manager.transform_gif(image_base64) - prompt = "这是一个动态图表情包,每一张图代表了动态图的某一帧,黑色背景代表透明,描述一下表情包表达的情感和内容,你可以关注其幽默和讽刺意味,必须从互联网梗,meme的角度去分析" + prompt = "这是一个动态图表情包,每一张图代表了动态图的某一帧,黑色背景代表透明,描述一下表情包表达的情感和内容,你可以关注其幽默和讽刺意味,动用贴吧,微博,小红书的知识,必须从互联网梗,meme的角度去分析" description, _ = await self.vlm.generate_response_for_image(prompt, image_base64, "jpg") else: - prompt = "这是一个表情包,请详细描述一下表情包所表达的情感和内容,你可以关注其幽默和讽刺意味,必须从互联网梗,meme的角度去分析" + prompt = "这是一个表情包,请详细描述一下表情包所表达的情感和内容,你可以关注其幽默和讽刺意味,动用贴吧,微博,小红书的知识,必须从互联网梗,meme的角度去分析" description, _ = await self.vlm.generate_response_for_image(prompt, image_base64, image_format) # 审核表情包 @@ -797,7 +797,7 @@ class EmojiManager: if register_success: self.emoji_objects.append(new_emoji) self.emoji_num += 1 - logger.success(f"[成功] 注册表情包: {filename}") + logger.success(f"[成功] 注册: {filename}") return True else: logger.error(f"[错误] 注册表情包到数据库失败: {filename}") @@ -814,7 +814,7 @@ class EmojiManager: 当目录中文件数超过50时,会全部删除 """ - logger.info("[清理] 开始清理临时表情包...") + logger.info("[清理] 开始清理缓存...") # 清理emoji目录 emoji_dir = os.path.join(BASE_DIR, "emoji") @@ -826,7 +826,7 @@ class EmojiManager: file_path = os.path.join(emoji_dir, filename) if os.path.isfile(file_path): os.remove(file_path) - logger.debug(f"[清理] 删除表情包文件: {filename}") + logger.debug(f"[清理] 删除: {filename}") # 清理image目录 image_dir = os.path.join(BASE_DIR, "image") @@ -838,14 +838,19 @@ class EmojiManager: file_path = os.path.join(image_dir, filename) if os.path.isfile(file_path): os.remove(file_path) - logger.debug(f"[清理] 删除图片文件: {filename}") + logger.debug(f"[清理] 删除图片: {filename}") - logger.success("[清理] 临时文件清理完成") + logger.success("[清理] 完成") async def clean_unused_emojis(self, emoji_dir, emoji_objects): """清理未使用的表情包文件 遍历指定文件夹中的所有文件,删除未在emoji_objects列表中的文件 """ + # 首先检查目录是否存在喵~ + if not os.path.exists(emoji_dir): + logger.warning(f"[清理] 表情包目录不存在,跳过清理: {emoji_dir}") + return + # 获取所有表情包路径 emoji_paths = {emoji.path for emoji in emoji_objects} diff --git a/src/plugins/heartFC_chat/heartFC_chat.py b/src/plugins/heartFC_chat/heartFC_chat.py index 99b76f03..bfdf2d6a 100644 --- a/src/plugins/heartFC_chat/heartFC_chat.py +++ b/src/plugins/heartFC_chat/heartFC_chat.py @@ -37,7 +37,7 @@ EMOJI_SEND_PRO = 0.3 # 设置一个概率,比如 30% 才真的发 CONSECUTIVE_NO_REPLY_THRESHOLD = 3 # 连续不回复的阈值 -logger = get_logger("interest") # Logger Name Changed +logger = get_logger("HFC") # Logger Name Changed # 默认动作定义 diff --git a/src/plugins/willing/willing_manager.py b/src/plugins/willing/willing_manager.py index 4b105ec9..78c86e25 100644 --- a/src/plugins/willing/willing_manager.py +++ b/src/plugins/willing/willing_manager.py @@ -77,7 +77,7 @@ class BaseWillingManager(ABC): if not issubclass(manager_class, cls): raise TypeError(f"Manager class {manager_class.__name__} is not a subclass of {cls.__name__}") else: - logger.info(f"成功载入willing模式:{manager_type}") + logger.info(f"普通回复模式:{manager_type}") return manager_class() except (ImportError, AttributeError, TypeError) as e: module = importlib.import_module(".mode_classical", __package__) From c8a72782f0597661eb51344ab1ce3957930dc1f6 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 30 Apr 2025 18:21:06 +0800 Subject: [PATCH 13/20] Update emoji_manager.py --- src/plugins/emoji_system/emoji_manager.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/plugins/emoji_system/emoji_manager.py b/src/plugins/emoji_system/emoji_manager.py index 72af602f..c71b2abe 100644 --- a/src/plugins/emoji_system/emoji_manager.py +++ b/src/plugins/emoji_system/emoji_manager.py @@ -656,11 +656,11 @@ class EmojiManager: # 调用大模型进行决策 decision, _ = await self.llm_emotion_judge.generate_response_async(prompt, temperature=0.8) - logger.info(f"[决策] 大模型决策结果: {decision}") + logger.info(f"[决策] 结果: {decision}") # 解析决策结果 if "不删除" in decision: - logger.info("[决策] 决定不删除任何表情包") + logger.info("[决策] 不删除任何表情包") return False # 尝试从决策中提取表情包编号 @@ -673,7 +673,7 @@ class EmojiManager: emoji_to_delete = selected_emojis[emoji_index] # 删除选定的表情包 - logger.info(f"[决策] 决定删除表情包: {emoji_to_delete.description}") + logger.info(f"[决策] 删除表情包: {emoji_to_delete.description}") delete_success = await self.delete_emoji(emoji_to_delete.hash) if delete_success: @@ -719,10 +719,10 @@ class EmojiManager: # 调用AI获取描述 if image_format == "gif" or image_format == "GIF": image_base64 = image_manager.transform_gif(image_base64) - prompt = "这是一个动态图表情包,每一张图代表了动态图的某一帧,黑色背景代表透明,描述一下表情包表达的情感和内容,你可以关注其幽默和讽刺意味,动用贴吧,微博,小红书的知识,必须从互联网梗,meme的角度去分析" + prompt = "这是一个动态图表情包,每一张图代表了动态图的某一帧,黑色背景代表透明,描述一下表情包表达的情感和内容,描述细节,从互联网梗,meme的角度去分析" description, _ = await self.vlm.generate_response_for_image(prompt, image_base64, "jpg") else: - prompt = "这是一个表情包,请详细描述一下表情包所表达的情感和内容,你可以关注其幽默和讽刺意味,动用贴吧,微博,小红书的知识,必须从互联网梗,meme的角度去分析" + prompt = "这是一个表情包,请详细描述一下表情包所表达的情感和内容,描述细节,从互联网梗,meme的角度去分析" description, _ = await self.vlm.generate_response_for_image(prompt, image_base64, image_format) # 审核表情包 @@ -741,12 +741,11 @@ class EmojiManager: # 分析情感含义 emotion_prompt = f""" - 基于这个表情包的描述:'{description}',请列出1-2个可能的情感标签,每个标签用一个词组表示,格式如下: - 幽默的讽刺,适用于调侃或吐槽场景 - 悲伤的无奈,适用于表达无力感或失望 - 愤怒的抗议,适用于表达不满或反对 - 愤怒的讽刺,适用于尖锐批评或反讽 - 直接输出词组,词组检用逗号分隔。""" + 请你识别这个表情包的含义和适用场景,给我简短的描述,每个描述不要超过15个字 + 这是一个基于这个表情包的描述:'{description}' + 你可以关注其幽默和讽刺意味,动用贴吧,微博,小红书的知识,必须从互联网梗,meme的角度去分析 + 请直接输出描述,不要出现任何其他内容,如果有多个描述,可以用逗号分隔 + """ emotions_text, _ = await self.llm_emotion_judge.generate_response_async(emotion_prompt, temperature=0.7) # 处理情感列表 From c8c27a14836e01c67d424e16465b71e7070d2ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E6=A2=93=E6=9F=92?= <1787882683@qq.com> Date: Wed, 30 Apr 2025 18:22:06 +0800 Subject: [PATCH 14/20] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E6=AD=A3=E7=9F=A5?= =?UTF-8?q?=E8=AF=86=E8=8E=B7=E5=8F=96=E9=80=BB=E8=BE=91=EF=BC=8C=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E7=9F=A5=E8=AF=86=E7=9B=B8=E5=85=B3=E6=80=A7=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E9=A1=BA=E5=BA=8F=EF=BC=8C=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E9=95=BF=E5=BA=A6=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/knowledge/src/qa_manager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/plugins/knowledge/src/qa_manager.py b/src/plugins/knowledge/src/qa_manager.py index 9cb5c018..6d84dc8e 100644 --- a/src/plugins/knowledge/src/qa_manager.py +++ b/src/plugins/knowledge/src/qa_manager.py @@ -11,6 +11,8 @@ from .lpmmconfig import global_config from .utils.dyn_topk import dyn_select_top_k +MAX_KNOWLEDGE_LENGTH = 10000 # 最大知识长度 + class QAManager: def __init__( self, @@ -112,8 +114,10 @@ class QAManager: for res in query_res ] found_knowledge = "\n".join( - [f"第{i + 1}条知识:{k[1]}\n 该条知识对于问题的相关性:{k[0]}" for i, k in enumerate(knowledge)] + [f"第{i + 1}条知识:{k[0]}\n 该条知识对于问题的相关性:{k[1]}" for i, k in enumerate(knowledge)] ) + if len(found_knowledge) > MAX_KNOWLEDGE_LENGTH: + found_knowledge = found_knowledge[:MAX_KNOWLEDGE_LENGTH] + "\n" return found_knowledge else: logger.info("LPMM知识库并未初始化,使用旧版数据库进行检索") From 5242573412dd47f9a05c48e1f199aa339c0e2de4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Apr 2025 10:22:25 +0000 Subject: [PATCH 15/20] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/knowledge/src/qa_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/knowledge/src/qa_manager.py b/src/plugins/knowledge/src/qa_manager.py index 6d84dc8e..a09879a1 100644 --- a/src/plugins/knowledge/src/qa_manager.py +++ b/src/plugins/knowledge/src/qa_manager.py @@ -13,6 +13,7 @@ from .utils.dyn_topk import dyn_select_top_k MAX_KNOWLEDGE_LENGTH = 10000 # 最大知识长度 + class QAManager: def __init__( self, From 377c88c2beda032e216004d646aad91446de0d22 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 30 Apr 2025 18:24:33 +0800 Subject: [PATCH 16/20] Update emoji_manager.py --- src/plugins/emoji_system/emoji_manager.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/plugins/emoji_system/emoji_manager.py b/src/plugins/emoji_system/emoji_manager.py index c71b2abe..1a9146a1 100644 --- a/src/plugins/emoji_system/emoji_manager.py +++ b/src/plugins/emoji_system/emoji_manager.py @@ -750,6 +750,12 @@ class EmojiManager: # 处理情感列表 emotions = [e.strip() for e in emotions_text.split(",") if e.strip()] + + # 根据情感标签数量随机选择喵~超过5个选3个,超过2个选2个 + if len(emotions) > 5: + emotions = random.sample(emotions, 3) + elif len(emotions) > 2: + emotions = random.sample(emotions, 2) return f"[表情包:{description}]", emotions From f18d0786bb69dfef3277eeaedb1dced3c93e6cc6 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 30 Apr 2025 18:28:32 +0800 Subject: [PATCH 17/20] Update heartflow_prompt_builder.py --- src/plugins/heartFC_chat/heartflow_prompt_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/heartFC_chat/heartflow_prompt_builder.py b/src/plugins/heartFC_chat/heartflow_prompt_builder.py index c8d636c5..c23d6a7e 100644 --- a/src/plugins/heartFC_chat/heartflow_prompt_builder.py +++ b/src/plugins/heartFC_chat/heartflow_prompt_builder.py @@ -69,7 +69,7 @@ def init_prompt(): 2. 文字回复(text_reply)适用: - 有实质性内容需要表达 - 有人提到你,但你还没有回应他 -- 可以追加emoji_query表达情绪(格式:情绪描述,如"俏皮的调侃") +- 可以追加emoji_query表达情绪(emoji_query填写表情包的适用场合,也就是当前场合) - 不要追加太多表情 3. 纯表情回复(emoji_reply)适用: From 3b63005f579f2a2354bfbda94f966d85872d44fb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Apr 2025 10:28:47 +0000 Subject: [PATCH 18/20] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/heart_flow/subheartflow_manager.py | 12 ++++-------- src/plugins/emoji_system/emoji_manager.py | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/heart_flow/subheartflow_manager.py b/src/heart_flow/subheartflow_manager.py index fe555ea2..16f36dcc 100644 --- a/src/heart_flow/subheartflow_manager.py +++ b/src/heart_flow/subheartflow_manager.py @@ -23,7 +23,6 @@ from src.individuality.individuality import Individuality import traceback - # 初始化日志记录器 logger = get_logger("subheartflow_manager") @@ -359,9 +358,7 @@ class SubHeartflowManager: # 3. 检查 CHAT 上限 current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT) if current_chat_count >= chat_limit: - logger.info( - f"{log_prefix} 想看看能不能聊,但是聊天太多了, ({current_chat_count}/{chat_limit}) 满了。" - ) + logger.info(f"{log_prefix} 想看看能不能聊,但是聊天太多了, ({current_chat_count}/{chat_limit}) 满了。") return # 满了,这次就算了 # --- 获取 FOCUSED 计数 --- @@ -421,7 +418,7 @@ class SubHeartflowManager: # --- 4. LLM 评估是否想聊 --- yao_kai_shi_liao_ma, reason = await self._llm_evaluate_state_transition(prompt) - + if reason: if yao_kai_shi_liao_ma: logger.info(f"{log_prefix} 打算开始聊,原因是: {reason}") @@ -532,7 +529,6 @@ class SubHeartflowManager: f"{log_prefix_task} 完成,共检查 {checked_count} 个子心流,{transitioned_to_absent} 个因超时转为 ABSENT。" ) - # --- 结束新增 --- async def _llm_evaluate_state_transition(self, prompt: str) -> Tuple[Optional[bool], Optional[str]]: @@ -567,8 +563,8 @@ class SubHeartflowManager: if isinstance(decision, bool): logger.debug(f"{log_prefix} LLM评估结果 (来自JSON): {'建议转换' if decision else '建议不转换'}") - - return decision , reason + + return decision, reason else: logger.warning( f"{log_prefix} LLM 返回的 JSON 中 'decision' 键的值不是布尔型: {decision}。响应: {response_text}" diff --git a/src/plugins/emoji_system/emoji_manager.py b/src/plugins/emoji_system/emoji_manager.py index 1a9146a1..844c6830 100644 --- a/src/plugins/emoji_system/emoji_manager.py +++ b/src/plugins/emoji_system/emoji_manager.py @@ -750,7 +750,7 @@ class EmojiManager: # 处理情感列表 emotions = [e.strip() for e in emotions_text.split(",") if e.strip()] - + # 根据情感标签数量随机选择喵~超过5个选3个,超过2个选2个 if len(emotions) > 5: emotions = random.sample(emotions, 3) From a206af76fb8e07d29837df44910cbd8eb3d8f9b8 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Wed, 30 Apr 2025 18:30:27 +0800 Subject: [PATCH 19/20] Update emoji_manager.py --- src/plugins/emoji_system/emoji_manager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/emoji_system/emoji_manager.py b/src/plugins/emoji_system/emoji_manager.py index 1a9146a1..d44d0f9a 100644 --- a/src/plugins/emoji_system/emoji_manager.py +++ b/src/plugins/emoji_system/emoji_manager.py @@ -131,7 +131,9 @@ class MaiEmoji: # 使用upsert确保记录存在或被更新 db["emoji"].update_one({"hash": self.hash}, {"$set": emoji_record}, upsert=True) - logger.success(f"[注册] 表情包信息保存到数据库: {self.description[:30]}...") + + + logger.success(f"[注册] 表情包信息保存到数据库: {self.emotion}") return True From c94ef758223427b612ed6b63a10f884994bea01a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 30 Apr 2025 10:30:48 +0000 Subject: [PATCH 20/20] =?UTF-8?q?=F0=9F=A4=96=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/emoji_system/emoji_manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/emoji_system/emoji_manager.py b/src/plugins/emoji_system/emoji_manager.py index 9c0a959d..6a9e0067 100644 --- a/src/plugins/emoji_system/emoji_manager.py +++ b/src/plugins/emoji_system/emoji_manager.py @@ -131,8 +131,7 @@ class MaiEmoji: # 使用upsert确保记录存在或被更新 db["emoji"].update_one({"hash": self.hash}, {"$set": emoji_record}, upsert=True) - - + logger.success(f"[注册] 表情包信息保存到数据库: {self.emotion}") return True