From 2669572b301460db3bb20258c2292f4125a95fc7 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Thu, 1 May 2025 22:50:29 +0800 Subject: [PATCH] r --- src/heart_flow/background_tasks.py | 10 +-- src/heart_flow/observation.py | 40 +++++---- src/heart_flow/sub_heartflow.py | 23 ++--- src/heart_flow/sub_mind.py | 32 +++---- src/heart_flow/subheartflow_manager.py | 85 ++++++++++-------- src/heart_flow/utils_chat.py | 43 ++++----- src/plugins/heartFC_chat/heartFC_chat.py | 63 +++++++------ .../heartFC_chat/heartflow_prompt_builder.py | 88 +++++++++---------- src/plugins/heartFC_chat/normal_chat.py | 24 ++--- 9 files changed, 220 insertions(+), 188 deletions(-) diff --git a/src/heart_flow/background_tasks.py b/src/heart_flow/background_tasks.py index 99816540e..c1730d5a5 100644 --- a/src/heart_flow/background_tasks.py +++ b/src/heart_flow/background_tasks.py @@ -20,7 +20,7 @@ NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS = 60 # 新增状态评估间隔 HF_JUDGE_STATE_UPDATE_INTERVAL_SECONDS = 60 # 新增私聊激活检查间隔 -PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS = 5 # 与兴趣评估类似,设为5秒 +PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS = 5 # 与兴趣评估类似,设为5秒 CLEANUP_INTERVAL_SECONDS = 1200 STATE_UPDATE_INTERVAL_SECONDS = 60 @@ -76,7 +76,7 @@ class BackgroundTaskManager: self._normal_chat_timeout_check_task: Optional[asyncio.Task] = None self._hf_judge_state_update_task: Optional[asyncio.Task] = None self._into_focus_task: Optional[asyncio.Task] = None - self._private_chat_activation_task: Optional[asyncio.Task] = None # 新增私聊激活任务引用 + self._private_chat_activation_task: Optional[asyncio.Task] = None # 新增私聊激活任务引用 self._tasks: List[Optional[asyncio.Task]] = [] # Keep track of all tasks async def start_tasks(self): @@ -129,9 +129,9 @@ class BackgroundTaskManager: ), # 新增私聊激活任务配置 ( - # Use lambda to pass the interval to the runner function + # Use lambda to pass the interval to the runner function lambda: self._run_private_chat_activation_cycle(PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS), - "debug", + "debug", f"私聊激活检查任务已启动 间隔:{PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS}s", "_private_chat_activation_task", ), @@ -294,5 +294,5 @@ class BackgroundTaskManager: await _run_periodic_loop( task_name="Private Chat Activation Check", interval=interval, - task_func=self.subheartflow_manager.sbhf_absent_private_into_focus + task_func=self.subheartflow_manager.sbhf_absent_private_into_focus, ) diff --git a/src/heart_flow/observation.py b/src/heart_flow/observation.py index 06dcdc313..0d51c9380 100644 --- a/src/heart_flow/observation.py +++ b/src/heart_flow/observation.py @@ -13,11 +13,10 @@ from src.plugins.utils.chat_message_builder import ( get_person_id_list, ) from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager -from src.plugins.chat.chat_stream import chat_manager from typing import Optional -from src.plugins.person_info.person_info import person_info_manager + # Import the new utility function -from .utils_chat import get_chat_type_and_target_info +from .utils_chat import get_chat_type_and_target_info logger = get_logger("observation") @@ -26,14 +25,14 @@ Prompt( """这是qq群聊的聊天记录,请总结以下聊天记录的主题: {chat_logs} 请用一句话概括,包括人物、事件和主要信息,不要分点。""", - "chat_summary_group_prompt" # Template for group chat + "chat_summary_group_prompt", # Template for group chat ) Prompt( """这是你和{chat_target}的私聊记录,请总结以下聊天记录的主题: {chat_logs} 请用一句话概括,包括事件,时间,和主要信息,不要分点。""", - "chat_summary_private_prompt" # Template for private chat + "chat_summary_private_prompt", # Template for private chat ) # --- End Prompt Template Definition --- @@ -56,9 +55,9 @@ class ChattingObservation(Observation): super().__init__("chat", chat_id) self.chat_id = chat_id - # --- Initialize attributes (defaults) --- + # --- Initialize attributes (defaults) --- self.is_group_chat: bool = False - self.chat_target_info: Optional[dict] = None + self.chat_target_info: Optional[dict] = None # --- End Initialization --- # --- Other attributes initialized in __init__ --- @@ -77,11 +76,12 @@ class ChattingObservation(Observation): model=global_config.llm_observation, temperature=0.7, max_tokens=300, request_type="chat_observation" ) - async def initialize(self): # --- Use utility function to determine chat type and fetch info --- self.is_group_chat, self.chat_target_info = await get_chat_type_and_target_info(self.chat_id) - logger.debug(f"ChattingObservation {self.chat_id} initialized: is_group={self.is_group_chat}, target_info={self.chat_target_info}") + logger.debug( + f"ChattingObservation {self.chat_id} initialized: is_group={self.is_group_chat}, target_info={self.chat_target_info}" + ) # --- End using utility function --- # Fetch initial messages (existing logic) @@ -141,30 +141,33 @@ class ChattingObservation(Observation): ) # --- Build prompt using template --- - prompt = None # Initialize prompt as None + prompt = None # Initialize prompt as None try: # 构建 Prompt - 根据 is_group_chat 选择模板 if self.is_group_chat: prompt_template_name = "chat_summary_group_prompt" prompt = await global_prompt_manager.format_prompt( - prompt_template_name, - chat_logs=oldest_messages_str + prompt_template_name, chat_logs=oldest_messages_str ) else: # For private chat, add chat_target to the prompt variables prompt_template_name = "chat_summary_private_prompt" # Determine the target name for the prompt - chat_target_name = "对方" # Default fallback + chat_target_name = "对方" # Default fallback if self.chat_target_info: # Prioritize person_name, then nickname - chat_target_name = self.chat_target_info.get('person_name') or self.chat_target_info.get('user_nickname') or chat_target_name - + chat_target_name = ( + self.chat_target_info.get("person_name") + or self.chat_target_info.get("user_nickname") + or chat_target_name + ) + # Format the private chat prompt prompt = await global_prompt_manager.format_prompt( prompt_template_name, # Assuming the private prompt template uses {chat_target} - chat_target=chat_target_name, - chat_logs=oldest_messages_str + chat_target=chat_target_name, + chat_logs=oldest_messages_str, ) except Exception as e: logger.error(f"构建总结 Prompt 失败 for chat {self.chat_id}: {e}") @@ -172,7 +175,7 @@ class ChattingObservation(Observation): summary = "没有主题的闲聊" # 默认值 - if prompt: # Check if prompt was built successfully + if prompt: # Check if prompt was built successfully try: summary_result, _, _ = await self.llm_summary.generate_response(prompt) if summary_result: # 确保结果不为空 @@ -183,7 +186,6 @@ class ChattingObservation(Observation): else: logger.warning(f"因 Prompt 构建失败,跳过 LLM 总结 for chat {self.chat_id}") - mid_memory = { "id": str(int(datetime.now().timestamp())), "theme": summary, diff --git a/src/heart_flow/sub_heartflow.py b/src/heart_flow/sub_heartflow.py index 8e00c263a..eb8bbabdb 100644 --- a/src/heart_flow/sub_heartflow.py +++ b/src/heart_flow/sub_heartflow.py @@ -13,7 +13,6 @@ from src.plugins.heartFC_chat.normal_chat import NormalChat 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 -from src.plugins.person_info.person_info import person_info_manager from .utils_chat import get_chat_type_and_target_info @@ -240,9 +239,9 @@ class SubHeartflow: self.chat_state_last_time: float = 0 self.history_chat_state: List[Tuple[ChatState, float]] = [] - # --- Initialize attributes --- + # --- Initialize attributes --- self.is_group_chat: bool = False - self.chat_target_info: Optional[dict] = None + self.chat_target_info: Optional[dict] = None # --- End Initialization --- # 兴趣检测器 @@ -268,16 +267,20 @@ class SubHeartflow: ) # 日志前缀 - Moved determination to initialize - self.log_prefix = str(subheartflow_id) # Initial default prefix + self.log_prefix = str(subheartflow_id) # Initial default prefix async def initialize(self): """异步初始化方法,创建兴趣流并确定聊天类型""" - + # --- Use utility function to determine chat type and fetch info --- self.is_group_chat, self.chat_target_info = await get_chat_type_and_target_info(self.chat_id) # Update log prefix after getting info (potential stream name) - self.log_prefix = chat_manager.get_stream_name(self.subheartflow_id) or self.subheartflow_id # Keep this line or adjust if utils provides name - logger.debug(f"SubHeartflow {self.chat_id} initialized: is_group={self.is_group_chat}, target_info={self.chat_target_info}") + self.log_prefix = ( + chat_manager.get_stream_name(self.subheartflow_id) or self.subheartflow_id + ) # Keep this line or adjust if utils provides name + logger.debug( + f"SubHeartflow {self.chat_id} initialized: is_group={self.is_group_chat}, target_info={self.chat_target_info}" + ) # --- End using utility function --- # Initialize interest system (existing logic) @@ -315,15 +318,15 @@ class SubHeartflow: if not chat_stream: logger.error(f"{log_prefix} 无法获取 chat_stream,无法启动 NormalChat。") return False - + self.normal_chat_instance = NormalChat(chat_stream=chat_stream, interest_dict=self.get_interest_dict()) - + # 进行异步初始化 await self.normal_chat_instance.initialize() # 启动聊天任务 logger.info(f"{log_prefix} 开始普通聊天,随便水群...") - await self.normal_chat_instance.start_chat() # start_chat now ensures init is called again if needed + await self.normal_chat_instance.start_chat() # start_chat now ensures init is called again if needed return True except Exception as e: logger.error(f"{log_prefix} 启动 NormalChat 或其初始化时出错: {e}") diff --git a/src/heart_flow/sub_mind.py b/src/heart_flow/sub_mind.py index ca774f1d6..4ce2fe417 100644 --- a/src/heart_flow/sub_mind.py +++ b/src/heart_flow/sub_mind.py @@ -20,7 +20,7 @@ logger = get_logger("sub_heartflow") def init_prompt(): - # --- Group Chat Prompt --- + # --- Group Chat Prompt --- group_prompt = """ {extra_info} {relation_prompt} @@ -45,7 +45,7 @@ def init_prompt(): 3. 如需处理消息或回复,请使用工具。""" Prompt(group_prompt, "sub_heartflow_prompt_before") - # --- Private Chat Prompt --- + # --- Private Chat Prompt --- private_prompt = """ {extra_info} {relation_prompt} @@ -69,9 +69,9 @@ def init_prompt(): 1. 输出想法后考虑是否需要使用工具 2. 工具可获取信息或执行操作 3. 如需处理消息或回复,请使用工具。""" - Prompt(private_prompt, "sub_heartflow_prompt_private_before") # New template name + Prompt(private_prompt, "sub_heartflow_prompt_private_before") # New template name - # --- Last Loop Prompt (remains the same) --- + # --- Last Loop Prompt (remains the same) --- last_loop_t = """ 刚刚你的内心想法是:{current_thinking_info} {if_replan_prompt} @@ -152,17 +152,19 @@ class SubMind: # 获取观察对象 observation = self.observations[0] if self.observations else None - if not observation or not hasattr(observation, 'is_group_chat'): # Ensure it's ChattingObservation or similar + if not observation or not hasattr(observation, "is_group_chat"): # Ensure it's ChattingObservation or similar logger.error(f"{self.log_prefix} 无法获取有效的观察对象或缺少聊天类型信息") self.update_current_mind("(观察出错了...)") return self.current_mind, self.past_mind - + is_group_chat = observation.is_group_chat chat_target_info = observation.chat_target_info - chat_target_name = "对方" # Default for private + chat_target_name = "对方" # Default for private if not is_group_chat and chat_target_info: - chat_target_name = chat_target_info.get('person_name') or chat_target_info.get('user_nickname') or chat_target_name - # --- End getting observation info --- + chat_target_name = ( + chat_target_info.get("person_name") or chat_target_info.get("user_nickname") or chat_target_name + ) + # --- End getting observation info --- # 获取观察内容 chat_observe_info = observation.get_observe_info() @@ -274,7 +276,7 @@ class SubMind: )[0] # ---------- 4. 构建最终提示词 ---------- - # --- Choose template based on chat type --- + # --- Choose template based on chat type --- if is_group_chat: template_name = "sub_heartflow_prompt_before" prompt = (await global_prompt_manager.get_prompt_async(template_name)).format( @@ -290,22 +292,22 @@ class SubMind: cycle_info_block=cycle_info_block, # chat_target_name is not used in group prompt ) - else: # Private chat + else: # Private chat template_name = "sub_heartflow_prompt_private_before" prompt = (await global_prompt_manager.get_prompt_async(template_name)).format( - extra_info="", + extra_info="", prompt_personality=prompt_personality, - relation_prompt=relation_prompt, # Might need adjustment for private context + relation_prompt=relation_prompt, # Might need adjustment for private context bot_name=individuality.name, time_now=time_now, - chat_target_name=chat_target_name, # Pass target name + chat_target_name=chat_target_name, # Pass target name chat_observe_info=chat_observe_info, mood_info=mood_info, hf_do_next=hf_do_next, last_loop_prompt=last_loop_prompt, cycle_info_block=cycle_info_block, ) - # --- End choosing template --- + # --- End choosing template --- # ---------- 5. 执行LLM请求并处理响应 ---------- content = "" # 初始化内容变量 diff --git a/src/heart_flow/subheartflow_manager.py b/src/heart_flow/subheartflow_manager.py index 25e5d3ccf..04153abfa 100644 --- a/src/heart_flow/subheartflow_manager.py +++ b/src/heart_flow/subheartflow_manager.py @@ -345,7 +345,8 @@ class SubHeartflowManager: async with self._lock: # 1. 筛选出所有 ABSENT 状态的 *群聊* 子心流 absent_group_subflows = [ - hf for hf in self.subheartflows.values() + hf + for hf in self.subheartflows.values() if hf.chat_state.chat_status == ChatState.ABSENT and hf.is_group_chat ] @@ -358,7 +359,7 @@ class SubHeartflowManager: 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}]" - + # --- Private chat check (redundant due to filter above, but safe) --- # if not sub_hf_to_evaluate.is_group_chat: # logger.debug(f"{log_prefix} 是私聊,跳过 CHAT 状态评估。") @@ -684,24 +685,26 @@ class SubHeartflowManager: current_state = subflow.chat_state.chat_status if current_state == ChatState.FOCUSED: - target_state = ChatState.ABSENT # Default target + target_state = ChatState.ABSENT # Default target log_reason = "默认转换 (私聊或群聊)" # --- Modify logic based on chat type --- # if subflow.is_group_chat: # Group chat: Decide between ABSENT or CHAT - if random.random() < 0.5: # 50% chance to try CHAT + if random.random() < 0.5: # 50% chance to try 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})" else: - target_state = ChatState.ABSENT # Fallback to ABSENT if CHAT limit reached - log_reason = f"群聊随机选择 CHAT 但已达上限 ({current_chat_count}/{chat_limit}),转为 ABSENT" - else: # 50% chance to go directly to ABSENT + target_state = ChatState.ABSENT # Fallback to ABSENT if CHAT limit reached + log_reason = ( + f"群聊随机选择 CHAT 但已达上限 ({current_chat_count}/{chat_limit}),转为 ABSENT" + ) + else: # 50% chance to go directly to ABSENT target_state = ChatState.ABSENT log_reason = "群聊随机选择 ABSENT" else: @@ -732,6 +735,7 @@ class SubHeartflowManager: logger.warning( f"[状态转换请求] 收到对 {stream_name} 的请求,但其状态为 {current_state.value} (非 FOCUSED),不执行转换" ) + # --- 结束新增 --- # # --- 新增:处理私聊从 ABSENT 直接到 FOCUSED 的逻辑 --- # @@ -740,17 +744,17 @@ class SubHeartflowManager: log_prefix_task = "[私聊激活检查]" transitioned_count = 0 checked_count = 0 - + # --- 获取当前状态和 FOCUSED 上限 --- # current_mai_state = self.mai_state_info.get_current_state() focused_limit = current_mai_state.get_focused_chat_max_num() # --- 检查是否允许 FOCUS 模式 --- # if not global_config.allow_focus_mode: - # Log less frequently to avoid spam - # if int(time.time()) % 60 == 0: - # logger.debug(f"{log_prefix_task} 配置不允许进入 FOCUSED 状态") - return + # Log less frequently to avoid spam + # if int(time.time()) % 60 == 0: + # logger.debug(f"{log_prefix_task} 配置不允许进入 FOCUSED 状态") + return if focused_limit <= 0: # logger.debug(f"{log_prefix_task} 当前状态 ({current_mai_state.value}) 不允许 FOCUSED 子心流") @@ -759,10 +763,11 @@ class SubHeartflowManager: async with self._lock: # --- 获取当前 FOCUSED 计数 (不上锁版本) --- # current_focused_count = self.count_subflows_by_state_nolock(ChatState.FOCUSED) - + # --- 筛选出所有 ABSENT 状态的私聊子心流 --- # eligible_subflows = [ - hf for hf in self.subheartflows.values() + hf + for hf in self.subheartflows.values() if hf.chat_state.chat_status == ChatState.ABSENT and not hf.is_group_chat ] checked_count = len(eligible_subflows) @@ -775,8 +780,10 @@ class SubHeartflowManager: for sub_hf in eligible_subflows: # --- 再次检查 FOCUSED 上限,因为可能有多个同时激活 --- # if current_focused_count >= focused_limit: - logger.debug(f"{log_prefix_task} 已达专注上限 ({current_focused_count}/{focused_limit}),停止检查后续私聊。") - break # 已满,无需再检查其他私聊 + logger.debug( + f"{log_prefix_task} 已达专注上限 ({current_focused_count}/{focused_limit}),停止检查后续私聊。" + ) + break # 已满,无需再检查其他私聊 flow_id = sub_hf.subheartflow_id stream_name = chat_manager.get_stream_name(flow_id) or flow_id @@ -784,45 +791,51 @@ class SubHeartflowManager: try: # --- 检查是否有新活动 --- # - observation = sub_hf._get_primary_observation() # 获取主要观察者 + observation = sub_hf._get_primary_observation() # 获取主要观察者 is_active = False if observation: - # 检查自上次状态变为 ABSENT 后是否有新消息 - # 使用 chat_state_changed_time 可能更精确 - # 加一点点缓冲时间(例如 1 秒)以防时间戳完全相等 - timestamp_to_check = sub_hf.chat_state_changed_time - 1 + # 检查自上次状态变为 ABSENT 后是否有新消息 + # 使用 chat_state_changed_time 可能更精确 + # 加一点点缓冲时间(例如 1 秒)以防时间戳完全相等 + timestamp_to_check = sub_hf.chat_state_changed_time - 1 has_new = await observation.has_new_messages_since(timestamp_to_check) if has_new: - is_active = True - logger.debug(f"{log_prefix} 检测到新消息,标记为活跃。") - # 可选:检查兴趣度是否大于0 (如果需要) - # interest_level = await sub_hf.interest_chatting.get_interest() - # if interest_level > 0: - # is_active = True - # logger.debug(f"{log_prefix} 检测到兴趣度 > 0 ({interest_level:.2f}),标记为活跃。") + is_active = True + logger.debug(f"{log_prefix} 检测到新消息,标记为活跃。") + # 可选:检查兴趣度是否大于0 (如果需要) + # interest_level = await sub_hf.interest_chatting.get_interest() + # if interest_level > 0: + # is_active = True + # logger.debug(f"{log_prefix} 检测到兴趣度 > 0 ({interest_level:.2f}),标记为活跃。") else: - logger.warning(f"{log_prefix} 无法获取主要观察者来检查活动状态。") + logger.warning(f"{log_prefix} 无法获取主要观察者来检查活动状态。") # --- 如果活跃且未达上限,则尝试转换 --- # if is_active: - logger.info(f"{log_prefix} 检测到活跃且未达专注上限 ({current_focused_count}/{focused_limit}),尝试转换为 FOCUSED。") + logger.info( + f"{log_prefix} 检测到活跃且未达专注上限 ({current_focused_count}/{focused_limit}),尝试转换为 FOCUSED。" + ) await sub_hf.change_chat_state(ChatState.FOCUSED) # 确认转换成功 if sub_hf.chat_state.chat_status == ChatState.FOCUSED: transitioned_count += 1 - current_focused_count += 1 # 更新计数器以供本轮后续检查 + current_focused_count += 1 # 更新计数器以供本轮后续检查 logger.info(f"{log_prefix} 成功进入 FOCUSED 状态。") else: - logger.warning(f"{log_prefix} 尝试进入 FOCUSED 状态失败。当前状态: {sub_hf.chat_state.chat_status.value}") + logger.warning( + f"{log_prefix} 尝试进入 FOCUSED 状态失败。当前状态: {sub_hf.chat_state.chat_status.value}" + ) # else: # 不活跃,无需操作 # logger.debug(f"{log_prefix} 未检测到新活动,保持 ABSENT。") - + except Exception as e: logger.error(f"{log_prefix} 检查私聊活动或转换状态时出错: {e}", exc_info=True) - + # --- 循环结束后记录总结日志 --- # if transitioned_count > 0: - logger.debug(f"{log_prefix_task} 完成,共检查 {checked_count} 个私聊,{transitioned_count} 个转换为 FOCUSED。") + logger.debug( + f"{log_prefix_task} 完成,共检查 {checked_count} 个私聊,{transitioned_count} 个转换为 FOCUSED。" + ) # --- 结束新增 --- # diff --git a/src/heart_flow/utils_chat.py b/src/heart_flow/utils_chat.py index 260fc16dc..3e082b6da 100644 --- a/src/heart_flow/utils_chat.py +++ b/src/heart_flow/utils_chat.py @@ -6,6 +6,7 @@ from src.plugins.person_info.person_info import person_info_manager logger = get_logger("heartflow_utils") + async def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Dict]]: """ 获取聊天类型(是否群聊)和私聊对象信息。 @@ -14,7 +15,7 @@ async def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Di chat_id: 聊天流ID Returns: - Tuple[bool, Optional[Dict]]: + Tuple[bool, Optional[Dict]]: - bool: 是否为群聊 (True 是群聊, False 是私聊或未知) - Optional[Dict]: 如果是私聊,包含对方信息的字典;否则为 None。 字典包含: platform, user_id, user_nickname, person_id, person_name @@ -23,29 +24,29 @@ async def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Di chat_target_info = None try: - chat_stream = await asyncio.to_thread(chat_manager.get_stream, chat_id) # Use to_thread if get_stream is sync + chat_stream = await asyncio.to_thread(chat_manager.get_stream, chat_id) # Use to_thread if get_stream is sync # If get_stream is already async, just use: chat_stream = await chat_manager.get_stream(chat_id) - + if chat_stream: if chat_stream.group_info: is_group_chat = True - chat_target_info = None # Explicitly None for group chat - elif chat_stream.user_info: # It's a private chat + chat_target_info = None # Explicitly None for group chat + elif chat_stream.user_info: # It's a private chat is_group_chat = False user_info = chat_stream.user_info platform = chat_stream.platform user_id = user_info.user_id - + # Initialize target_info with basic info target_info = { - 'platform': platform, - 'user_id': user_id, - 'user_nickname': user_info.user_nickname, - 'person_id': None, - 'person_name': None + "platform": platform, + "user_id": user_id, + "user_nickname": user_info.user_nickname, + "person_id": None, + "person_name": None, } - - # Try to fetch person info + + # Try to fetch person info try: # Assume get_person_id is sync (as per original code), keep using to_thread person_id = await asyncio.to_thread(person_info_manager.get_person_id, platform, user_id) @@ -54,18 +55,20 @@ async def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Di # get_value is async, so await it directly person_name = await person_info_manager.get_value(person_id, "person_name") - target_info['person_id'] = person_id - target_info['person_name'] = person_name + target_info["person_id"] = person_id + target_info["person_name"] = person_name except Exception as person_e: - logger.warning(f"获取 person_id 或 person_name 时出错 for {platform}:{user_id} in utils: {person_e}") - - chat_target_info = target_info + logger.warning( + f"获取 person_id 或 person_name 时出错 for {platform}:{user_id} in utils: {person_e}" + ) + + chat_target_info = target_info else: logger.warning(f"无法获取 chat_stream for {chat_id} in utils") # Keep defaults: is_group_chat=False, chat_target_info=None - + except Exception as e: logger.error(f"获取聊天类型和目标信息时出错 for {chat_id}: {e}", exc_info=True) # Keep defaults on error - return is_group_chat, chat_target_info \ No newline at end of file + return is_group_chat, chat_target_info diff --git a/src/plugins/heartFC_chat/heartFC_chat.py b/src/plugins/heartFC_chat/heartFC_chat.py index 260fb4406..dd078f7ce 100644 --- a/src/plugins/heartFC_chat/heartFC_chat.py +++ b/src/plugins/heartFC_chat/heartFC_chat.py @@ -26,7 +26,6 @@ from .heartFC_sender import HeartFCSender from src.plugins.chat.utils import process_llm_response from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager from src.plugins.moods.moods import MoodManager -from src.individuality.individuality import Individuality from src.heart_flow.utils_chat import get_chat_type_and_target_info @@ -197,9 +196,9 @@ class HeartFChatting: # 日志前缀 self.log_prefix: str = str(chat_id) # Initial default, will be updated - # --- Initialize attributes (defaults) --- + # --- Initialize attributes (defaults) --- self.is_group_chat: bool = False - self.chat_target_info: Optional[dict] = None + self.chat_target_info: Optional[dict] = None # --- End Initialization --- # 动作管理器 @@ -244,26 +243,30 @@ class HeartFChatting: """ if self._initialized: return True - + # --- Use utility function to determine chat type and fetch info --- # Note: get_chat_type_and_target_info handles getting the chat_stream internally self.is_group_chat, self.chat_target_info = await get_chat_type_and_target_info(self.stream_id) - + # Update log prefix based on potential stream name (if needed, or get it from chat_stream if util doesn't return it) # Assuming get_chat_type_and_target_info focuses only on type/target # We still need the chat_stream object itself for other operations try: self.chat_stream = await asyncio.to_thread(chat_manager.get_stream, self.stream_id) if not self.chat_stream: - logger.error(f"[HFC:{self.stream_id}] 获取ChatStream失败 during _initialize, though util func might have succeeded earlier.") - return False # Cannot proceed without chat_stream object + logger.error( + f"[HFC:{self.stream_id}] 获取ChatStream失败 during _initialize, though util func might have succeeded earlier." + ) + return False # Cannot proceed without chat_stream object # Update log prefix using the fetched stream object self.log_prefix = f"[{chat_manager.get_stream_name(self.stream_id) or self.stream_id}]" except Exception as e: - logger.error(f"[HFC:{self.stream_id}] 获取ChatStream时出错 in _initialize: {e}") - return False - - logger.debug(f"{self.log_prefix} HeartFChatting initialized: is_group={self.is_group_chat}, target_info={self.chat_target_info}") + logger.error(f"[HFC:{self.stream_id}] 获取ChatStream时出错 in _initialize: {e}") + return False + + logger.debug( + f"{self.log_prefix} HeartFChatting initialized: is_group={self.is_group_chat}, target_info={self.chat_target_info}" + ) # --- End using utility function --- self._initialized = True @@ -853,13 +856,13 @@ class HeartFChatting: # --- 构建提示词 (调用修改后的 PromptBuilder 方法) --- prompt = await prompt_builder.build_planner_prompt( - is_group_chat=self.is_group_chat, # <-- Pass HFC state - chat_target_info=self.chat_target_info, # <-- Pass HFC state - cycle_history=self._cycle_history, # <-- Pass HFC state - observed_messages_str=observed_messages_str, # <-- Pass local variable - current_mind=current_mind, # <-- Pass argument - structured_info=self.sub_mind.structured_info, # <-- Pass SubMind info - current_available_actions=current_available_actions # <-- Pass determined actions + is_group_chat=self.is_group_chat, # <-- Pass HFC state + chat_target_info=self.chat_target_info, # <-- Pass HFC state + cycle_history=self._cycle_history, # <-- Pass HFC state + observed_messages_str=observed_messages_str, # <-- Pass local variable + current_mind=current_mind, # <-- Pass argument + structured_info=self.sub_mind.structured_info, # <-- Pass SubMind info + current_available_actions=current_available_actions, # <-- Pass determined actions ) # --- 调用 LLM (普通文本生成) --- @@ -1279,25 +1282,29 @@ class HeartFChatting: # 2. 获取信息捕捉器 info_catcher = info_catcher_manager.get_info_catcher(thinking_id) - # --- Determine sender_name for private chat --- - sender_name_for_prompt = "某人" # Default for group or if info unavailable + # --- Determine sender_name for private chat --- + sender_name_for_prompt = "某人" # Default for group or if info unavailable if not self.is_group_chat and self.chat_target_info: # Prioritize person_name, then nickname - sender_name_for_prompt = self.chat_target_info.get('person_name') or self.chat_target_info.get('user_nickname') or sender_name_for_prompt - # --- End determining sender_name --- + sender_name_for_prompt = ( + self.chat_target_info.get("person_name") + or self.chat_target_info.get("user_nickname") + or sender_name_for_prompt + ) + # --- End determining sender_name --- # 3. 构建 Prompt with Timer("构建Prompt", {}): # 内部计时器,可选保留 prompt = await prompt_builder.build_prompt( build_mode="focus", - chat_stream=self.chat_stream, # Pass the stream object + chat_stream=self.chat_stream, # Pass the stream object # Focus specific args: reason=reason, current_mind_info=self.sub_mind.current_mind, structured_info=self.sub_mind.structured_info, - sender_name=sender_name_for_prompt, # Pass determined name + sender_name=sender_name_for_prompt, # Pass determined name # Normal specific args (not used in focus mode): - # message_txt="", + # message_txt="", ) # 4. 调用 LLM 生成回复 @@ -1305,9 +1312,9 @@ class HeartFChatting: reasoning_content = None model_name = "unknown_model" if not prompt: - logger.error(f"{self.log_prefix}[Replier-{thinking_id}] Prompt 构建失败,无法生成回复。") - return None - + logger.error(f"{self.log_prefix}[Replier-{thinking_id}] Prompt 构建失败,无法生成回复。") + return None + try: with Timer("LLM生成", {}): # 内部计时器,可选保留 content, reasoning_content, model_name = await self.model_normal.generate_response(prompt) diff --git a/src/plugins/heartFC_chat/heartflow_prompt_builder.py b/src/plugins/heartFC_chat/heartflow_prompt_builder.py index 811b54d52..92346c93d 100644 --- a/src/plugins/heartFC_chat/heartflow_prompt_builder.py +++ b/src/plugins/heartFC_chat/heartflow_prompt_builder.py @@ -138,7 +138,7 @@ JSON 结构如下,包含三个字段 "action", "reasoning", "emoji_query": Prompt("你现在正在做的事情是:{schedule_info}", "schedule_prompt") Prompt("\n你有以下这些**知识**:\n{prompt_info}\n请你**记住上面的知识**,之后可能会用到。\n", "knowledge_prompt") - # --- Template for HeartFChatting (FOCUSED mode) --- + # --- Template for HeartFChatting (FOCUSED mode) --- Prompt( """ {info_from_tools} @@ -157,10 +157,10 @@ JSON 结构如下,包含三个字段 "action", "reasoning", "emoji_query": 回复尽量简短一些。请注意把握聊天内容,{reply_style2}。{prompt_ger} {reply_style1},说中文,不要刻意突出自身学科背景,注意只输出回复内容。 {moderation_prompt}。注意:回复不要输出多余内容(包括前后缀,冒号和引号,括号,表情包,at或 @等 )。""", - "heart_flow_private_prompt", # New template for private FOCUSED chat + "heart_flow_private_prompt", # New template for private FOCUSED chat ) - # --- Template for NormalChat (CHAT mode) --- + # --- Template for NormalChat (CHAT mode) --- Prompt( """ {memory_prompt} @@ -179,17 +179,17 @@ JSON 结构如下,包含三个字段 "action", "reasoning", "emoji_query": 请注意不要输出多余内容(包括前后缀,冒号和引号,括号等),只输出回复内容。 {moderation_prompt} 不要输出多余内容(包括前后缀,冒号和引号,括号(),表情包,at或 @等 )。只输出回复内容""", - "reasoning_prompt_private_main", # New template for private CHAT chat + "reasoning_prompt_private_main", # New template for private CHAT chat ) async def _build_prompt_focus(reason, current_mind_info, structured_info, chat_stream, sender_name) -> str: individuality = Individuality.get_instance() prompt_personality = individuality.get_prompt(x_person=0, level=2) - + # Determine if it's a group chat is_group_chat = bool(chat_stream.group_info) - + # Use sender_name passed from caller for private chat, otherwise use a default for group # Default sender_name for group chat isn't used in the group prompt template, but set for consistency effective_sender_name = sender_name if not is_group_chat else "某人" @@ -243,21 +243,21 @@ async def _build_prompt_focus(reason, current_mind_info, structured_info, chat_s logger.debug("开始构建 focus prompt") - # --- Choose template based on chat type --- + # --- Choose template based on chat type --- if is_group_chat: template_name = "heart_flow_prompt" # Group specific formatting variables (already fetched or default) chat_target_1 = await global_prompt_manager.get_prompt_async("chat_target_group1") chat_target_2 = await global_prompt_manager.get_prompt_async("chat_target_group2") - + prompt = await global_prompt_manager.format_prompt( template_name, info_from_tools=structured_info_prompt, - chat_target=chat_target_1, # Used in group template + chat_target=chat_target_1, # Used in group template chat_talking_prompt=chat_talking_prompt, bot_name=global_config.BOT_NICKNAME, prompt_personality=prompt_personality, - chat_target_2=chat_target_2, # Used in group template + chat_target_2=chat_target_2, # Used in group template current_mind_info=current_mind_info, reply_style2=reply_style2_chosen, reply_style1=reply_style1_chosen, @@ -266,12 +266,12 @@ async def _build_prompt_focus(reason, current_mind_info, structured_info, chat_s moderation_prompt=await global_prompt_manager.get_prompt_async("moderation_prompt"), # sender_name is not used in the group template ) - else: # Private chat + else: # Private chat template_name = "heart_flow_private_prompt" prompt = await global_prompt_manager.format_prompt( template_name, info_from_tools=structured_info_prompt, - sender_name=effective_sender_name, # Used in private template + sender_name=effective_sender_name, # Used in private template chat_talking_prompt=chat_talking_prompt, bot_name=global_config.BOT_NICKNAME, prompt_personality=prompt_personality, @@ -283,7 +283,7 @@ async def _build_prompt_focus(reason, current_mind_info, structured_info, chat_s prompt_ger=prompt_ger, moderation_prompt=await global_prompt_manager.get_prompt_async("moderation_prompt"), ) - # --- End choosing template --- + # --- End choosing template --- logger.debug(f"focus_chat_prompt (is_group={is_group_chat}): \n{prompt}") return prompt @@ -302,10 +302,8 @@ class PromptBuilder: current_mind_info=None, structured_info=None, message_txt=None, - sender_name = "某人", + sender_name="某人", ) -> Optional[str]: - is_group_chat = bool(chat_stream.group_info) - if build_mode == "normal": return await self._build_prompt_normal(chat_stream, message_txt, sender_name) @@ -326,20 +324,22 @@ class PromptBuilder: who_chat_in_group = [] if is_group_chat: - who_chat_in_group = get_recent_group_speaker( - chat_stream.stream_id, - (chat_stream.user_info.platform, chat_stream.user_info.user_id) if chat_stream.user_info else None, - limit=global_config.observation_context_size, - ) + who_chat_in_group = get_recent_group_speaker( + chat_stream.stream_id, + (chat_stream.user_info.platform, chat_stream.user_info.user_id) if chat_stream.user_info else None, + limit=global_config.observation_context_size, + ) elif chat_stream.user_info: - who_chat_in_group.append((chat_stream.user_info.platform, chat_stream.user_info.user_id, chat_stream.user_info.user_nickname)) - + who_chat_in_group.append( + (chat_stream.user_info.platform, chat_stream.user_info.user_id, chat_stream.user_info.user_nickname) + ) + relation_prompt = "" for person in who_chat_in_group: if len(person) >= 3 and person[0] and person[1]: - relation_prompt += await relationship_manager.build_relationship_info(person) + relation_prompt += await relationship_manager.build_relationship_info(person) else: - logger.warning(f"Invalid person tuple encountered for relationship prompt: {person}") + logger.warning(f"Invalid person tuple encountered for relationship prompt: {person}") mood_manager = MoodManager.get_instance() mood_prompt = mood_manager.get_prompt() @@ -425,8 +425,6 @@ class PromptBuilder: end_time = time.time() logger.debug(f"知识检索耗时: {(end_time - start_time):.3f}秒") - - if global_config.ENABLE_SCHEDULE_GEN: schedule_prompt = await global_prompt_manager.format_prompt( "schedule_prompt", schedule_info=bot_schedule.get_current_num_task(num=1, time_info=False) @@ -435,14 +433,14 @@ class PromptBuilder: schedule_prompt = "" logger.debug("开始构建 normal prompt") - - # --- Choose template and format based on chat type --- + + # --- Choose template and format based on chat type --- if is_group_chat: template_name = "reasoning_prompt_main" - effective_sender_name = sender_name + effective_sender_name = sender_name chat_target_1 = await global_prompt_manager.get_prompt_async("chat_target_group1") chat_target_2 = await global_prompt_manager.get_prompt_async("chat_target_group2") - + prompt = await global_prompt_manager.format_prompt( template_name, relation_prompt=relation_prompt, @@ -466,8 +464,8 @@ class PromptBuilder: ) else: template_name = "reasoning_prompt_private_main" - effective_sender_name = sender_name - + effective_sender_name = sender_name + prompt = await global_prompt_manager.format_prompt( template_name, relation_prompt=relation_prompt, @@ -487,7 +485,7 @@ class PromptBuilder: prompt_ger=prompt_ger, moderation_prompt=await global_prompt_manager.get_prompt_async("moderation_prompt"), ) - # --- End choosing template --- + # --- End choosing template --- return prompt @@ -749,9 +747,9 @@ class PromptBuilder: async def build_planner_prompt( self, - is_group_chat: bool, # Now passed as argument - chat_target_info: Optional[dict], # Now passed as argument - cycle_history: Deque["CycleInfo"], # Now passed as argument (Type hint needs import or string) + is_group_chat: bool, # Now passed as argument + chat_target_info: Optional[dict], # Now passed as argument + cycle_history: Deque["CycleInfo"], # Now passed as argument (Type hint needs import or string) observed_messages_str: str, current_mind: Optional[str], structured_info: Dict[str, Any], @@ -760,20 +758,22 @@ class PromptBuilder: ) -> str: """构建 Planner LLM 的提示词 (获取模板并填充数据)""" try: - # --- Determine chat context --- + # --- Determine chat context --- chat_context_description = "你现在正在一个群聊中" - chat_target_name = None # Only relevant for private + chat_target_name = None # Only relevant for private if not is_group_chat and chat_target_info: - chat_target_name = chat_target_info.get('person_name') or chat_target_info.get('user_nickname') or "对方" + chat_target_name = ( + chat_target_info.get("person_name") or chat_target_info.get("user_nickname") or "对方" + ) chat_context_description = f"你正在和 {chat_target_name} 私聊" # --- End determining chat context --- - + # ... (Copy logic from HeartFChatting._build_planner_prompt here) ... # Structured info block structured_info_block = "" if structured_info: structured_info_block = f"以下是一些额外的信息:\n{structured_info}\n" - + # Chat content block chat_content_block = "" if observed_messages_str: @@ -784,14 +784,14 @@ class PromptBuilder: ---""" else: chat_content_block = "当前没有观察到新的聊天内容。\\n" - + # Current mind block current_mind_block = "" if current_mind: current_mind_block = f"你的内心想法:\n{current_mind}" else: current_mind_block = "你的内心想法:\n[没有特别的想法]" - + # Cycle info block (using passed cycle_history) cycle_info_block = "" recent_active_cycles = [] diff --git a/src/plugins/heartFC_chat/normal_chat.py b/src/plugins/heartFC_chat/normal_chat.py index 3668e1874..6b8fb9ce2 100644 --- a/src/plugins/heartFC_chat/normal_chat.py +++ b/src/plugins/heartFC_chat/normal_chat.py @@ -33,14 +33,14 @@ class NormalChat: self.chat_stream = chat_stream self.stream_id = chat_stream.stream_id # Get initial stream name, might be updated in initialize - self.stream_name = chat_manager.get_stream_name(self.stream_id) or self.stream_id + self.stream_name = chat_manager.get_stream_name(self.stream_id) or self.stream_id # Interest dict self.interest_dict = interest_dict - # --- Initialize attributes (defaults) --- + # --- Initialize attributes (defaults) --- self.is_group_chat: bool = False - self.chat_target_info: Optional[dict] = None + self.chat_target_info: Optional[dict] = None # --- End Initialization --- # Other sync initializations @@ -49,21 +49,23 @@ class NormalChat: self.start_time = time.time() self.last_speak_time = 0 self._chat_task: Optional[asyncio.Task] = None - self._initialized = False # Track initialization status - - # logger.info(f"[{self.stream_name}] NormalChat 实例 __init__ 完成 (同步部分)。") + self._initialized = False # Track initialization status + + # logger.info(f"[{self.stream_name}] NormalChat 实例 __init__ 完成 (同步部分)。") # Avoid logging here as stream_name might not be final async def initialize(self): """异步初始化,获取聊天类型和目标信息。""" if self._initialized: return - + # --- Use utility function to determine chat type and fetch info --- self.is_group_chat, self.chat_target_info = await get_chat_type_and_target_info(self.stream_id) # Update stream_name again after potential async call in util func - self.stream_name = chat_manager.get_stream_name(self.stream_id) or self.stream_id - logger.debug(f"[{self.stream_name}] NormalChat initialized: is_group={self.is_group_chat}, target_info={self.chat_target_info}") + self.stream_name = chat_manager.get_stream_name(self.stream_id) or self.stream_id + logger.debug( + f"[{self.stream_name}] NormalChat initialized: is_group={self.is_group_chat}, target_info={self.chat_target_info}" + ) # --- End using utility function --- self._initialized = True logger.info(f"[{self.stream_name}] NormalChat 实例 initialize 完成 (异步部分)。") @@ -437,8 +439,8 @@ class NormalChat: async def start_chat(self): """先进行异步初始化,然后启动聊天任务。""" if not self._initialized: - await self.initialize() # Ensure initialized before starting tasks - + await self.initialize() # Ensure initialized before starting tasks + if self._chat_task is None or self._chat_task.done(): logger.info(f"[{self.stream_name}] 开始后台处理初始兴趣消息和轮询任务...") # Process initial messages first