feat:为HFC创建私聊特殊prompt模板
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
if summary_result: # 确保结果不为空
|
||||
summary = summary_result
|
||||
# 构建 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"总结主题失败 for chat {self.chat_id}: {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())),
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -335,27 +335,35 @@ 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)
|
||||
@@ -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,先检查限制
|
||||
current_mai_state = self.mai_state_info.get_current_state()
|
||||
chat_limit = current_mai_state.get_normal_chat_max_num()
|
||||
# 使用不上锁的版本,因为我们已经在锁内
|
||||
current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT)
|
||||
|
||||
if current_chat_count < chat_limit:
|
||||
target_state = ChatState.CHAT
|
||||
log_reason = f"随机选择 CHAT (当前 {current_chat_count}/{chat_limit})"
|
||||
logger.debug(
|
||||
f"[状态转换请求] {stream_name} ({current_state.value}) 随机决定进入 CHAT,未达上限 ({current_chat_count}/{chat_limit})"
|
||||
)
|
||||
else:
|
||||
# --- 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})"
|
||||
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 的状态转换请求 --- #
|
||||
|
||||
76
src/heart_flow/utils_chat.py
Normal file
76
src/heart_flow/utils_chat.py
Normal 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
|
||||
@@ -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)
|
||||
if not self.chat_stream:
|
||||
logger.error(f"{self.log_prefix} 获取ChatStream失败。")
|
||||
return False
|
||||
|
||||
# 更新日志前缀(以防流名称发生变化)
|
||||
self.log_prefix = f"[{chat_manager.get_stream_name(self.stream_id) or 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"[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}")
|
||||
# --- 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):
|
||||
|
||||
@@ -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", # 保持名称不变,替换内容
|
||||
|
||||
@@ -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
|
||||
self.stream_name = chat_manager.get_stream_name(self.stream_id) or self.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 not self._initialized:
|
||||
await self.initialize() # Ensure initialized before starting tasks
|
||||
|
||||
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(...)
|
||||
# --- 修改结束 ---
|
||||
|
||||
# 启动后台轮询任务 (这部分不变)
|
||||
logger.info(f"[{self.stream_name}] 启动后台兴趣消息轮询任务...")
|
||||
polling_task = asyncio.create_task(self._reply_interested_message()) # 注意变量名区分
|
||||
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}] 聊天轮询任务已在运行中。")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user