feat:为HFC创建私聊特殊prompt模板

This commit is contained in:
SengokuCola
2025-05-01 21:38:38 +08:00
parent d97aa6b115
commit 462fac2547
9 changed files with 417 additions and 121 deletions

View File

@@ -22,7 +22,7 @@ logger = get_logger("config")
# 考虑到实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码
is_test = False
mai_version_main = "0.6.3"
mai_version_fix = "fix-1"
mai_version_fix = "fix-2"
if mai_version_fix:
if is_test:

View File

@@ -19,6 +19,8 @@ INTEREST_EVAL_INTERVAL_SECONDS = 5
NORMAL_CHAT_TIMEOUT_CHECK_INTERVAL_SECONDS = 60
# 新增状态评估间隔
HF_JUDGE_STATE_UPDATE_INTERVAL_SECONDS = 60
# 新增私聊激活检查间隔
PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS = 5 # 与兴趣评估类似设为5秒
CLEANUP_INTERVAL_SECONDS = 1200
STATE_UPDATE_INTERVAL_SECONDS = 60
@@ -71,9 +73,10 @@ class BackgroundTaskManager:
self._state_update_task: Optional[asyncio.Task] = None
self._cleanup_task: Optional[asyncio.Task] = None
self._logging_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._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._tasks: List[Optional[asyncio.Task]] = [] # Keep track of all tasks
async def start_tasks(self):
@@ -124,6 +127,14 @@ class BackgroundTaskManager:
f"专注评估任务已启动 间隔:{INTEREST_EVAL_INTERVAL_SECONDS}s",
"_into_focus_task",
),
# 新增私聊激活任务配置
(
# Use lambda to pass the interval to the runner function
lambda: self._run_private_chat_activation_cycle(PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS),
"debug",
f"私聊激活检查任务已启动 间隔:{PRIVATE_CHAT_ACTIVATION_CHECK_INTERVAL_SECONDS}s",
"_private_chat_activation_task",
),
]
# 统一启动所有任务
@@ -277,3 +288,11 @@ class BackgroundTaskManager:
interval=INTEREST_EVAL_INTERVAL_SECONDS,
task_func=self._perform_into_focus_work,
)
# 新增私聊激活任务运行器
async def _run_private_chat_activation_cycle(self, interval: int):
await _run_periodic_loop(
task_name="Private Chat Activation Check",
interval=interval,
task_func=self.subheartflow_manager.sbhf_absent_private_into_focus
)

View File

@@ -12,9 +12,31 @@ from src.plugins.utils.chat_message_builder import (
num_new_messages_since,
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
logger = get_logger("observation")
# --- Define Prompt Templates for Chat Summary ---
Prompt(
"""这是qq群聊的聊天记录请总结以下聊天记录的主题
{chat_logs}
请用一句话概括,包括人物、事件和主要信息,不要分点。""",
"chat_summary_group_prompt" # Template for group chat
)
Prompt(
"""这是你和{chat_target}的私聊记录,请总结以下聊天记录的主题:
{chat_logs}
请用一句话概括,包括事件,时间,和主要信息,不要分点。""",
"chat_summary_private_prompt" # Template for private chat
)
# --- End Prompt Template Definition ---
# 所有观察的基类
class Observation:
@@ -34,28 +56,37 @@ class ChattingObservation(Observation):
super().__init__("chat", chat_id)
self.chat_id = chat_id
# --- Initialize attributes (defaults) ---
self.is_group_chat: bool = False
self.chat_target_info: Optional[dict] = None
# --- End Initialization ---
# --- Other attributes initialized in __init__ ---
self.talking_message = []
self.talking_message_str = ""
self.talking_message_str_truncate = ""
self.name = global_config.BOT_NICKNAME
self.nick_name = global_config.BOT_ALIAS_NAMES
self.max_now_obs_len = global_config.observation_context_size
self.overlap_len = global_config.compressed_length
self.mid_memorys = []
self.max_mid_memory_len = global_config.compress_length_limit
self.mid_memory_info = ""
self.person_list = []
self.llm_summary = LLMRequest(
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}")
# --- End using utility function ---
# Fetch initial messages (existing logic)
initial_messages = get_raw_msg_before_timestamp_with_chat(self.chat_id, self.last_observe_time, 10)
self.talking_message = initial_messages # 将这些消息设为初始上下文
self.talking_message = initial_messages
self.talking_message_str = await build_readable_messages(self.talking_message)
# 进行一次观察 返回观察结果observe_info
@@ -109,18 +140,49 @@ class ChattingObservation(Observation):
messages=oldest_messages, timestamp_mode="normal", read_mark=0
)
# 调用 LLM 总结主题
prompt = (
f"请总结以下聊天记录的主题:\n{oldest_messages_str}\n用一句话概括包括人物事件和主要信息,不要分点:"
)
summary = "没有主题的闲聊" # 默认值
# --- Build prompt using template ---
prompt = None # Initialize prompt as None
try:
summary_result, _ = await self.llm_summary.generate_response_async(prompt)
# 构建 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
)
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
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
# 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
)
except Exception as e:
logger.error(f"构建总结 Prompt 失败 for chat {self.chat_id}: {e}")
# prompt remains None
summary = "没有主题的闲聊" # 默认值
if prompt: # Check if prompt was built successfully
try:
summary_result, _, _ = await self.llm_summary.generate_response(prompt)
if summary_result: # 确保结果不为空
summary = summary_result
except Exception as e:
logger.error(f"总结主题失败 for chat {self.chat_id}: {e}")
# 保留默认总结 "没有主题的闲聊"
else:
logger.warning(f"因 Prompt 构建失败,跳过 LLM 总结 for chat {self.chat_id}")
mid_memory = {
"id": str(int(datetime.now().timestamp())),

View File

@@ -13,6 +13,8 @@ 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
# 定义常量 (从 interest.py 移动过来)
@@ -238,6 +240,11 @@ class SubHeartflow:
self.chat_state_last_time: float = 0
self.history_chat_state: List[Tuple[ChatState, float]] = []
# --- Initialize attributes ---
self.is_group_chat: bool = False
self.chat_target_info: Optional[dict] = None
# --- End Initialization ---
# 兴趣检测器
self.interest_chatting: InterestChatting = InterestChatting()
@@ -260,11 +267,20 @@ class SubHeartflow:
subheartflow_id=self.subheartflow_id, chat_state=self.chat_state, observations=self.observations
)
# 日志前缀
self.log_prefix = chat_manager.get_stream_name(self.subheartflow_id) or self.subheartflow_id
# 日志前缀 - Moved determination to initialize
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}")
# --- End using utility function ---
# Initialize interest system (existing logic)
await self.interest_chatting.initialize()
logger.debug(f"{self.log_prefix} InterestChatting 实例已初始化。")
@@ -286,26 +302,33 @@ class SubHeartflow:
async def _start_normal_chat(self) -> bool:
"""
启动 NormalChat 实例,
进入 CHAT 状态时使用
确保 HeartFChatting 已停止
启动 NormalChat 实例,并进行异步初始化。
进入 CHAT 状态时使用
确保 HeartFChatting 已停止。
"""
await self._stop_heart_fc_chat() # 确保 专注聊天已停止
log_prefix = self.log_prefix
try:
# 获取聊天流并创建 NormalChat 实例
# 获取聊天流并创建 NormalChat 实例 (同步部分)
chat_stream = chat_manager.get_stream(self.chat_id)
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
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}")
logger.error(f"{log_prefix} 启动 NormalChat 或其初始化时出错: {e}")
logger.error(traceback.format_exc())
self.normal_chat_instance = None # 启动失败,清理实例
self.normal_chat_instance = None # 启动/初始化失败,清理实例
return False
async def _stop_heart_fc_chat(self):

View File

@@ -335,28 +335,36 @@ class SubHeartflowManager:
async def sbhf_absent_into_chat(self):
"""
随机选一个 ABSENT 状态的子心流,评估是否应转换为 CHAT 状态。
随机选一个 ABSENT 状态的 *群聊* 子心流,评估是否应转换为 CHAT 状态。
每次调用最多转换一个。
私聊会被忽略。
"""
current_mai_state = self.mai_state_info.get_current_state()
chat_limit = current_mai_state.get_normal_chat_max_num()
async with self._lock:
# 1. 筛选出所有 ABSENT 状态的子心流
absent_subflows = [
hf for hf in self.subheartflows.values() if hf.chat_state.chat_status == ChatState.ABSENT
# 1. 筛选出所有 ABSENT 状态的 *群聊* 子心流
absent_group_subflows = [
hf for hf in self.subheartflows.values()
if hf.chat_state.chat_status == ChatState.ABSENT and hf.is_group_chat
]
if not absent_subflows:
logger.debug("没有摸鱼的子心流可以评估。") # 日志太频繁,注释掉
if not absent_group_subflows:
# logger.debug("没有摸鱼的群聊子心流可以评估。") # 日志太频繁
return # 没有目标,直接返回
# 2. 随机选一个幸运儿
sub_hf_to_evaluate = random.choice(absent_subflows)
sub_hf_to_evaluate = random.choice(absent_group_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}]"
# --- 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 状态评估。")
# return
# --- End check ---
# 3. 检查 CHAT 上限
current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT)
if current_chat_count >= chat_limit:
@@ -658,8 +666,10 @@ class SubHeartflowManager:
# --- 新增:处理来自 HeartFChatting 的状态转换请求 --- #
async def sbhf_focus_into_absent(self, subflow_id: Any):
"""
接收来自 HeartFChatting 的请求,将特定子心流的状态转换为 ABSENT。
接收来自 HeartFChatting 的请求,将特定子心流的状态转换为 ABSENT 或 CHAT
通常在连续多次 "no_reply" 后被调用。
对于私聊,总是转换为 ABSENT。
对于群聊,随机决定转换为 ABSENT 或 CHAT (如果 CHAT 未达上限)。
Args:
subflow_id: 需要转换状态的子心流 ID。
@@ -667,50 +677,44 @@ class SubHeartflowManager:
async with self._lock:
subflow = self.subheartflows.get(subflow_id)
if not subflow:
logger.warning(f"[状态转换请求] 尝试转换不存在的子心流 {subflow_id} 到 ABSENT")
logger.warning(f"[状态转换请求] 尝试转换不存在的子心流 {subflow_id} 到 ABSENT/CHAT")
return
stream_name = chat_manager.get_stream_name(subflow_id) or subflow_id
current_state = subflow.chat_state.chat_status
# 仅当子心流处于 FOCUSED 状态时才进行转换
# 因为 HeartFChatting 只在 FOCUSED 状态下运行
if current_state == ChatState.FOCUSED:
target_state = ChatState.ABSENT # 默认目标状态
log_reason = "默认转换"
target_state = ChatState.ABSENT # Default target
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先检查限制
# --- 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
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})"
)
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
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"
)
log_reason = "群聊随机选择 ABSENT"
else:
# Private chat: Always go to ABSENT
target_state = ChatState.ABSENT
log_reason = "私聊退出 FOCUSED转为 ABSENT"
# --- End modification --- #
# 开始转换
logger.info(
f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 尝试转换为 {target_state.value} ({log_reason})"
)
try:
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}")
@@ -728,5 +732,98 @@ class SubHeartflowManager:
logger.warning(
f"[状态转换请求] 收到对 {stream_name} 的请求,但其状态为 {current_state.value} (非 FOCUSED),不执行转换"
)
# --- 结束新增 --- #
# --- 新增:处理私聊从 ABSENT 直接到 FOCUSED 的逻辑 --- #
async def sbhf_absent_private_into_focus(self):
"""检查 ABSENT 状态的私聊子心流是否有新活动,若有且未达 FOCUSED 上限,则直接转换为 FOCUSED。"""
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
if focused_limit <= 0:
# logger.debug(f"{log_prefix_task} 当前状态 ({current_mai_state.value}) 不允许 FOCUSED 子心流")
return
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()
if hf.chat_state.chat_status == ChatState.ABSENT and not hf.is_group_chat
]
checked_count = len(eligible_subflows)
if not eligible_subflows:
# logger.debug(f"{log_prefix_task} 没有 ABSENT 状态的私聊子心流可以评估。")
return
# --- 遍历评估每个符合条件的私聊 --- #
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 # 已满,无需再检查其他私聊
flow_id = sub_hf.subheartflow_id
stream_name = chat_manager.get_stream_name(flow_id) or flow_id
log_prefix = f"[{stream_name}]({log_prefix_task})"
try:
# --- 检查是否有新活动 --- #
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
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}),标记为活跃。")
else:
logger.warning(f"{log_prefix} 无法获取主要观察者来检查活动状态。")
# --- 如果活跃且未达上限,则尝试转换 --- #
if is_active:
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 # 更新计数器以供本轮后续检查
logger.info(f"{log_prefix} 成功进入 FOCUSED 状态。")
else:
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。")
# --- 结束新增 --- #
# --- 结束新增:处理来自 HeartFChatting 的状态转换请求 --- #

View File

@@ -0,0 +1,76 @@
import asyncio
from typing import Optional, Tuple, Dict
from src.common.logger_manager import get_logger
from src.plugins.chat.chat_stream import chat_manager
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]]:
"""
获取聊天类型(是否群聊)和私聊对象信息。
Args:
chat_id: 聊天流ID
Returns:
Tuple[bool, Optional[Dict]]:
- bool: 是否为群聊 (True 是群聊, False 是私聊或未知)
- Optional[Dict]: 如果是私聊,包含对方信息的字典;否则为 None。
字典包含: platform, user_id, user_nickname, person_id, person_name
"""
is_group_chat = False # Default to private/unknown
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
# 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
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
}
# Try to fetch person info (assuming person_info_manager methods are sync)
try:
# Use asyncio.to_thread for potentially blocking sync calls
person_id = await asyncio.to_thread(person_info_manager.get_person_id, platform, user_id)
person_name = None
if person_id:
person_name = await asyncio.to_thread(person_info_manager.get_value, person_id, "person_name")
# If person_info_manager methods are async, await them directly:
# person_id = await person_info_manager.get_person_id(platform, user_id)
# person_name = None
# if person_id:
# person_name = await person_info_manager.get_value(person_id, "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
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

View File

@@ -27,6 +27,7 @@ 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
WAITING_TIME_THRESHOLD = 300 # 等待新消息时间阈值,单位秒
@@ -194,7 +195,12 @@ class HeartFChatting:
self.on_consecutive_no_reply_callback = on_consecutive_no_reply_callback
# 日志前缀
self.log_prefix: str = f"[{chat_manager.get_stream_name(chat_id) or chat_id}]"
self.log_prefix: str = str(chat_id) # Initial default, will be updated
# --- Initialize attributes (defaults) ---
self.is_group_chat: bool = False
self.chat_target_info: Optional[dict] = None
# --- End Initialization ---
# 动作管理器
self.action_manager = ActionManager()
@@ -234,22 +240,34 @@ class HeartFChatting:
async def _initialize(self) -> bool:
"""
懒初始化以使用提供的标识符解析chat_stream。
确保实例已准备好处理触发器。
懒初始化解析chat_stream, 获取聊天类型和目标信息
"""
if self._initialized:
return True
self.chat_stream = chat_manager.get_stream(self.stream_id)
# --- 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"{self.log_prefix} 获取ChatStream失败")
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
# 更新日志前缀(以防流名称发生变化)
self.log_prefix = f"[{chat_manager.get_stream_name(self.stream_id) or self.stream_id}]"
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
logger.debug(f"{self.log_prefix}麦麦感觉到了,可以开始认真水群 ")
logger.debug(f"{self.log_prefix} 麦麦感觉到了,可以开始认真水群 ")
return True
async def start(self):

View File

@@ -79,7 +79,7 @@ def init_prompt():
- 避免重复或评价自己的发言
- 不要和自己聊天
决策任务
决策任务
{action_options_text}
你必须从上面列出的可用行动中选择一个,并说明原因。
@@ -90,20 +90,6 @@ JSON 结构如下,包含三个字段 "action", "reasoning", "emoji_query":
"reasoning": "string", // 做出此决定的详细理由和思考过程,说明你如何应用了回复原则
"emoji_query": "string" // 可选。如果行动是 'emoji_reply',必须提供表情主题(填写表情包的适用场合);如果行动是 'text_reply' 且你想附带表情,也在此提供表情主题,否则留空字符串 ""。遵循回复原则,不要滥用。
}}
例如:
{{
"action": "text_reply",
"reasoning": "用户提到了我,且问题比较具体,适合用文本回复。考虑到内容,可以带上一个微笑表情。",
"emoji_query": "微笑"
}}
{{
"action": "no_reply",
"reasoning": "我已经连续回复了两次,而且这个话题我不太感兴趣,根据回复原则,选择不回复,等待其他人发言。",
"emoji_query": ""
}}
请输出你的决策 JSON
""", # 使用三引号避免内部引号问题
"planner_prompt", # 保持名称不变,替换内容

View File

@@ -19,6 +19,7 @@ from src.plugins.chat.chat_stream import ChatStream, chat_manager
from src.plugins.person_info.relationship_manager import relationship_manager
from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager
from src.plugins.utils.timer_calculator import Timer
from src.heart_flow.utils_chat import get_chat_type_and_target_info
logger = get_logger("chat")
@@ -26,28 +27,46 @@ logger = get_logger("chat")
class NormalChat:
def __init__(self, chat_stream: ChatStream, interest_dict: dict):
"""
初始化 NormalChat 实例,针对特定的 ChatStream。
Args:
chat_stream (ChatStream): 此 NormalChat 实例关联的聊天流对象。
"""
"""初始化 NormalChat 实例。只进行同步操作。"""
# Basic info from chat_stream (sync)
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
# Interest dict
self.interest_dict = interest_dict
# --- Initialize attributes (defaults) ---
self.is_group_chat: bool = False
self.chat_target_info: Optional[dict] = None
# --- End Initialization ---
# Other sync initializations
self.gpt = NormalChatGenerator()
self.mood_manager = MoodManager.get_instance() # MoodManager 保持单例
# 存储此实例的兴趣监控任务
self.mood_manager = MoodManager.get_instance()
self.start_time = time.time()
self.last_speak_time = 0
self._chat_task: Optional[asyncio.Task] = None
logger.info(f"[{self.stream_name}] NormalChat 实例初始化完成。")
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}")
# --- End using utility function ---
self._initialized = True
logger.info(f"[{self.stream_name}] NormalChat 实例 initialize 完成 (异步部分)。")
# 改为实例方法
async def _create_thinking_message(self, message: MessageRecv) -> str:
@@ -416,22 +435,18 @@ class NormalChat:
# 改为实例方法, 移除 chat 参数
async def start_chat(self):
"""为此 NormalChat 实例关联的 ChatStream 启动聊天任务(如果尚未运行),
并在后台处理一次初始的高兴趣消息。""" # 文言文注释示例:启聊之始,若有遗珠,当于暗处拂拭,勿碍正途。
if self._chat_task is None or self._chat_task.done():
# --- 修改:使用 create_task 启动初始消息处理 ---
logger.info(f"[{self.stream_name}] 开始后台处理初始兴趣消息...")
# 创建一个任务来处理初始消息,不阻塞当前流程
_initial_process_task = asyncio.create_task(self._process_initial_interest_messages())
# 可以考虑给这个任务也添加完成回调来记录日志或处理错误
# initial_process_task.add_done_callback(...)
# --- 修改结束 ---
"""先进行异步初始化,然后启动聊天任务。"""
if not self._initialized:
await self.initialize() # Ensure initialized before starting tasks
# 启动后台轮询任务 (这部分不变)
logger.info(f"[{self.stream_name}] 启动后台兴趣消息轮询任务...")
polling_task = asyncio.create_task(self._reply_interested_message()) # 注意变量名区分
if self._chat_task is None or self._chat_task.done():
logger.info(f"[{self.stream_name}] 开始后台处理初始兴趣消息轮询任务...")
# Process initial messages first
await self._process_initial_interest_messages()
# Then start polling task
polling_task = asyncio.create_task(self._reply_interested_message())
polling_task.add_done_callback(lambda t: self._handle_task_completion(t))
self._chat_task = polling_task # self._chat_task 仍然指向主要的轮询任务
self._chat_task = polling_task
else:
logger.info(f"[{self.stream_name}] 聊天轮询任务已在运行中。")