better:更清晰的子心流停用逻辑

This commit is contained in:
SengokuCola
2025-04-26 13:25:35 +08:00
parent 510aa7a12d
commit 14157bdab2
9 changed files with 162 additions and 185 deletions

View File

@@ -246,7 +246,7 @@ class InterestMonitorApp:
self.stream_sub_minds[stream_id] = subflow_entry.get("sub_mind", "N/A") self.stream_sub_minds[stream_id] = subflow_entry.get("sub_mind", "N/A")
self.stream_chat_states[stream_id] = subflow_entry.get("sub_chat_state", "N/A") self.stream_chat_states[stream_id] = subflow_entry.get("sub_chat_state", "N/A")
self.stream_threshold_status[stream_id] = subflow_entry.get("is_above_threshold", False) self.stream_threshold_status[stream_id] = subflow_entry.get("is_above_threshold", False)
self.stream_last_active[stream_id] = subflow_entry.get("last_active_time") # 存储原始时间戳 self.stream_last_active[stream_id] = subflow_entry.get("last_changed_state_time") # 存储原始时间戳
self.stream_last_interaction[stream_id] = subflow_entry.get( self.stream_last_interaction[stream_id] = subflow_entry.get(
"last_interaction_time" "last_interaction_time"
) # 存储原始时间戳 ) # 存储原始时间戳

View File

@@ -101,10 +101,12 @@ c HeartFChatting工作方式
- **文件**: `subheartflow_manager.py` - **文件**: `subheartflow_manager.py`
- **职责**: - **职责**:
- 作为 `Heartflow` 的成员变量存在。 - 作为 `Heartflow` 的成员变量存在。
- **在初始化时接收并持有 `Heartflow``MaiStateInfo` 实例。**
- 负责所有 `SubHeartflow` 实例的生命周期管理,包括: - 负责所有 `SubHeartflow` 实例的生命周期管理,包括:
- 创建和获取 (`create_or_get_subheartflow`)。 - 创建和获取 (`get_or_create_subheartflow`)。
- 停止和清理 (`stop_subheartflow`, `cleanup_inactive_subheartflows`)。 - 停止和清理 (`sleep_subheartflow`, `cleanup_inactive_subheartflows`)。
- 根据 `Heartflow` 的状态和限制条件,激活、停用或调整子心流的状态 - 根据 `Heartflow` 的状态 (`self.mai_state_info`) 和限制条件,激活、停用或调整子心流的状态(例如 `enforce_subheartflow_limits`, `activate_random_subflows_to_chat`, `evaluate_interest_and_promote`
- **注意**: 不再提供直接获取所有 ID (`get_all_subheartflows_ids`) 或单个子心流 (`get_subheartflow`) 的公共方法。
### 1.5. 消息处理与回复流程 (Message Processing vs. Replying Flow) ### 1.5. 消息处理与回复流程 (Message Processing vs. Replying Flow)
- **关注点分离**: 系统严格区分了接收和处理传入消息的流程与决定和生成回复的流程。 - **关注点分离**: 系统严格区分了接收和处理传入消息的流程与决定和生成回复的流程。
@@ -125,9 +127,10 @@ c HeartFChatting工作方式
### 2.1. Heart Flow 整体控制 ### 2.1. Heart Flow 整体控制
- **控制者**: 主心流 (`Heartflow`) - **控制者**: 主心流 (`Heartflow`)
- **核心职责**: - **核心职责**:
- 通过其成员 `SubHeartflowManager` 创建和管理子心流。 - 通过其成员 `SubHeartflowManager` 创建和管理子心流**在创建 `SubHeartflowManager` 时会传入自身的 `MaiStateInfo`**
- 通过其成员 `self.current_state: MaiStateInfo` 控制整体行为模式。 - 通过其成员 `self.current_state: MaiStateInfo` 控制整体行为模式。
- 管理系统级后台任务。 - 管理系统级后台任务。
- **注意**: 不再提供直接获取所有子心流 ID (`get_all_subheartflows_streams_ids`) 的公共方法。
### 2.2. Heart Flow 状态 (`MaiStateInfo`) ### 2.2. Heart Flow 状态 (`MaiStateInfo`)
- **定义与管理**: `Heartflow` 持有 `MaiStateInfo` 的实例 (`self.current_state`) 来管理其状态。状态的枚举定义在 `my_state_manager.py` 中的 `MaiState` - **定义与管理**: `Heartflow` 持有 `MaiStateInfo` 的实例 (`self.current_state`) 来管理其状态。状态的枚举定义在 `my_state_manager.py` 中的 `MaiState`
@@ -146,10 +149,10 @@ c HeartFChatting工作方式
* `ChatState.FOCUSED` (专注/认真水群): 专注聊天模式。激活 `HeartFlowChatInstance` * `ChatState.FOCUSED` (专注/认真水群): 专注聊天模式。激活 `HeartFlowChatInstance`
- **选择**: 子心流可以根据外部指令(来自 `SubHeartflowManager`)或内部逻辑(未来的扩展)选择进入 `ABSENT` 状态(不回复不观察),或进入 `CHAT` / `FOCUSED` 中的一种回复模式。 - **选择**: 子心流可以根据外部指令(来自 `SubHeartflowManager`)或内部逻辑(未来的扩展)选择进入 `ABSENT` 状态(不回复不观察),或进入 `CHAT` / `FOCUSED` 中的一种回复模式。
- **状态转换机制** (由 `SubHeartflowManager` 驱动): - **状态转换机制** (由 `SubHeartflowManager` 驱动):
- **激活 `CHAT`**: 当 `Heartflow` 状态从 `OFFLINE` 变为允许聊天的状态时,`SubHeartflowManager` 会根据限制,选择部分 `ABSENT` 状态的子心流,**检查当前 CHAT 状态数量是否达到上限**,如果未达上限,则调用其 `set_chat_state` 方法将其转换为 `CHAT` - **激活 `CHAT`**: 当 `Heartflow` 状态从 `OFFLINE` 变为允许聊天的状态时,`SubHeartflowManager` 会根据限制(通过 `self.mai_state_info` 获取),选择部分 `ABSENT` 状态的子心流,**检查当前 CHAT 状态数量是否达到上限**,如果未达上限,则调用其 `change_chat_state` 方法将其转换为 `CHAT`
- **激活 `FOCUSED`**: `SubHeartflowManager` 会定期评估处于 `CHAT` 状态的子心流的兴趣度 (`InterestChatting.start_hfc_probability`),若满足条件且**检查当前 FOCUSED 状态数量未达上限**,则调用 `set_chat_state` 将其提升为 `FOCUSED` - **激活 `FOCUSED`**: `SubHeartflowManager` 会定期评估处于 `CHAT` 状态的子心流的兴趣度 (`InterestChatting.start_hfc_probability`),若满足条件且**检查当前 FOCUSED 状态数量未达上限**(通过 `self.mai_state_info` 获取限制),则调用 `change_chat_state` 将其提升为 `FOCUSED`
- **停用/回退**: `SubHeartflowManager` 可能因 `Heartflow` 状态变化、达到数量限制、长时间不活跃或随机概率等原因,调用 `set_chat_state` 将子心流状态设置为 `ABSENT` 或从 `FOCUSED` 回退到 `CHAT` - **停用/回退**: `SubHeartflowManager` 可能因 `Heartflow` 状态变化、达到数量限制、长时间不活跃或随机概率等原因,调用 `change_chat_state` 将子心流状态设置为 `ABSENT` 或从 `FOCUSED` 回退到 `CHAT`
- **注意**: `set_chat_state` 方法本身只负责执行状态转换和管理内部聊天实例(`NormalChatInstance`/`HeartFlowChatInstance`),不再进行限额检查。限额检查的责任完全由调用方(即 `SubHeartflowManager` 中的相关方法)承担。 - **注意**: `change_chat_state` 方法本身只负责执行状态转换和管理内部聊天实例(`NormalChatInstance`/`HeartFlowChatInstance`),不再进行限额检查。限额检查的责任完全由调用方(即 `SubHeartflowManager` 中的相关方法,这些方法会使用内部存储的 `mai_state_info` 来获取限制)承担。
## 3. 聊天实例详解 (Chat Instances Explained) ## 3. 聊天实例详解 (Chat Instances Explained)
@@ -185,23 +188,24 @@ c HeartFChatting工作方式
1. **启动**: `Heartflow` 启动,初始化 `MaiStateInfo` (例如 `OFFLINE`) 和 `SubHeartflowManager` 1. **启动**: `Heartflow` 启动,初始化 `MaiStateInfo` (例如 `OFFLINE`) 和 `SubHeartflowManager`
2. **状态变化**: 用户操作或内部逻辑使 `Heartflow``current_state` 变为 `NORMAL_CHAT` 2. **状态变化**: 用户操作或内部逻辑使 `Heartflow``current_state` 变为 `NORMAL_CHAT`
3. **管理器响应**: `SubHeartflowManager` 检测到状态变化,根据 `NORMAL_CHAT` 的限制,调用 `create_or_get_subheartflow` 获取或创建子心流,并通过 `set_chat_state` 将部分子心流状态从 `ABSENT` 激活为 `CHAT` 3. **管理器响应**: `SubHeartflowManager` 检测到状态变化,根据 `NORMAL_CHAT` 的限制,调用 `get_or_create_subheartflow` 获取或创建子心流,并通过 `change_chat_state` 将部分子心流状态从 `ABSENT` 激活为 `CHAT`
4. **子心流激活**: 被激活的 `SubHeartflow` 启动其 `NormalChatInstance` 4. **子心流激活**: 被激活的 `SubHeartflow` 启动其 `NormalChatInstance`
5. **信息接收**: 该 `SubHeartflow``ChattingObservation` 开始从数据库拉取新消息。 5. **信息接收**: 该 `SubHeartflow``ChattingObservation` 开始从数据库拉取新消息。
6. **普通回复**: `NormalChatInstance` 处理观察到的信息,执行普通回复逻辑。 6. **普通回复**: `NormalChatInstance` 处理观察到的信息,执行普通回复逻辑。
7. **兴趣评估**: `SubHeartflowManager` 定期评估该子心流的 `InterestChatting` 状态。 7. **兴趣评估**: `SubHeartflowManager` 定期评估该子心流的 `InterestChatting` 状态。
8. **提升状态**: 若兴趣度达标且 `Heartflow` 状态允许,`SubHeartflowManager` 调用该子心流的 `set_chat_state` 将其状态提升为 `FOCUSED` 8. **提升状态**: 若兴趣度达标且 `Heartflow` 状态允许,`SubHeartflowManager` 调用该子心流的 `change_chat_state` 将其状态提升为 `FOCUSED`
9. **子心流切换**: `SubHeartflow` 内部停止 `NormalChatInstance`,启动 `HeartFlowChatInstance` 9. **子心流切换**: `SubHeartflow` 内部停止 `NormalChatInstance`,启动 `HeartFlowChatInstance`
10. **专注回复**: `HeartFlowChatInstance` 开始根据其逻辑进行更深入的交互。 10. **专注回复**: `HeartFlowChatInstance` 开始根据其逻辑进行更深入的交互。
11. **状态回落/停用**: 若 `Heartflow` 状态变为 `OFFLINE``SubHeartflowManager` 会调用所有子心流的 `set_chat_state(ChatState.ABSENT)`,使其停止活动。 11. **状态回落/停用**: 若 `Heartflow` 状态变为 `OFFLINE``SubHeartflowManager` 会调用所有子心流的 `change_chat_state(ChatState.ABSENT)`,使其停止活动。
## 5. 使用与配置 (Usage and Configuration) ## 5. 使用与配置 (Usage and Configuration)
### 5.1. 使用说明 (Code Examples) ### 5.1. 使用说明 (Code Examples)
- **(内部)创建/获取子心流** (由 `SubHeartflowManager` 调用): - **(内部)创建/获取子心流** (由 `SubHeartflowManager` 调用, 示例):
```python ```python
# subheartflow_manager.py # subheartflow_manager.py (get_or_create_subheartflow 内部)
new_subflow = SubHeartflow(subheartflow_id, mai_states) # 注意mai_states 现在是 self.mai_state_info
new_subflow = SubHeartflow(subheartflow_id, self.mai_state_info)
await new_subflow.initialize() await new_subflow.initialize()
observation = ChattingObservation(chat_id=subheartflow_id) observation = ChattingObservation(chat_id=subheartflow_id)
new_subflow.add_observation(observation) new_subflow.add_observation(observation)

View File

@@ -49,7 +49,6 @@ class BackgroundTaskManager:
self.update_interval = update_interval self.update_interval = update_interval
self.cleanup_interval = cleanup_interval self.cleanup_interval = cleanup_interval
self.log_interval = log_interval self.log_interval = log_interval
self.inactive_threshold = inactive_threshold # For cleanup task
self.interest_eval_interval = interest_eval_interval # 存储兴趣评估间隔 self.interest_eval_interval = interest_eval_interval # 存储兴趣评估间隔
self.random_deactivation_interval = random_deactivation_interval # 存储随机停用间隔 self.random_deactivation_interval = random_deactivation_interval # 存储随机停用间隔
@@ -217,21 +216,35 @@ class BackgroundTaskManager:
current_state == self.mai_state_info.mai_status.OFFLINE current_state == self.mai_state_info.mai_status.OFFLINE
and previous_status != self.mai_state_info.mai_status.OFFLINE and previous_status != self.mai_state_info.mai_status.OFFLINE
): ):
logger.info("[后台任务] 主状态离线,触发子流停用") logger.info("检测到离线,停用所有子心流")
await self.subheartflow_manager.deactivate_all_subflows() await self.subheartflow_manager.deactivate_all_subflows()
async def _perform_cleanup_work(self): async def _perform_cleanup_work(self):
"""执行一轮子心流清理操作。""" """执行子心流清理任务
flows_to_stop = self.subheartflow_manager.cleanup_inactive_subheartflows(self.inactive_threshold) 1. 获取需要清理的不活跃子心流列表
if flows_to_stop: 2. 逐个停止这些子心流
logger.info(f"[Background Task Cleanup] Attempting to stop {len(flows_to_stop)} inactive flows...") 3. 记录清理结果
"""
# 获取需要清理的子心流列表(包含ID和原因)
flows_to_stop = self.subheartflow_manager.get_inactive_subheartflows()
if not flows_to_stop:
return # 没有需要清理的子心流直接返回
logger.info(f"准备删除 {len(flows_to_stop)} 个不活跃(1h)子心流")
stopped_count = 0 stopped_count = 0
for flow_id, reason in flows_to_stop:
if await self.subheartflow_manager.stop_subheartflow(flow_id, f"定期清理: {reason}"): # 逐个停止子心流
for flow_id in flows_to_stop:
success = await self.subheartflow_manager.delete_subflow(flow_id)
if success:
stopped_count += 1 stopped_count += 1
logger.info(f"[Background Task Cleanup] Cleanup cycle finished. Stopped {stopped_count} inactive flows.") logger.debug(f"[清理任务] 已停止子心流 {flow_id}")
# else:
# logger.debug("[Background Task Cleanup] Cleanup cycle finished. No inactive flows found.") # 记录最终清理结果
logger.info(f"[清理任务] 清理完成, 共停止 {stopped_count}/{len(flows_to_stop)} 个子心流")
async def _perform_logging_work(self): async def _perform_logging_work(self):
"""执行一轮状态日志记录。""" """执行一轮状态日志记录。"""

View File

@@ -47,8 +47,8 @@ class Heartflow:
self.current_state: MaiStateInfo = MaiStateInfo() # 当前状态信息 self.current_state: MaiStateInfo = MaiStateInfo() # 当前状态信息
self.mai_state_manager: MaiStateManager = MaiStateManager() # 状态决策管理器 self.mai_state_manager: MaiStateManager = MaiStateManager() # 状态决策管理器
# 子心流管理 # 子心流管理 (在初始化时传入 current_state)
self.subheartflow_manager: SubHeartflowManager = SubHeartflowManager() # 子心流管理器 self.subheartflow_manager: SubHeartflowManager = SubHeartflowManager(self.current_state)
# LLM模型配置 # LLM模型配置
self.llm_model = LLMRequest( self.llm_model = LLMRequest(
@@ -75,23 +75,18 @@ class Heartflow:
inactive_threshold=INACTIVE_THRESHOLD_SECONDS, inactive_threshold=INACTIVE_THRESHOLD_SECONDS,
) )
async def create_subheartflow(self, subheartflow_id: Any) -> Optional["SubHeartflow"]: async def get_or_create_subheartflow(self, subheartflow_id: Any) -> Optional["SubHeartflow"]:
"""获取或创建一个新的SubHeartflow实例 - 委托给 SubHeartflowManager""" """获取或创建一个新的SubHeartflow实例 - 委托给 SubHeartflowManager"""
return await self.subheartflow_manager.create_or_get_subheartflow(subheartflow_id, self.current_state) # 不再需要传入 self.current_state
return await self.subheartflow_manager.get_or_create_subheartflow(subheartflow_id)
def get_subheartflow(self, subheartflow_id: Any) -> Optional["SubHeartflow"]:
"""获取指定ID的SubHeartflow实例"""
return self.subheartflow_manager.get_subheartflow(subheartflow_id)
def get_all_subheartflows_streams_ids(self) -> list[Any]:
"""获取当前所有活跃的子心流的 ID 列表 - 委托给 SubHeartflowManager"""
return self.subheartflow_manager.get_all_subheartflows_ids()
async def heartflow_start_working(self): async def heartflow_start_working(self):
"""启动后台任务""" """启动后台任务"""
await self.background_task_manager.start_tasks() await self.background_task_manager.start_tasks()
logger.info("[Heartflow] 后台任务已启动") logger.info("[Heartflow] 后台任务已启动")
# 根本不会用到这个函数吧,那样麦麦直接死了
async def stop_working(self): async def stop_working(self):
"""停止所有任务和子心流""" """停止所有任务和子心流"""
logger.info("[Heartflow] 正在停止任务和子心流...") logger.info("[Heartflow] 正在停止任务和子心流...")

View File

@@ -58,7 +58,7 @@ class InterestLogger:
return results return results
for subheartflow in all_flows: for subheartflow in all_flows:
if self.subheartflow_manager.get_subheartflow(subheartflow.subheartflow_id): if self.subheartflow_manager.get_or_create_subheartflow(subheartflow.subheartflow_id):
tasks.append( tasks.append(
asyncio.create_task(subheartflow.get_full_state(), name=f"get_state_{subheartflow.subheartflow_id}") asyncio.create_task(subheartflow.get_full_state(), name=f"get_state_{subheartflow.subheartflow_id}")
) )

View File

@@ -2,7 +2,7 @@ from .observation import Observation, ChattingObservation
import asyncio import asyncio
from src.config.config import global_config from src.config.config import global_config
import time import time
from typing import Optional, List, Dict, Callable from typing import Optional, List, Dict, Callable, Tuple
import traceback import traceback
from src.common.logger import get_module_logger, LogConfig, SUB_HEARTFLOW_STYLE_CONFIG # noqa: E402 from src.common.logger import get_module_logger, LogConfig, SUB_HEARTFLOW_STYLE_CONFIG # noqa: E402
import random import random
@@ -41,7 +41,6 @@ class InterestChatting:
increase_rate=probability_increase_rate_per_second, increase_rate=probability_increase_rate_per_second,
decay_factor=global_config.probability_decay_factor_per_second, decay_factor=global_config.probability_decay_factor_per_second,
max_probability=max_reply_probability, max_probability=max_reply_probability,
state_change_callback: Optional[Callable[[ChatState], None]] = None,
): ):
# 基础属性初始化 # 基础属性初始化
self.interest_level: float = 0.0 self.interest_level: float = 0.0
@@ -70,12 +69,22 @@ class InterestChatting:
self.above_threshold = False self.above_threshold = False
self.start_hfc_probability = 0.0 self.start_hfc_probability = 0.0
@classmethod async def initialize(self):
async def create(cls, *args, **kwargs): async with self._task_lock:
"""异步工厂方法,用于创建并初始化 InterestChatting 实例""" if self._is_running:
instance = cls(*args, **kwargs) logger.debug("后台兴趣更新任务已在运行中。")
await instance.start_updates(instance.update_interval) return
return instance
# 清理已完成或已取消的任务
if self.update_task and (self.update_task.done() or self.update_task.cancelled()):
self.update_task = None
if not self.update_task:
self._stop_event.clear()
self._is_running = True
self.update_task = asyncio.create_task(self._run_update_loop(self.update_interval))
logger.debug("后台兴趣更新任务已创建并启动。")
def add_interest_dict(self, message: MessageRecv, interest_value: float, is_mentioned: bool): def add_interest_dict(self, message: MessageRecv, interest_value: float, is_mentioned: bool):
self.interest_dict[message.message_info.message_id] = (message, interest_value, is_mentioned) self.interest_dict[message.message_info.message_id] = (message, interest_value, is_mentioned)
@@ -123,11 +132,11 @@ class InterestChatting:
if self.start_hfc_probability != 0: if self.start_hfc_probability != 0:
self.start_hfc_probability -= 0.1 self.start_hfc_probability -= 0.1
async def increase_interest(self, current_time: float, value: float): async def increase_interest(self, value: float):
self.interest_level += value self.interest_level += value
self.interest_level = min(self.interest_level, self.max_interest) self.interest_level = min(self.interest_level, self.max_interest)
async def decrease_interest(self, current_time: float, value: float): async def decrease_interest(self, value: float):
self.interest_level -= value self.interest_level -= value
self.interest_level = max(self.interest_level, 0.0) self.interest_level = max(self.interest_level, 0.0)
@@ -176,22 +185,6 @@ class InterestChatting:
self._is_running = False self._is_running = False
logger.info("InterestChatting 更新循环已停止。") logger.info("InterestChatting 更新循环已停止。")
async def start_updates(self, update_interval: float = 1.0):
"""启动后台更新任务,使用锁确保并发安全"""
async with self._task_lock:
if self._is_running:
logger.debug("后台兴趣更新任务已在运行中。")
return
# 清理已完成或已取消的任务
if self.update_task and (self.update_task.done() or self.update_task.cancelled()):
self.update_task = None
if not self.update_task:
self._stop_event.clear()
self._is_running = True
self.update_task = asyncio.create_task(self._run_update_loop(update_interval))
logger.debug("后台兴趣更新任务已创建并启动。")
async def stop_updates(self): async def stop_updates(self):
"""停止后台更新任务,使用锁确保并发安全""" """停止后台更新任务,使用锁确保并发安全"""
@@ -241,12 +234,14 @@ class SubHeartflow:
# 这个聊天流的状态 # 这个聊天流的状态
self.chat_state: ChatStateInfo = ChatStateInfo() self.chat_state: ChatStateInfo = ChatStateInfo()
self.chat_state_changed_time: float = time.time()
self.chat_state_last_time: float = 0
self.history_chat_state: List[Tuple[ChatState, float]] = []
# 兴趣检测器 # 兴趣检测器
self.interest_chatting = None self.interest_chatting: InterestChatting = InterestChatting()
# 活动状态管理 # 活动状态管理
self.last_active_time = time.time() # 最后活跃时间
self.should_stop = False # 停止标志 self.should_stop = False # 停止标志
self.task: Optional[asyncio.Task] = None # 后台任务 self.task: Optional[asyncio.Task] = None # 后台任务
@@ -269,23 +264,12 @@ class SubHeartflow:
self.log_prefix = chat_manager.get_stream_name(self.subheartflow_id) or self.subheartflow_id self.log_prefix = chat_manager.get_stream_name(self.subheartflow_id) or self.subheartflow_id
async def initialize(self): async def initialize(self):
"""异步初始化方法,创建兴趣检测器""" """异步初始化方法,创建兴趣"""
self.interest_chatting = await InterestChatting.create(state_change_callback=self.set_chat_state) await self.interest_chatting.initialize()
logger.debug(f"{self.log_prefix} InterestChatting 实例已创建并初始化。") logger.debug(f"{self.log_prefix} InterestChatting 实例已初始化。")
async def add_time_current_state(self, add_time: float): def update_last_chat_state_time(self):
"""增加当前状态的时间""" self.chat_state_last_time = time.time() - self.chat_state_changed_time
self.current_state_time += add_time
async def change_to_state_chat(self):
"""改变到随便水群状态"""
self.current_state_time = 120
self._start_normal_chat()
async def change_to_state_focused(self):
"""改变到认真水群状态"""
self.current_state_time = 60
self._start_heart_fc_chat()
async def _stop_normal_chat(self): async def _stop_normal_chat(self):
""" """
@@ -381,11 +365,11 @@ class SubHeartflow:
self.heart_fc_instance = None # 创建或初始化异常,清理实例 self.heart_fc_instance = None # 创建或初始化异常,清理实例
return False return False
async def set_chat_state(self, new_state: "ChatState"): async def change_chat_state(self, new_state: "ChatState"):
"""更新sub_heartflow的聊天状态并管理 HeartFChatting 和 NormalChat 实例及任务""" """更新sub_heartflow的聊天状态并管理 HeartFChatting 和 NormalChat 实例及任务"""
current_state = self.chat_state.chat_status current_state = self.chat_state.chat_status
if current_state == new_state: if current_state == new_state:
# logger.trace(f"{self.log_prefix} 状态已为 {current_state.value}, 无需更改。") # 减少日志噪音
return return
log_prefix = self.log_prefix log_prefix = self.log_prefix
@@ -422,9 +406,14 @@ class SubHeartflow:
# --- 更新状态和最后活动时间 --- # --- 更新状态和最后活动时间 ---
if state_changed: if state_changed:
logger.info(f"{log_prefix} 麦麦的聊天状态从 {current_state.value} 变更为 {new_state.value}") self.update_last_chat_state_time()
self.history_chat_state.append((current_state, self.chat_state_last_time))
logger.info(f"{log_prefix} 麦麦的聊天状态从 {current_state.value} (持续了 {self.chat_state_last_time} 秒) 变更为 {new_state.value}")
self.chat_state.chat_status = new_state self.chat_state.chat_status = new_state
self.last_active_time = time.time() self.chat_state_last_time = 0
self.chat_state_changed_time = time.time()
else: else:
# 如果因为某些原因(如启动失败)没有成功改变状态,记录一下 # 如果因为某些原因(如启动失败)没有成功改变状态,记录一下
logger.debug( logger.debug(
@@ -479,9 +468,6 @@ class SubHeartflow:
async def should_evaluate_reply(self) -> bool: async def should_evaluate_reply(self) -> bool:
return await self.interest_chatting.should_evaluate_reply() return await self.interest_chatting.should_evaluate_reply()
async def add_interest_dict_entry(self, message: MessageRecv, interest_value: float, is_mentioned: bool):
self.interest_chatting.add_interest_dict(message, interest_value, is_mentioned)
def get_interest_dict(self) -> Dict[str, tuple[MessageRecv, float, bool]]: def get_interest_dict(self) -> Dict[str, tuple[MessageRecv, float, bool]]:
return self.interest_chatting.interest_dict return self.interest_chatting.interest_dict
@@ -495,7 +481,7 @@ class SubHeartflow:
"interest_state": interest_state, "interest_state": interest_state,
"current_mind": self.sub_mind.current_mind, "current_mind": self.sub_mind.current_mind,
"chat_state": self.chat_state.chat_status.value, "chat_state": self.chat_state.chat_status.value,
"last_active_time": self.last_active_time, "last_changed_state_time": self.last_changed_state_time,
} }
async def shutdown(self): async def shutdown(self):

View File

@@ -23,41 +23,29 @@ subheartflow_manager_log_config = LogConfig(
logger = get_module_logger("subheartflow_manager", config=subheartflow_manager_log_config) logger = get_module_logger("subheartflow_manager", config=subheartflow_manager_log_config)
# 子心流管理相关常量 # 子心流管理相关常量
INACTIVE_THRESHOLD_SECONDS = 1200 # 子心流不活跃超时时间(秒) INACTIVE_THRESHOLD_SECONDS = 3600 # 子心流不活跃超时时间(秒)
class SubHeartflowManager: class SubHeartflowManager:
"""管理所有活跃的 SubHeartflow 实例。""" """管理所有活跃的 SubHeartflow 实例。"""
def __init__(self): def __init__(self, mai_state_info: MaiStateInfo):
self.subheartflows: Dict[Any, "SubHeartflow"] = {} self.subheartflows: Dict[Any, "SubHeartflow"] = {}
self._lock = asyncio.Lock() # 用于保护 self.subheartflows 的访问 self._lock = asyncio.Lock() # 用于保护 self.subheartflows 的访问
self.mai_state_info: MaiStateInfo = mai_state_info # 存储传入的 MaiStateInfo 实例
def get_all_subheartflows(self) -> List["SubHeartflow"]: def get_all_subheartflows(self) -> List["SubHeartflow"]:
"""获取所有当前管理的 SubHeartflow 实例列表 (快照)。""" """获取所有当前管理的 SubHeartflow 实例列表 (快照)。"""
return list(self.subheartflows.values()) return list(self.subheartflows.values())
def get_all_subheartflows_ids(self) -> List[Any]: async def get_or_create_subheartflow(
"""获取所有当前管理的 SubHeartflow ID 列表。""" self, subheartflow_id: Any
return list(self.subheartflows.keys())
def get_subheartflow(self, subheartflow_id: Any) -> Optional["SubHeartflow"]:
"""获取指定 ID 的 SubHeartflow 实例。"""
# 注意:这里没有加锁,假设读取操作相对安全或在已知上下文中调用
# 如果并发写操作很多get 也应该加锁
subflow = self.subheartflows.get(subheartflow_id)
if subflow:
subflow.last_active_time = time.time() # 获取时更新活动时间
return subflow
async def create_or_get_subheartflow(
self, subheartflow_id: Any, mai_states: MaiStateInfo
) -> Optional["SubHeartflow"]: ) -> Optional["SubHeartflow"]:
"""获取或创建指定ID的子心流实例 """获取或创建指定ID的子心流实例
Args: Args:
subheartflow_id: 子心流唯一标识符 subheartflow_id: 子心流唯一标识符
mai_states: 当前麦麦状态信息 # mai_states 参数已被移除,使用 self.mai_state_info
Returns: Returns:
成功返回SubHeartflow实例失败返回None 成功返回SubHeartflow实例失败返回None
@@ -75,8 +63,8 @@ class SubHeartflowManager:
return subflow return subflow
try: try:
# 初始化子心流 # 初始化子心流, 传入存储的 mai_state_info
new_subflow = SubHeartflow(subheartflow_id, mai_states) new_subflow = SubHeartflow(subheartflow_id, self.mai_state_info)
# 异步初始化 # 异步初始化
await new_subflow.initialize() await new_subflow.initialize()
@@ -98,7 +86,7 @@ class SubHeartflowManager:
logger.error(f"创建子心流 {subheartflow_id} 失败: {e}", exc_info=True) logger.error(f"创建子心流 {subheartflow_id} 失败: {e}", exc_info=True)
return None return None
async def stop_subheartflow(self, subheartflow_id: Any, reason: str) -> bool: async def sleep_subheartflow(self, subheartflow_id: Any, reason: str) -> bool:
"""停止指定的子心流并清理资源""" """停止指定的子心流并清理资源"""
subheartflow = self.subheartflows.get(subheartflow_id) subheartflow = self.subheartflows.get(subheartflow_id)
if not subheartflow: if not subheartflow:
@@ -111,7 +99,7 @@ class SubHeartflowManager:
# 设置状态为ABSENT释放资源 # 设置状态为ABSENT释放资源
if subheartflow.chat_state.chat_status != ChatState.ABSENT: if subheartflow.chat_state.chat_status != ChatState.ABSENT:
logger.debug(f"[子心流管理] 设置 {stream_name} 状态为ABSENT") logger.debug(f"[子心流管理] 设置 {stream_name} 状态为ABSENT")
await subheartflow.set_chat_state(ChatState.ABSENT) await subheartflow.change_chat_state(ChatState.ABSENT)
else: else:
logger.debug(f"[子心流管理] {stream_name} 已是ABSENT状态") logger.debug(f"[子心流管理] {stream_name} 已是ABSENT状态")
except Exception as e: except Exception as e:
@@ -135,27 +123,26 @@ class SubHeartflowManager:
logger.warning(f"[子心流管理] {stream_name} 已被提前移除") logger.warning(f"[子心流管理] {stream_name} 已被提前移除")
return False return False
def cleanup_inactive_subheartflows(self, max_age_seconds=INACTIVE_THRESHOLD_SECONDS): def get_inactive_subheartflows(self, max_age_seconds=INACTIVE_THRESHOLD_SECONDS):
"""识别并返回需要清理的不活跃子心流(id, 原因)""" """识别并返回需要清理的不活跃(处于ABSENT状态超过一小时)子心流(id, 原因)"""
current_time = time.time() current_time = time.time()
flows_to_stop = [] flows_to_stop = []
for subheartflow_id, subheartflow in list(self.subheartflows.items()): for subheartflow_id, subheartflow in list(self.subheartflows.items()):
# 只检查有interest_chatting的子心流 state = subheartflow.chat_state.chat_status
if hasattr(subheartflow, "interest_chatting") and subheartflow.interest_chatting: if state != ChatState.ABSENT:
last_interact = subheartflow.interest_chatting.last_interaction_time continue
if max_age_seconds and (current_time - last_interact) > max_age_seconds: subheartflow.update_last_chat_state_time()
reason = f"不活跃时间({current_time - last_interact:.0f}s) > 阈值({max_age_seconds}s)" absent_last_time = subheartflow.chat_state_last_time
name = chat_manager.get_stream_name(subheartflow_id) or subheartflow_id if max_age_seconds and (current_time - absent_last_time) > max_age_seconds:
logger.debug(f"[清理] 标记 {name} 待移除: {reason}") flows_to_stop.append(subheartflow_id)
flows_to_stop.append((subheartflow_id, reason))
if flows_to_stop:
logger.info(f"[清理] 发现 {len(flows_to_stop)} 个不活跃子心流")
return flows_to_stop return flows_to_stop
async def enforce_subheartflow_limits(self, current_mai_state: MaiState): async def enforce_subheartflow_limits(self):
"""根据主状态限制停止超额子心流(优先停不活跃的)""" """根据主状态限制停止超额子心流(优先停不活跃的)"""
# 使用 self.mai_state_info 获取当前状态和限制
current_mai_state = self.mai_state_info.get_current_state()
normal_limit = current_mai_state.get_normal_chat_max_num() normal_limit = current_mai_state.get_normal_chat_max_num()
focused_limit = current_mai_state.get_focused_chat_max_num() focused_limit = current_mai_state.get_focused_chat_max_num()
logger.debug(f"[限制] 状态:{current_mai_state.value}, 普通限:{normal_limit}, 专注限:{focused_limit}") logger.debug(f"[限制] 状态:{current_mai_state.value}, 普通限:{normal_limit}, 专注限:{focused_limit}")
@@ -178,7 +165,7 @@ class SubHeartflowManager:
logger.info(f"[限制] 普通聊天超额({len(normal_flows)}>{normal_limit}), 停止{excess}") logger.info(f"[限制] 普通聊天超额({len(normal_flows)}>{normal_limit}), 停止{excess}")
normal_flows.sort(key=lambda x: x[1]) normal_flows.sort(key=lambda x: x[1])
for flow_id, _ in normal_flows[:excess]: for flow_id, _ in normal_flows[:excess]:
if await self.stop_subheartflow(flow_id, f"普通聊天超额(限{normal_limit})"): if await self.sleep_subheartflow(flow_id, f"普通聊天超额(限{normal_limit})"):
stopped += 1 stopped += 1
# 处理专注聊天超额(需重新统计) # 处理专注聊天超额(需重新统计)
@@ -192,7 +179,7 @@ class SubHeartflowManager:
logger.info(f"[限制] 专注聊天超额({len(focused_flows)}>{focused_limit}), 停止{excess}") logger.info(f"[限制] 专注聊天超额({len(focused_flows)}>{focused_limit}), 停止{excess}")
focused_flows.sort(key=lambda x: x[1]) focused_flows.sort(key=lambda x: x[1])
for flow_id, _ in focused_flows[:excess]: for flow_id, _ in focused_flows[:excess]:
if await self.stop_subheartflow(flow_id, f"专注聊天超额(限{focused_limit})"): if await self.sleep_subheartflow(flow_id, f"专注聊天超额(限{focused_limit})"):
stopped += 1 stopped += 1
if stopped: if stopped:
@@ -200,8 +187,10 @@ class SubHeartflowManager:
else: else:
logger.debug(f"[限制] 无需停止, 当前总数:{len(self.subheartflows)}") logger.debug(f"[限制] 无需停止, 当前总数:{len(self.subheartflows)}")
async def activate_random_subflows_to_chat(self, current_mai_state: MaiState): async def activate_random_subflows_to_chat(self):
"""主状态激活时随机选择ABSENT子心流进入CHAT状态""" """主状态激活时随机选择ABSENT子心流进入CHAT状态"""
# 使用 self.mai_state_info 获取当前状态和限制
current_mai_state = self.mai_state_info.get_current_state()
limit = current_mai_state.get_normal_chat_max_num() limit = current_mai_state.get_normal_chat_max_num()
if limit <= 0: if limit <= 0:
logger.info("[激活] 当前状态不允许CHAT子心流") logger.info("[激活] 当前状态不允许CHAT子心流")
@@ -236,7 +225,7 @@ class SubHeartflowManager:
# --- 结束限额检查 --- # # --- 结束限额检查 --- #
# 移除 states_num 参数 # 移除 states_num 参数
await flow.set_chat_state(ChatState.CHAT) await flow.change_chat_state(ChatState.CHAT)
if flow.chat_state.chat_status == ChatState.CHAT: if flow.chat_state.chat_status == ChatState.CHAT:
activated_count += 1 activated_count += 1
@@ -246,25 +235,43 @@ class SubHeartflowManager:
logger.info(f"[激活] 完成, 成功激活{activated_count}个子心流") logger.info(f"[激活] 完成, 成功激活{activated_count}个子心流")
async def deactivate_all_subflows(self): async def deactivate_all_subflows(self):
"""停用所有子心流(主状态变为OFFLINE时调用)""" """所有子心流的状态更改为 ABSENT (例如主状态变为OFFLINE时调用)"""
logger.info("[停用] 开始停用所有子心流") # logger.info("[停用] 开始所有子心流状态设置为 ABSENT")
flow_ids = list(self.subheartflows.keys()) # 使用 list() 创建一个当前值的快照,防止在迭代时修改字典
flows_to_update = list(self.subheartflows.values())
if not flow_ids: if not flows_to_update:
logger.info("[停用] 无活跃子心流") logger.debug("[停用] 无活跃子心流,无需操作")
return return
stopped_count = 0 changed_count = 0
for flow_id in flow_ids: for subflow in flows_to_update:
if await self.stop_subheartflow(flow_id, "主状态离线"): flow_id = subflow.subheartflow_id
stopped_count += 1 stream_name = chat_manager.get_stream_name(flow_id) or flow_id
# 再次检查子心流是否仍然存在于管理器中,以防万一在迭代过程中被移除
logger.info(f"[停用] 完成, 尝试停止{len(flow_ids)}个, 成功{stopped_count}") if subflow.chat_state.chat_status != ChatState.ABSENT:
logger.debug(f"正在将子心流 {stream_name} 的状态从 {subflow.chat_state.chat_status.value} 更改为 ABSENT")
try:
# 调用 change_chat_state 将状态设置为 ABSENT
await subflow.change_chat_state(ChatState.ABSENT)
# 验证状态是否真的改变了
if flow_id in self.subheartflows and self.subheartflows[flow_id].chat_state.chat_status == ChatState.ABSENT:
changed_count += 1
else:
logger.warning(f"[停用] 尝试更改子心流 {stream_name} 状态后,状态仍未变为 ABSENT 或子心流已消失。")
except Exception as e:
logger.error(f"[停用] 更改子心流 {stream_name} 状态为 ABSENT 时出错: {e}", exc_info=True)
else:
logger.debug(f"[停用] 子心流 {stream_name} 已处于 ABSENT 状态,无需更改。")
async def evaluate_interest_and_promote(self, current_mai_state: MaiStateInfo): logger.info(f"下限完成,共处理 {len(flows_to_update)} 个子心流,成功将 {changed_count} 个子心流的状态更改为 ABSENT。")
async def evaluate_interest_and_promote(self):
"""评估子心流兴趣度满足条件且未达上限则提升到FOCUSED状态基于start_hfc_probability""" """评估子心流兴趣度满足条件且未达上限则提升到FOCUSED状态基于start_hfc_probability"""
log_prefix = "[兴趣评估]" log_prefix = "[兴趣评估]"
current_state = current_mai_state.get_current_state() # 使用 self.mai_state_info 获取当前状态和限制
current_state = self.mai_state_info.get_current_state()
focused_limit = current_state.get_focused_chat_max_num() focused_limit = current_state.get_focused_chat_max_num()
if int(time.time()) % 20 == 0: # 每20秒输出一次 if int(time.time()) % 20 == 0: # 每20秒输出一次
@@ -312,7 +319,7 @@ class SubHeartflowManager:
) )
# 执行状态提升 # 执行状态提升
await current_subflow.set_chat_state(ChatState.FOCUSED) await current_subflow.change_chat_state(ChatState.FOCUSED)
# 验证提升结果 # 验证提升结果
if ( if (
@@ -354,7 +361,7 @@ class SubHeartflowManager:
# --- 状态设置 --- # # --- 状态设置 --- #
# 注意:这里传递的状态数量是 *停用前* 的状态数量 # 注意:这里传递的状态数量是 *停用前* 的状态数量
await current_subflow.set_chat_state(ChatState.ABSENT) await current_subflow.change_chat_state(ChatState.ABSENT)
# --- 状态验证 (可选) --- # --- 状态验证 (可选) ---
final_subflow = self.subheartflows.get(flow_id) final_subflow = self.subheartflows.get(flow_id)
@@ -419,44 +426,18 @@ class SubHeartflowManager:
) )
logger.debug(f"[子心流管理器] 更新了{updated_count}个子心流的主想法") logger.debug(f"[子心流管理器] 更新了{updated_count}个子心流的主想法")
async def deactivate_subflow(self, subheartflow_id: Any): async def delete_subflow(self, subheartflow_id: Any):
"""停用并移除指定的子心流。""" """除指定的子心流。"""
async with self._lock: async with self._lock:
subflow = self.subheartflows.pop(subheartflow_id, None) subflow = self.subheartflows.pop(subheartflow_id, None)
if subflow: if subflow:
logger.info(f"正在停用 SubHeartflow: {subheartflow_id}...") logger.info(f"正在删除 SubHeartflow: {subheartflow_id}...")
try: try:
# --- 调用 shutdown 方法 --- # 调用 shutdown 方法确保资源释放
await subflow.shutdown() await subflow.shutdown()
# --- 结束调用 --- logger.info(f"SubHeartflow {subheartflow_id} 已成功删除。")
logger.info(f"SubHeartflow {subheartflow_id} 已成功停用。")
except Exception as e: except Exception as e:
logger.error(f"停用 SubHeartflow {subheartflow_id} 时出错: {e}", exc_info=True) logger.error(f"删除 SubHeartflow {subheartflow_id} 时出错: {e}", exc_info=True)
else: else:
logger.warning(f"尝试停用不存在的 SubHeartflow: {subheartflow_id}") logger.warning(f"尝试删除不存在的 SubHeartflow: {subheartflow_id}")
async def cleanup_inactive_subflows(self, inactive_threshold_seconds: int):
"""清理长时间不活跃的子心流。"""
current_time = time.time()
inactive_ids = []
# 不加锁地迭代,识别不活跃的 ID
for sub_id, subflow in self.subheartflows.items():
# 检查 last_active_time 是否存在且是数值
last_active = getattr(subflow, "last_active_time", 0)
if isinstance(last_active, (int, float)):
if current_time - last_active > inactive_threshold_seconds:
inactive_ids.append(sub_id)
logger.info(
f"发现不活跃的 SubHeartflow: {sub_id} (上次活跃: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(last_active))})"
)
else:
logger.warning(f"SubHeartflow {sub_id} 的 last_active_time 无效: {last_active}。跳过清理检查。")
if inactive_ids:
logger.info(f"准备清理 {len(inactive_ids)} 个不活跃的 SubHeartflows: {inactive_ids}")
# 逐个停用deactivate_subflow 会加锁)
tasks = [self.deactivate_subflow(sub_id) for sub_id in inactive_ids]
await asyncio.gather(*tasks)
logger.info("不活跃的 SubHeartflows 清理完成。")
# else:
# logger.debug("没有发现不活跃的 SubHeartflows 需要清理。")

View File

@@ -138,7 +138,7 @@ class HeartFCProcessor:
group_info=groupinfo, group_info=groupinfo,
) )
subheartflow = await heartflow.create_subheartflow(chat.stream_id) subheartflow = await heartflow.get_or_create_subheartflow(chat.stream_id)
message.update_chat_stream(chat) message.update_chat_stream(chat)
await message.process() await message.process()
@@ -166,9 +166,8 @@ class HeartFCProcessor:
# 6. 兴趣度计算与更新 # 6. 兴趣度计算与更新
interested_rate, is_mentioned = await self._calculate_interest(message) interested_rate, is_mentioned = await self._calculate_interest(message)
current_time = time.time() await subheartflow.interest_chatting.increase_interest(value=interested_rate)
await subheartflow.interest_chatting.increase_interest(current_time, value=interested_rate) await subheartflow.interest_chatting.add_interest_dict(message, interested_rate, is_mentioned)
await subheartflow.add_interest_dict_entry(message, interested_rate, is_mentioned)
# 7. 日志记录 # 7. 日志记录
mes_name = chat.group_info.group_name if chat.group_info else "私聊" mes_name = chat.group_info.group_name if chat.group_info else "私聊"

View File

@@ -63,7 +63,6 @@ def init_prompt():
- 话题无关/无聊/不感兴趣 - 话题无关/无聊/不感兴趣
- 最后一条消息是你自己发的且无人回应你 - 最后一条消息是你自己发的且无人回应你
- 讨论你不懂的专业话题 - 讨论你不懂的专业话题
- 讨论你不想参与的话题
- 你发送了太多消息 - 你发送了太多消息
2. 文字回复(text_reply)适用: 2. 文字回复(text_reply)适用: