This commit is contained in:
SengokuCola
2025-05-01 22:50:29 +08:00
parent c4a7b842f6
commit 2669572b30
9 changed files with 220 additions and 188 deletions

View File

@@ -20,7 +20,7 @@ NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS = 60
# 新增状态评估间隔 # 新增状态评估间隔
HF_JUDGE_STATE_UPDATE_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 CLEANUP_INTERVAL_SECONDS = 1200
STATE_UPDATE_INTERVAL_SECONDS = 60 STATE_UPDATE_INTERVAL_SECONDS = 60
@@ -76,7 +76,7 @@ class BackgroundTaskManager:
self._normal_chat_timeout_check_task: Optional[asyncio.Task] = None self._normal_chat_timeout_check_task: Optional[asyncio.Task] = None
self._hf_judge_state_update_task: Optional[asyncio.Task] = None self._hf_judge_state_update_task: Optional[asyncio.Task] = None
self._into_focus_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 self._tasks: List[Optional[asyncio.Task]] = [] # Keep track of all tasks
async def start_tasks(self): async def start_tasks(self):
@@ -129,7 +129,7 @@ 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), lambda: self._run_private_chat_activation_cycle(PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS),
"debug", "debug",
f"私聊激活检查任务已启动 间隔:{PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS}s", f"私聊激活检查任务已启动 间隔:{PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS}s",
@@ -294,5 +294,5 @@ class BackgroundTaskManager:
await _run_periodic_loop( await _run_periodic_loop(
task_name="Private Chat Activation Check", task_name="Private Chat Activation Check",
interval=interval, interval=interval,
task_func=self.subheartflow_manager.sbhf_absent_private_into_focus task_func=self.subheartflow_manager.sbhf_absent_private_into_focus,
) )

View File

@@ -13,9 +13,8 @@ from src.plugins.utils.chat_message_builder import (
get_person_id_list, get_person_id_list,
) )
from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager 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 typing import Optional
from src.plugins.person_info.person_info import person_info_manager
# Import the new utility function # 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
@@ -26,14 +25,14 @@ Prompt(
"""这是qq群聊的聊天记录请总结以下聊天记录的主题 """这是qq群聊的聊天记录请总结以下聊天记录的主题
{chat_logs} {chat_logs}
请用一句话概括,包括人物、事件和主要信息,不要分点。""", 请用一句话概括,包括人物、事件和主要信息,不要分点。""",
"chat_summary_group_prompt" # Template for group chat "chat_summary_group_prompt", # Template for group chat
) )
Prompt( Prompt(
"""这是你和{chat_target}的私聊记录,请总结以下聊天记录的主题: """这是你和{chat_target}的私聊记录,请总结以下聊天记录的主题:
{chat_logs} {chat_logs}
请用一句话概括,包括事件,时间,和主要信息,不要分点。""", 请用一句话概括,包括事件,时间,和主要信息,不要分点。""",
"chat_summary_private_prompt" # Template for private chat "chat_summary_private_prompt", # Template for private chat
) )
# --- End Prompt Template Definition --- # --- End Prompt Template Definition ---
@@ -77,11 +76,12 @@ class ChattingObservation(Observation):
model=global_config.llm_observation, temperature=0.7, max_tokens=300, request_type="chat_observation" model=global_config.llm_observation, temperature=0.7, max_tokens=300, request_type="chat_observation"
) )
async def initialize(self): async def initialize(self):
# --- Use utility function to determine chat type and fetch info --- # --- 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) 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 --- # --- End using utility function ---
# Fetch initial messages (existing logic) # Fetch initial messages (existing logic)
@@ -141,30 +141,33 @@ class ChattingObservation(Observation):
) )
# --- Build prompt using template --- # --- Build prompt using template ---
prompt = None # Initialize prompt as None prompt = None # Initialize prompt as None
try: try:
# 构建 Prompt - 根据 is_group_chat 选择模板 # 构建 Prompt - 根据 is_group_chat 选择模板
if self.is_group_chat: if self.is_group_chat:
prompt_template_name = "chat_summary_group_prompt" prompt_template_name = "chat_summary_group_prompt"
prompt = await global_prompt_manager.format_prompt( prompt = await global_prompt_manager.format_prompt(
prompt_template_name, prompt_template_name, chat_logs=oldest_messages_str
chat_logs=oldest_messages_str
) )
else: else:
# For private chat, add chat_target to the prompt variables # For private chat, add chat_target to the prompt variables
prompt_template_name = "chat_summary_private_prompt" prompt_template_name = "chat_summary_private_prompt"
# Determine the target name for the prompt # Determine the target name for the prompt
chat_target_name = "对方" # Default fallback chat_target_name = "对方" # Default fallback
if self.chat_target_info: if self.chat_target_info:
# Prioritize person_name, then nickname # 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 # Format the private chat prompt
prompt = await global_prompt_manager.format_prompt( prompt = await global_prompt_manager.format_prompt(
prompt_template_name, prompt_template_name,
# Assuming the private prompt template uses {chat_target} # Assuming the private prompt template uses {chat_target}
chat_target=chat_target_name, chat_target=chat_target_name,
chat_logs=oldest_messages_str chat_logs=oldest_messages_str,
) )
except Exception as e: except Exception as e:
logger.error(f"构建总结 Prompt 失败 for chat {self.chat_id}: {e}") logger.error(f"构建总结 Prompt 失败 for chat {self.chat_id}: {e}")
@@ -172,7 +175,7 @@ class ChattingObservation(Observation):
summary = "没有主题的闲聊" # 默认值 summary = "没有主题的闲聊" # 默认值
if prompt: # Check if prompt was built successfully if prompt: # Check if prompt was built successfully
try: try:
summary_result, _, _ = await self.llm_summary.generate_response(prompt) summary_result, _, _ = await self.llm_summary.generate_response(prompt)
if summary_result: # 确保结果不为空 if summary_result: # 确保结果不为空
@@ -183,7 +186,6 @@ class ChattingObservation(Observation):
else: else:
logger.warning(f"因 Prompt 构建失败,跳过 LLM 总结 for chat {self.chat_id}") logger.warning(f"因 Prompt 构建失败,跳过 LLM 总结 for chat {self.chat_id}")
mid_memory = { mid_memory = {
"id": str(int(datetime.now().timestamp())), "id": str(int(datetime.now().timestamp())),
"theme": summary, "theme": summary,

View File

@@ -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.mai_state_manager import MaiStateInfo
from src.heart_flow.chat_state_info import ChatState, ChatStateInfo from src.heart_flow.chat_state_info import ChatState, ChatStateInfo
from src.heart_flow.sub_mind import SubMind 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 from .utils_chat import get_chat_type_and_target_info
@@ -268,7 +267,7 @@ class SubHeartflow:
) )
# 日志前缀 - Moved determination to initialize # 日志前缀 - 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): async def initialize(self):
"""异步初始化方法,创建兴趣流并确定聊天类型""" """异步初始化方法,创建兴趣流并确定聊天类型"""
@@ -276,8 +275,12 @@ class SubHeartflow:
# --- Use utility function to determine chat type and fetch info --- # --- 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) 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) # 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 self.log_prefix = (
logger.debug(f"SubHeartflow {self.chat_id} initialized: is_group={self.is_group_chat}, target_info={self.chat_target_info}") 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 --- # --- End using utility function ---
# Initialize interest system (existing logic) # Initialize interest system (existing logic)
@@ -323,7 +326,7 @@ class SubHeartflow:
# 启动聊天任务 # 启动聊天任务
logger.info(f"{log_prefix} 开始普通聊天,随便水群...") 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 return True
except Exception as e: except Exception as e:
logger.error(f"{log_prefix} 启动 NormalChat 或其初始化时出错: {e}") logger.error(f"{log_prefix} 启动 NormalChat 或其初始化时出错: {e}")

View File

@@ -69,7 +69,7 @@ def init_prompt():
1. 输出想法后考虑是否需要使用工具 1. 输出想法后考虑是否需要使用工具
2. 工具可获取信息或执行操作 2. 工具可获取信息或执行操作
3. 如需处理消息或回复,请使用工具。""" 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 = """ last_loop_t = """
@@ -152,16 +152,18 @@ class SubMind:
# 获取观察对象 # 获取观察对象
observation = self.observations[0] if self.observations else None 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} 无法获取有效的观察对象或缺少聊天类型信息") logger.error(f"{self.log_prefix} 无法获取有效的观察对象或缺少聊天类型信息")
self.update_current_mind("(观察出错了...)") self.update_current_mind("(观察出错了...)")
return self.current_mind, self.past_mind return self.current_mind, self.past_mind
is_group_chat = observation.is_group_chat is_group_chat = observation.is_group_chat
chat_target_info = observation.chat_target_info 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: 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_name = (
chat_target_info.get("person_name") or chat_target_info.get("user_nickname") or chat_target_name
)
# --- End getting observation info --- # --- End getting observation info ---
# 获取观察内容 # 获取观察内容
@@ -290,15 +292,15 @@ class SubMind:
cycle_info_block=cycle_info_block, cycle_info_block=cycle_info_block,
# chat_target_name is not used in group prompt # chat_target_name is not used in group prompt
) )
else: # Private chat else: # Private chat
template_name = "sub_heartflow_prompt_private_before" template_name = "sub_heartflow_prompt_private_before"
prompt = (await global_prompt_manager.get_prompt_async(template_name)).format( prompt = (await global_prompt_manager.get_prompt_async(template_name)).format(
extra_info="", extra_info="",
prompt_personality=prompt_personality, 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, bot_name=individuality.name,
time_now=time_now, 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, chat_observe_info=chat_observe_info,
mood_info=mood_info, mood_info=mood_info,
hf_do_next=hf_do_next, hf_do_next=hf_do_next,

View File

@@ -345,7 +345,8 @@ class SubHeartflowManager:
async with self._lock: async with self._lock:
# 1. 筛选出所有 ABSENT 状态的 *群聊* 子心流 # 1. 筛选出所有 ABSENT 状态的 *群聊* 子心流
absent_group_subflows = [ 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 if hf.chat_state.chat_status == ChatState.ABSENT and hf.is_group_chat
] ]
@@ -684,13 +685,13 @@ class SubHeartflowManager:
current_state = subflow.chat_state.chat_status current_state = subflow.chat_state.chat_status
if current_state == ChatState.FOCUSED: if current_state == ChatState.FOCUSED:
target_state = ChatState.ABSENT # Default target target_state = ChatState.ABSENT # Default target
log_reason = "默认转换 (私聊或群聊)" log_reason = "默认转换 (私聊或群聊)"
# --- Modify logic based on chat type --- # # --- Modify logic based on chat type --- #
if subflow.is_group_chat: if subflow.is_group_chat:
# Group chat: Decide between ABSENT or 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() current_mai_state = self.mai_state_info.get_current_state()
chat_limit = current_mai_state.get_normal_chat_max_num() chat_limit = current_mai_state.get_normal_chat_max_num()
current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT) current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT)
@@ -699,9 +700,11 @@ class SubHeartflowManager:
target_state = ChatState.CHAT target_state = ChatState.CHAT
log_reason = f"群聊随机选择 CHAT (当前 {current_chat_count}/{chat_limit})" log_reason = f"群聊随机选择 CHAT (当前 {current_chat_count}/{chat_limit})"
else: else:
target_state = ChatState.ABSENT # Fallback to ABSENT if CHAT limit reached target_state = ChatState.ABSENT # Fallback to ABSENT if CHAT limit reached
log_reason = f"群聊随机选择 CHAT 但已达上限 ({current_chat_count}/{chat_limit}),转为 ABSENT" log_reason = (
else: # 50% chance to go directly to ABSENT f"群聊随机选择 CHAT 但已达上限 ({current_chat_count}/{chat_limit}),转为 ABSENT"
)
else: # 50% chance to go directly to ABSENT
target_state = ChatState.ABSENT target_state = ChatState.ABSENT
log_reason = "群聊随机选择 ABSENT" log_reason = "群聊随机选择 ABSENT"
else: else:
@@ -732,6 +735,7 @@ class SubHeartflowManager:
logger.warning( logger.warning(
f"[状态转换请求] 收到对 {stream_name} 的请求,但其状态为 {current_state.value} (非 FOCUSED),不执行转换" f"[状态转换请求] 收到对 {stream_name} 的请求,但其状态为 {current_state.value} (非 FOCUSED),不执行转换"
) )
# --- 结束新增 --- # # --- 结束新增 --- #
# --- 新增:处理私聊从 ABSENT 直接到 FOCUSED 的逻辑 --- # # --- 新增:处理私聊从 ABSENT 直接到 FOCUSED 的逻辑 --- #
@@ -747,10 +751,10 @@ class SubHeartflowManager:
# --- 检查是否允许 FOCUS 模式 --- # # --- 检查是否允许 FOCUS 模式 --- #
if not global_config.allow_focus_mode: if not global_config.allow_focus_mode:
# Log less frequently to avoid spam # Log less frequently to avoid spam
# if int(time.time()) % 60 == 0: # if int(time.time()) % 60 == 0:
# logger.debug(f"{log_prefix_task} 配置不允许进入 FOCUSED 状态") # logger.debug(f"{log_prefix_task} 配置不允许进入 FOCUSED 状态")
return return
if focused_limit <= 0: if focused_limit <= 0:
# logger.debug(f"{log_prefix_task} 当前状态 ({current_mai_state.value}) 不允许 FOCUSED 子心流") # logger.debug(f"{log_prefix_task} 当前状态 ({current_mai_state.value}) 不允许 FOCUSED 子心流")
@@ -762,7 +766,8 @@ class SubHeartflowManager:
# --- 筛选出所有 ABSENT 状态的私聊子心流 --- # # --- 筛选出所有 ABSENT 状态的私聊子心流 --- #
eligible_subflows = [ 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 if hf.chat_state.chat_status == ChatState.ABSENT and not hf.is_group_chat
] ]
checked_count = len(eligible_subflows) checked_count = len(eligible_subflows)
@@ -775,8 +780,10 @@ class SubHeartflowManager:
for sub_hf in eligible_subflows: for sub_hf in eligible_subflows:
# --- 再次检查 FOCUSED 上限,因为可能有多个同时激活 --- # # --- 再次检查 FOCUSED 上限,因为可能有多个同时激活 --- #
if current_focused_count >= focused_limit: if current_focused_count >= focused_limit:
logger.debug(f"{log_prefix_task} 已达专注上限 ({current_focused_count}/{focused_limit}),停止检查后续私聊。") logger.debug(
break # 已满,无需再检查其他私聊 f"{log_prefix_task} 已达专注上限 ({current_focused_count}/{focused_limit}),停止检查后续私聊。"
)
break # 已满,无需再检查其他私聊
flow_id = sub_hf.subheartflow_id flow_id = sub_hf.subheartflow_id
stream_name = chat_manager.get_stream_name(flow_id) or flow_id stream_name = chat_manager.get_stream_name(flow_id) or flow_id
@@ -784,36 +791,40 @@ class SubHeartflowManager:
try: try:
# --- 检查是否有新活动 --- # # --- 检查是否有新活动 --- #
observation = sub_hf._get_primary_observation() # 获取主要观察者 observation = sub_hf._get_primary_observation() # 获取主要观察者
is_active = False is_active = False
if observation: if observation:
# 检查自上次状态变为 ABSENT 后是否有新消息 # 检查自上次状态变为 ABSENT 后是否有新消息
# 使用 chat_state_changed_time 可能更精确 # 使用 chat_state_changed_time 可能更精确
# 加一点点缓冲时间(例如 1 秒)以防时间戳完全相等 # 加一点点缓冲时间(例如 1 秒)以防时间戳完全相等
timestamp_to_check = sub_hf.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) has_new = await observation.has_new_messages_since(timestamp_to_check)
if has_new: if has_new:
is_active = True is_active = True
logger.debug(f"{log_prefix} 检测到新消息,标记为活跃。") logger.debug(f"{log_prefix} 检测到新消息,标记为活跃。")
# 可选检查兴趣度是否大于0 (如果需要) # 可选检查兴趣度是否大于0 (如果需要)
# interest_level = await sub_hf.interest_chatting.get_interest() # interest_level = await sub_hf.interest_chatting.get_interest()
# if interest_level > 0: # if interest_level > 0:
# is_active = True # is_active = True
# logger.debug(f"{log_prefix} 检测到兴趣度 > 0 ({interest_level:.2f}),标记为活跃。") # logger.debug(f"{log_prefix} 检测到兴趣度 > 0 ({interest_level:.2f}),标记为活跃。")
else: else:
logger.warning(f"{log_prefix} 无法获取主要观察者来检查活动状态。") logger.warning(f"{log_prefix} 无法获取主要观察者来检查活动状态。")
# --- 如果活跃且未达上限,则尝试转换 --- # # --- 如果活跃且未达上限,则尝试转换 --- #
if is_active: 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) await sub_hf.change_chat_state(ChatState.FOCUSED)
# 确认转换成功 # 确认转换成功
if sub_hf.chat_state.chat_status == ChatState.FOCUSED: if sub_hf.chat_state.chat_status == ChatState.FOCUSED:
transitioned_count += 1 transitioned_count += 1
current_focused_count += 1 # 更新计数器以供本轮后续检查 current_focused_count += 1 # 更新计数器以供本轮后续检查
logger.info(f"{log_prefix} 成功进入 FOCUSED 状态。") logger.info(f"{log_prefix} 成功进入 FOCUSED 状态。")
else: 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: # 不活跃,无需操作 # else: # 不活跃,无需操作
# logger.debug(f"{log_prefix} 未检测到新活动,保持 ABSENT。") # logger.debug(f"{log_prefix} 未检测到新活动,保持 ABSENT。")
@@ -822,7 +833,9 @@ class SubHeartflowManager:
# --- 循环结束后记录总结日志 --- # # --- 循环结束后记录总结日志 --- #
if transitioned_count > 0: 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。"
)
# --- 结束新增 --- # # --- 结束新增 --- #

View File

@@ -6,6 +6,7 @@ from src.plugins.person_info.person_info import person_info_manager
logger = get_logger("heartflow_utils") logger = get_logger("heartflow_utils")
async def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Dict]]: async def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Dict]]:
""" """
获取聊天类型(是否群聊)和私聊对象信息。 获取聊天类型(是否群聊)和私聊对象信息。
@@ -23,14 +24,14 @@ async def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Di
chat_target_info = None chat_target_info = None
try: 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 get_stream is already async, just use: chat_stream = await chat_manager.get_stream(chat_id)
if chat_stream: if chat_stream:
if chat_stream.group_info: if chat_stream.group_info:
is_group_chat = True is_group_chat = True
chat_target_info = None # Explicitly None for group chat chat_target_info = None # Explicitly None for group chat
elif chat_stream.user_info: # It's a private chat elif chat_stream.user_info: # It's a private chat
is_group_chat = False is_group_chat = False
user_info = chat_stream.user_info user_info = chat_stream.user_info
platform = chat_stream.platform platform = chat_stream.platform
@@ -38,11 +39,11 @@ async def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Di
# Initialize target_info with basic info # Initialize target_info with basic info
target_info = { target_info = {
'platform': platform, "platform": platform,
'user_id': user_id, "user_id": user_id,
'user_nickname': user_info.user_nickname, "user_nickname": user_info.user_nickname,
'person_id': None, "person_id": None,
'person_name': None "person_name": None,
} }
# Try to fetch person info # Try to fetch person info
@@ -54,10 +55,12 @@ async def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Di
# get_value is async, so await it directly # get_value is async, so await it directly
person_name = await person_info_manager.get_value(person_id, "person_name") person_name = await person_info_manager.get_value(person_id, "person_name")
target_info['person_id'] = person_id target_info["person_id"] = person_id
target_info['person_name'] = person_name target_info["person_name"] = person_name
except Exception as person_e: except Exception as person_e:
logger.warning(f"获取 person_id 或 person_name 时出错 for {platform}:{user_id} in utils: {person_e}") logger.warning(
f"获取 person_id 或 person_name 时出错 for {platform}:{user_id} in utils: {person_e}"
)
chat_target_info = target_info chat_target_info = target_info
else: else:

View File

@@ -26,7 +26,6 @@ from .heartFC_sender import HeartFCSender
from src.plugins.chat.utils import process_llm_response from src.plugins.chat.utils import process_llm_response
from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager
from src.plugins.moods.moods import MoodManager 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 from src.heart_flow.utils_chat import get_chat_type_and_target_info
@@ -255,15 +254,19 @@ class HeartFChatting:
try: try:
self.chat_stream = await asyncio.to_thread(chat_manager.get_stream, self.stream_id) self.chat_stream = await asyncio.to_thread(chat_manager.get_stream, self.stream_id)
if not self.chat_stream: if not self.chat_stream:
logger.error(f"[HFC:{self.stream_id}] 获取ChatStream失败 during _initialize, though util func might have succeeded earlier.") logger.error(
return False # Cannot proceed without chat_stream object 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 # Update log prefix using the fetched stream object
self.log_prefix = f"[{chat_manager.get_stream_name(self.stream_id) or self.stream_id}]" self.log_prefix = f"[{chat_manager.get_stream_name(self.stream_id) or self.stream_id}]"
except Exception as e: except Exception as e:
logger.error(f"[HFC:{self.stream_id}] 获取ChatStream时出错 in _initialize: {e}") logger.error(f"[HFC:{self.stream_id}] 获取ChatStream时出错 in _initialize: {e}")
return False return False
logger.debug(f"{self.log_prefix} HeartFChatting initialized: is_group={self.is_group_chat}, target_info={self.chat_target_info}") logger.debug(
f"{self.log_prefix} HeartFChatting initialized: is_group={self.is_group_chat}, target_info={self.chat_target_info}"
)
# --- End using utility function --- # --- End using utility function ---
self._initialized = True self._initialized = True
@@ -853,13 +856,13 @@ class HeartFChatting:
# --- 构建提示词 (调用修改后的 PromptBuilder 方法) --- # --- 构建提示词 (调用修改后的 PromptBuilder 方法) ---
prompt = await prompt_builder.build_planner_prompt( prompt = await prompt_builder.build_planner_prompt(
is_group_chat=self.is_group_chat, # <-- Pass HFC state is_group_chat=self.is_group_chat, # <-- Pass HFC state
chat_target_info=self.chat_target_info, # <-- Pass HFC state chat_target_info=self.chat_target_info, # <-- Pass HFC state
cycle_history=self._cycle_history, # <-- Pass HFC state cycle_history=self._cycle_history, # <-- Pass HFC state
observed_messages_str=observed_messages_str, # <-- Pass local variable observed_messages_str=observed_messages_str, # <-- Pass local variable
current_mind=current_mind, # <-- Pass argument current_mind=current_mind, # <-- Pass argument
structured_info=self.sub_mind.structured_info, # <-- Pass SubMind info structured_info=self.sub_mind.structured_info, # <-- Pass SubMind info
current_available_actions=current_available_actions # <-- Pass determined actions current_available_actions=current_available_actions, # <-- Pass determined actions
) )
# --- 调用 LLM (普通文本生成) --- # --- 调用 LLM (普通文本生成) ---
@@ -1280,22 +1283,26 @@ class HeartFChatting:
info_catcher = info_catcher_manager.get_info_catcher(thinking_id) info_catcher = info_catcher_manager.get_info_catcher(thinking_id)
# --- Determine sender_name for private chat --- # --- Determine sender_name for private chat ---
sender_name_for_prompt = "某人" # Default for group or if info unavailable sender_name_for_prompt = "某人" # Default for group or if info unavailable
if not self.is_group_chat and self.chat_target_info: if not self.is_group_chat and self.chat_target_info:
# Prioritize person_name, then nickname # 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 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 --- # --- End determining sender_name ---
# 3. 构建 Prompt # 3. 构建 Prompt
with Timer("构建Prompt", {}): # 内部计时器,可选保留 with Timer("构建Prompt", {}): # 内部计时器,可选保留
prompt = await prompt_builder.build_prompt( prompt = await prompt_builder.build_prompt(
build_mode="focus", build_mode="focus",
chat_stream=self.chat_stream, # Pass the stream object chat_stream=self.chat_stream, # Pass the stream object
# Focus specific args: # Focus specific args:
reason=reason, reason=reason,
current_mind_info=self.sub_mind.current_mind, current_mind_info=self.sub_mind.current_mind,
structured_info=self.sub_mind.structured_info, 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): # Normal specific args (not used in focus mode):
# message_txt="", # message_txt="",
) )
@@ -1305,8 +1312,8 @@ class HeartFChatting:
reasoning_content = None reasoning_content = None
model_name = "unknown_model" model_name = "unknown_model"
if not prompt: if not prompt:
logger.error(f"{self.log_prefix}[Replier-{thinking_id}] Prompt 构建失败,无法生成回复。") logger.error(f"{self.log_prefix}[Replier-{thinking_id}] Prompt 构建失败,无法生成回复。")
return None return None
try: try:
with Timer("LLM生成", {}): # 内部计时器,可选保留 with Timer("LLM生成", {}): # 内部计时器,可选保留

View File

@@ -157,7 +157,7 @@ JSON 结构如下,包含三个字段 "action", "reasoning", "emoji_query":
回复尽量简短一些。请注意把握聊天内容,{reply_style2}{prompt_ger} 回复尽量简短一些。请注意把握聊天内容,{reply_style2}{prompt_ger}
{reply_style1},说中文,不要刻意突出自身学科背景,注意只输出回复内容。 {reply_style1},说中文,不要刻意突出自身学科背景,注意只输出回复内容。
{moderation_prompt}。注意:回复不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 )。""", {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) ---
@@ -179,7 +179,7 @@ JSON 结构如下,包含三个字段 "action", "reasoning", "emoji_query":
请注意不要输出多余内容(包括前后缀,冒号和引号,括号等),只输出回复内容。 请注意不要输出多余内容(包括前后缀,冒号和引号,括号等),只输出回复内容。
{moderation_prompt} {moderation_prompt}
不要输出多余内容(包括前后缀,冒号和引号,括号()表情包at或 @等 )。只输出回复内容""", 不要输出多余内容(包括前后缀,冒号和引号,括号()表情包at或 @等 )。只输出回复内容""",
"reasoning_prompt_private_main", # New template for private CHAT chat "reasoning_prompt_private_main", # New template for private CHAT chat
) )
@@ -253,11 +253,11 @@ async def _build_prompt_focus(reason, current_mind_info, structured_info, chat_s
prompt = await global_prompt_manager.format_prompt( prompt = await global_prompt_manager.format_prompt(
template_name, template_name,
info_from_tools=structured_info_prompt, 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, chat_talking_prompt=chat_talking_prompt,
bot_name=global_config.BOT_NICKNAME, bot_name=global_config.BOT_NICKNAME,
prompt_personality=prompt_personality, 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, current_mind_info=current_mind_info,
reply_style2=reply_style2_chosen, reply_style2=reply_style2_chosen,
reply_style1=reply_style1_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"), moderation_prompt=await global_prompt_manager.get_prompt_async("moderation_prompt"),
# sender_name is not used in the group template # sender_name is not used in the group template
) )
else: # Private chat else: # Private chat
template_name = "heart_flow_private_prompt" template_name = "heart_flow_private_prompt"
prompt = await global_prompt_manager.format_prompt( prompt = await global_prompt_manager.format_prompt(
template_name, template_name,
info_from_tools=structured_info_prompt, 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, chat_talking_prompt=chat_talking_prompt,
bot_name=global_config.BOT_NICKNAME, bot_name=global_config.BOT_NICKNAME,
prompt_personality=prompt_personality, prompt_personality=prompt_personality,
@@ -302,10 +302,8 @@ class PromptBuilder:
current_mind_info=None, current_mind_info=None,
structured_info=None, structured_info=None,
message_txt=None, message_txt=None,
sender_name = "某人", sender_name="某人",
) -> Optional[str]: ) -> Optional[str]:
is_group_chat = bool(chat_stream.group_info)
if build_mode == "normal": if build_mode == "normal":
return await self._build_prompt_normal(chat_stream, message_txt, sender_name) return await self._build_prompt_normal(chat_stream, message_txt, sender_name)
@@ -326,20 +324,22 @@ class PromptBuilder:
who_chat_in_group = [] who_chat_in_group = []
if is_group_chat: if is_group_chat:
who_chat_in_group = get_recent_group_speaker( who_chat_in_group = get_recent_group_speaker(
chat_stream.stream_id, chat_stream.stream_id,
(chat_stream.user_info.platform, chat_stream.user_info.user_id) if chat_stream.user_info else None, (chat_stream.user_info.platform, chat_stream.user_info.user_id) if chat_stream.user_info else None,
limit=global_config.observation_context_size, limit=global_config.observation_context_size,
) )
elif chat_stream.user_info: 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 = "" relation_prompt = ""
for person in who_chat_in_group: for person in who_chat_in_group:
if len(person) >= 3 and person[0] and person[1]: 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: 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_manager = MoodManager.get_instance()
mood_prompt = mood_manager.get_prompt() mood_prompt = mood_manager.get_prompt()
@@ -425,8 +425,6 @@ class PromptBuilder:
end_time = time.time() end_time = time.time()
logger.debug(f"知识检索耗时: {(end_time - start_time):.3f}") logger.debug(f"知识检索耗时: {(end_time - start_time):.3f}")
if global_config.ENABLE_SCHEDULE_GEN: if global_config.ENABLE_SCHEDULE_GEN:
schedule_prompt = await global_prompt_manager.format_prompt( schedule_prompt = await global_prompt_manager.format_prompt(
"schedule_prompt", schedule_info=bot_schedule.get_current_num_task(num=1, time_info=False) "schedule_prompt", schedule_info=bot_schedule.get_current_num_task(num=1, time_info=False)
@@ -749,9 +747,9 @@ class PromptBuilder:
async def build_planner_prompt( async def build_planner_prompt(
self, self,
is_group_chat: bool, # Now passed as argument is_group_chat: bool, # Now passed as argument
chat_target_info: Optional[dict], # 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) cycle_history: Deque["CycleInfo"], # Now passed as argument (Type hint needs import or string)
observed_messages_str: str, observed_messages_str: str,
current_mind: Optional[str], current_mind: Optional[str],
structured_info: Dict[str, Any], structured_info: Dict[str, Any],
@@ -762,9 +760,11 @@ class PromptBuilder:
try: try:
# --- Determine chat context --- # --- Determine chat context ---
chat_context_description = "你现在正在一个群聊中" 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: 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} 私聊" chat_context_description = f"你正在和 {chat_target_name} 私聊"
# --- End determining chat context --- # --- End determining chat context ---

View File

@@ -49,7 +49,7 @@ class NormalChat:
self.start_time = time.time() self.start_time = time.time()
self.last_speak_time = 0 self.last_speak_time = 0
self._chat_task: Optional[asyncio.Task] = None self._chat_task: Optional[asyncio.Task] = None
self._initialized = False # Track initialization status self._initialized = False # Track initialization status
# logger.info(f"[{self.stream_name}] NormalChat 实例 __init__ 完成 (同步部分)。") # logger.info(f"[{self.stream_name}] NormalChat 实例 __init__ 完成 (同步部分)。")
# Avoid logging here as stream_name might not be final # Avoid logging here as stream_name might not be final
@@ -63,7 +63,9 @@ class NormalChat:
self.is_group_chat, self.chat_target_info = await get_chat_type_and_target_info(self.stream_id) 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 # 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 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}") logger.debug(
f"[{self.stream_name}] NormalChat initialized: is_group={self.is_group_chat}, target_info={self.chat_target_info}"
)
# --- End using utility function --- # --- End using utility function ---
self._initialized = True self._initialized = True
logger.info(f"[{self.stream_name}] NormalChat 实例 initialize 完成 (异步部分)。") logger.info(f"[{self.stream_name}] NormalChat 实例 initialize 完成 (异步部分)。")
@@ -437,7 +439,7 @@ class NormalChat:
async def start_chat(self): async def start_chat(self):
"""先进行异步初始化,然后启动聊天任务。""" """先进行异步初始化,然后启动聊天任务。"""
if not self._initialized: 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(): if self._chat_task is None or self._chat_task.done():
logger.info(f"[{self.stream_name}] 开始后台处理初始兴趣消息和轮询任务...") logger.info(f"[{self.stream_name}] 开始后台处理初始兴趣消息和轮询任务...")