feat:提供方法让HFC结束,当等待过久no_reply,会回到ABSENT模式

This commit is contained in:
SengokuCola
2025-04-26 19:03:36 +08:00
parent 042e969292
commit 3763a0ed9e
6 changed files with 214 additions and 140 deletions

View File

@@ -337,7 +337,7 @@ REMOTE_STYLE_CONFIG = {
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 远程 | {message}",
},
"simple": {
"console_format": "<level>{time:MM-DD HH:mm}</level> | <light-yellow>远程</light-yellow> | {message}",
"console_format": "<level>{time:MM-DD HH:mm}</level> | <fg #00788A>远程| {message}</fg #00788A>",
"file_format": "{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {extra[module]: <15} | 远程 | {message}",
},
}

View File

@@ -61,6 +61,7 @@ c HeartFChatting工作方式
c.5.2.2 通过 HeartFCSender 直接发送匹配查询 (emoji_query) 的表情。
c.5.3 如果决策是 no_reply:
c.5.3.1 进入等待状态,直到检测到新消息或超时。
c.5.3.2 同时,增加内部连续不回复计数器。如果该计数器达到预设阈值(例如 5 次),则调用初始化时由 `SubHeartflowManager` 提供的回调函数。此回调函数会通知 `SubHeartflowManager` 请求将对应的 `SubHeartflow` 状态转换为 `ABSENT`。如果执行了其他动作(如 `text_reply``emoji_reply`),则此计数器会被重置。
c.6 循环结束后,记录周期信息 (CycleInfo)并根据情况进行短暂休眠防止CPU空转。
@@ -152,7 +153,7 @@ c HeartFChatting工作方式
- **状态转换机制** (由 `SubHeartflowManager` 驱动):
- **激活 `CHAT`**: 当 `Heartflow` 状态从 `OFFLINE` 变为允许聊天的状态时,`SubHeartflowManager` 会根据限制(通过 `self.mai_state_info` 获取),选择部分 `ABSENT` 状态的子心流,**检查当前 CHAT 状态数量是否达到上限**,如果未达上限,则调用其 `change_chat_state` 方法将其转换为 `CHAT`。此外,`evaluate_and_transition_subflows_by_llm` 方法也会根据 LLM 的判断,在未达上限时将 `ABSENT` 状态的子心流激活为 `CHAT`
- **激活 `FOCUSED`**: `SubHeartflowManager` 会定期评估处于 `CHAT` 状态的子心流的兴趣度 (`InterestChatting.start_hfc_probability`),若满足条件且**检查当前 FOCUSED 状态数量未达上限**(通过 `self.mai_state_info` 获取限制),则调用 `change_chat_state` 将其提升为 `FOCUSED`
- **停用/回退**: `SubHeartflowManager` 可能因 `Heartflow` 状态变化、达到数量限制、长时间不活跃、随机概率 (`randomly_deactivate_subflows`)LLM 评估 (`evaluate_and_transition_subflows_by_llm` 判断 `CHAT` 状态子心流应休眠) 等原因,调用 `change_chat_state` 将子心流状态设置为 `ABSENT` 或从 `FOCUSED` 回退到 `CHAT`。当子心流进入 `ABSENT` 状态后,如果持续一小时不活跃,才会被后台清理任务删除。
- **停用/回退**: `SubHeartflowManager` 可能因 `Heartflow` 状态变化、达到数量限制、长时间不活跃、随机概率 (`randomly_deactivate_subflows`)LLM 评估 (`evaluate_and_transition_subflows_by_llm` 判断 `CHAT` 状态子心流应休眠) 或收到来自 `HeartFChatting` 的连续不回复回调信号 (`request_absent_transition`) 等原因,调用 `change_chat_state` 将子心流状态设置为 `ABSENT` 或从 `FOCUSED` 回退到 `CHAT`。当子心流进入 `ABSENT` 状态后,如果持续一小时不活跃,才会被后台清理任务删除。
- **注意**: `change_chat_state` 方法本身只负责执行状态转换和管理内部聊天实例(`NormalChatInstance`/`HeartFlowChatInstance`),不再进行限额检查。限额检查的责任完全由调用方(即 `SubHeartflowManager` 中的相关方法,这些方法会使用内部存储的 `mai_state_info` 来获取限制)承担。
## 3. 聊天实例详解 (Chat Instances Explained)

View File

@@ -112,15 +112,6 @@ class BackgroundTaskManager:
f"兴趣评估任务已启动 间隔:{self.interest_eval_interval}s",
"_interest_eval_task",
),
# 新增随机停用任务配置
(
self._random_deactivation_task,
self._run_random_deactivation_cycle,
"hf_random_deactivation",
"debug", # 设为debug避免过多日志
f"随机停用任务已启动 间隔:{self.random_deactivation_interval}s",
"_random_deactivation_task",
),
]
# 统一启动所有任务
@@ -264,10 +255,6 @@ class BackgroundTaskManager:
# --- 结束新增 ---
# --- 新增随机停用工作函数 ---
async def _perform_random_deactivation_work(self):
"""执行一轮子心流随机停用检查。"""
await self.subheartflow_manager.randomly_deactivate_subflows()
# --- 结束新增 ---
@@ -300,15 +287,3 @@ class BackgroundTaskManager:
task_func=self._perform_interest_eval_work,
)
# --- 结束新增 ---
# --- 新增随机停用任务运行器 ---
async def _run_random_deactivation_cycle(self):
"""运行随机停用循环。"""
await self._run_periodic_loop(
task_name="Random Deactivation",
interval=self.random_deactivation_interval,
task_func=self._perform_random_deactivation_work,
)
# --- 结束新增 ---

View File

@@ -2,7 +2,7 @@ from .observation import Observation, ChattingObservation
import asyncio
from src.config.config import global_config
import time
from typing import Optional, List, Dict, Tuple
from typing import Optional, List, Dict, Tuple, TYPE_CHECKING, Callable, Coroutine
import traceback
from src.common.logger import get_module_logger, LogConfig, SUB_HEARTFLOW_STYLE_CONFIG # noqa: E402
import random
@@ -15,6 +15,11 @@ 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
# # --- REMOVE: Conditional import --- #
# if TYPE_CHECKING:
# from src.heart_flow.subheartflow_manager import SubHeartflowManager
# # --- END REMOVE --- #
# 定义常量 (从 interest.py 移动过来)
MAX_INTEREST = 15.0
@@ -234,16 +239,23 @@ class InterestChatting:
class SubHeartflow:
def __init__(self, subheartflow_id, mai_states: MaiStateInfo):
def __init__(
self,
subheartflow_id,
mai_states: MaiStateInfo,
hfc_no_reply_callback: Callable[[], Coroutine[None, None, None]]
):
"""子心流初始化函数
Args:
subheartflow_id: 子心流唯一标识符
parent_heartflow: 父级心流实例
mai_states: 麦麦状态信息实例
hfc_no_reply_callback: HFChatting 连续不回复时触发的回调
"""
# 基础属性,两个值是一样的
self.subheartflow_id = subheartflow_id
self.chat_id = subheartflow_id
self.hfc_no_reply_callback = hfc_no_reply_callback
# 麦麦的状态
self.mai_states = mai_states
@@ -364,11 +376,17 @@ class SubHeartflow:
# 如果实例不存在,则创建并启动
logger.info(f"{log_prefix} 麦麦准备开始专注聊天 (创建新实例)...")
try:
# 创建 HeartFChatting 实例,并传递 从构造函数传入的 回调函数
self.heart_fc_instance = HeartFChatting(
chat_id=self.chat_id, sub_mind=self.sub_mind, observations=self.observations
chat_id=self.subheartflow_id,
sub_mind=self.sub_mind,
observations=self.observations, # 传递所有观察者
on_consecutive_no_reply_callback=self.hfc_no_reply_callback # <-- Use stored callback
)
# 初始化并启动 HeartFChatting
if await self.heart_fc_instance._initialize():
await self.heart_fc_instance.start() # 初始化成功后启动循环
await self.heart_fc_instance.start()
logger.info(f"{log_prefix} 麦麦已成功进入专注聊天模式 (新实例已启动)。")
return True
else:

View File

@@ -3,6 +3,7 @@ import time
import random
from typing import Dict, Any, Optional, List
import json # 导入 json 模块
import functools # <-- 新增导入
# 导入日志模块
from src.common.logger import get_module_logger, LogConfig, SUBHEARTFLOW_MANAGER_STYLE_CONFIG
@@ -77,8 +78,17 @@ class SubHeartflowManager:
return subflow
try:
# 初始化子心流, 传入存储的 mai_state_info
new_subflow = SubHeartflow(subheartflow_id, self.mai_state_info)
# --- 使用 functools.partial 创建 HFC 回调 --- #
# 将 manager 的 _handle_hfc_no_reply 方法与当前的 subheartflow_id 绑定
hfc_callback = functools.partial(self._handle_hfc_no_reply, subheartflow_id)
# --- 结束创建回调 --- #
# 初始化子心流, 传入 mai_state_info 和 partial 创建的回调
new_subflow = SubHeartflow(
subheartflow_id,
self.mai_state_info,
hfc_callback # <-- 传递 partial 创建的回调
)
# 异步初始化
await new_subflow.initialize()
@@ -285,74 +295,11 @@ class SubHeartflowManager:
) and final_subflow.chat_state.chat_status == ChatState.FOCUSED:
current_focused_count += 1
async def randomly_deactivate_subflows(self, deactivation_probability: float = 0.1):
"""以一定概率将 FOCUSED 或 CHAT 状态的子心流回退到 ABSENT 状态。"""
log_prefix_manager = "[子心流管理器-随机停用]"
logger.debug(f"{log_prefix_manager} 开始随机停用检查... (概率: {deactivation_probability:.0%})")
# 使用快照安全遍历
subflows_snapshot = list(self.subheartflows.values())
deactivated_count = 0
try:
for sub_hf in subflows_snapshot:
flow_id = sub_hf.subheartflow_id
stream_name = chat_manager.get_stream_name(flow_id) or flow_id
log_prefix_flow = f"[{stream_name}]"
current_state = sub_hf.chat_state.chat_status
# 只处理 FOCUSED 或 CHAT 状态
if current_state not in [ChatState.FOCUSED, ChatState.CHAT]:
continue
# 检查随机概率
if random.random() < deactivation_probability:
logger.info(
f"{log_prefix_manager} {log_prefix_flow} 随机触发停用 (从 {current_state.value}) -> ABSENT"
)
# 获取当前实例以检查最新状态
current_subflow = self.subheartflows.get(flow_id)
if not current_subflow or current_subflow.chat_state.chat_status != current_state:
logger.warning(f"{log_prefix_manager} {log_prefix_flow} 尝试停用时状态已改变或实例消失,跳过。")
continue
# --- 状态设置 --- #
# 注意:这里传递的状态数量是 *停用前* 的状态数量
await current_subflow.change_chat_state(ChatState.ABSENT)
# --- 状态验证 (可选) ---
final_subflow = self.subheartflows.get(flow_id)
if final_subflow:
final_state = final_subflow.chat_state.chat_status
if final_state == ChatState.ABSENT:
logger.debug(
f"{log_prefix_manager} {log_prefix_flow} 成功从 {current_state.value} 停用到 ABSENT 状态"
)
deactivated_count += 1
else:
logger.warning(
f"{log_prefix_manager} {log_prefix_flow} 尝试停用到 ABSENT 后状态仍为 {final_state.value}"
)
else:
logger.warning(f"{log_prefix_manager} {log_prefix_flow} 停用后验证时子心流 {flow_id} 消失")
except Exception as e:
logger.error(f"{log_prefix_manager} 随机停用周期出错: {e}", exc_info=True)
if deactivated_count > 0:
logger.info(f"{log_prefix_manager} 随机停用周期结束, 成功停用 {deactivated_count} 个子心流。")
else:
logger.debug(f"{log_prefix_manager} 随机停用周期结束, 未停用任何子心流。")
async def evaluate_and_transition_subflows_by_llm(self):
"""
使用LLM评估每个子心流的状态并根据LLM的判断执行状态转换ABSENT <-> CHAT
注意此函数包含对假设的LLM函数的调用。
"""
log_prefix = "[LLM状态评估]"
logger.info(f"{log_prefix} 开始基于LLM评估子心流状态...")
# 获取当前状态和限制用于CHAT激活检查
current_mai_state = self.mai_state_info.get_current_state()
chat_limit = current_mai_state.get_normal_chat_max_num()
@@ -366,33 +313,27 @@ class SubHeartflowManager:
current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT)
if not subflows_snapshot:
logger.info(f"{log_prefix} 当前没有子心流需要评估。")
logger.info("当前没有子心流需要评估。")
return
for sub_hf in subflows_snapshot:
flow_id = sub_hf.subheartflow_id
stream_name = chat_manager.get_stream_name(flow_id) or flow_id
log_prefix = f"[{stream_name}]"
current_subflow_state = sub_hf.chat_state.chat_status
# --- 获取观察内容 ---
# 从 sub_hf.observations 获取 ChattingObservation 并提取信息
_observation_summary = "没有可用的观察信息。" # 默认值
try:
# 检查 observations 列表是否存在且不为空
# 假设第一个观察者是 ChattingObservation
first_observation = sub_hf.observations[0]
if isinstance(first_observation, ChattingObservation):
# 组合中期记忆和当前聊天内容
current_chat = first_observation.talking_message_str or "当前聊天内容"
combined_summary = f"当前聊天内容:\n{current_chat}"
else:
logger.warning(f"{log_prefix} [{stream_name}] 第一个观察者不是 ChattingObservation 类型。")
first_observation = sub_hf.observations[0]
if isinstance(first_observation, ChattingObservation):
# 组合中期记忆和当前聊天内容
current_chat = first_observation.talking_message_str or "当前聊天内容"
combined_summary = f"当前聊天内容:\n{current_chat}"
else:
logger.warning(f"{log_prefix} [{stream_name}] 第一个观察者不是 ChattingObservation 类型。")
except Exception as e:
logger.warning(f"{log_prefix} [{stream_name}] 获取观察信息失败: {e}", exc_info=True)
# 保留默认值或错误信息
combined_summary = f"获取观察信息时出错: {e}"
# --- 获取麦麦状态 ---
mai_state_description = f"麦麦当前状态: {current_mai_state.value}"
@@ -414,7 +355,7 @@ class SubHeartflowManager:
# 调用LLM评估
should_activate = await self._llm_evaluate_state_transition(prompt)
if should_activate is None: # 处理解析失败或意外情况
logger.warning(f"{log_prefix} [{stream_name}] LLM评估返回无效结果跳过。")
logger.warning(f"{log_prefix}LLM评估返回无效结果跳过。")
continue
if should_activate:
@@ -423,17 +364,19 @@ class SubHeartflowManager:
current_chat_count = self.count_subflows_by_state_nolock(ChatState.CHAT)
if current_chat_count < chat_limit:
logger.info(
f"{log_prefix} [{stream_name}] LLM建议激活到CHAT状态且未达上限({current_chat_count}/{chat_limit})。正在尝试转换..."
f"{log_prefix}LLM建议激活到CHAT状态且未达上限({current_chat_count}/{chat_limit})。正在尝试转换..."
)
await sub_hf.change_chat_state(ChatState.CHAT)
if sub_hf.chat_state.chat_status == ChatState.CHAT:
transitioned_to_chat += 1
else:
logger.warning(f"{log_prefix} [{stream_name}] 尝试激活到CHAT失败。")
logger.warning(f"{log_prefix}尝试激活到CHAT失败。")
else:
logger.info(
f"{log_prefix} [{stream_name}] LLM建议激活到CHAT状态但已达到上限({current_chat_count}/{chat_limit})。跳过转换。"
f"{log_prefix}LLM建议激活到CHAT状态但已达到上限({current_chat_count}/{chat_limit})。跳过转换。"
)
else:
logger.info(f"{log_prefix}LLM建议不激活到CHAT状态。")
# --- 针对 CHAT 状态 ---
elif current_subflow_state == ChatState.CHAT:
@@ -452,20 +395,18 @@ class SubHeartflowManager:
# 调用LLM评估
should_deactivate = await self._llm_evaluate_state_transition(prompt)
if should_deactivate is None: # 处理解析失败或意外情况
logger.warning(f"{log_prefix} [{stream_name}] LLM评估返回无效结果跳过。")
logger.warning(f"{log_prefix}LLM评估返回无效结果跳过。")
continue
if should_deactivate:
logger.info(f"{log_prefix} [{stream_name}] LLM建议进入ABSENT状态。正在尝试转换...")
logger.info(f"{log_prefix}LLM建议进入ABSENT状态。正在尝试转换...")
await sub_hf.change_chat_state(ChatState.ABSENT)
if sub_hf.chat_state.chat_status == ChatState.ABSENT:
transitioned_to_absent += 1
logger.info(
f"{log_prefix} LLM评估周期结束。"
f" 成功转换到CHAT: {transitioned_to_chat}."
f" 成功转换到ABSENT: {transitioned_to_absent}."
)
else:
logger.info(f"{log_prefix}LLM建议不进入ABSENT状态。")
async def _llm_evaluate_state_transition(self, prompt: str) -> Optional[bool]:
"""
@@ -482,7 +423,8 @@ class SubHeartflowManager:
try:
# --- 真实的 LLM 调用 ---
response_text, _ = await self.llm_state_evaluator.generate_response_async(prompt)
logger.debug(f"{log_prefix} 使用模型 {self.llm_state_evaluator.model_name} 评估,原始响应: ```{response_text}```")
logger.debug(f"{log_prefix} 使用模型 {self.llm_state_evaluator.model_name} 评估")
logger.debug(f"{log_prefix} 原始响应: {response_text}")
# --- 解析 JSON 响应 ---
try:
@@ -573,3 +515,46 @@ class SubHeartflowManager:
logger.error(f"删除 SubHeartflow {subheartflow_id} 时出错: {e}", exc_info=True)
else:
logger.warning(f"尝试删除不存在的 SubHeartflow: {subheartflow_id}")
# --- 新增:处理 HFC 无回复回调的专用方法 --- #
async def _handle_hfc_no_reply(self, subheartflow_id: Any):
"""处理来自 HeartFChatting 的连续无回复信号 (通过 partial 绑定 ID)"""
# 注意:这里不需要再获取锁,因为 request_absent_transition 内部会处理锁
logger.debug(f"[管理器 HFC 处理器] 接收到来自 {subheartflow_id} 的 HFC 无回复信号")
await self.request_absent_transition(subheartflow_id)
# --- 结束新增 --- #
# --- 新增:处理来自 HeartFChatting 的状态转换请求 --- #
async def request_absent_transition(self, subflow_id: Any):
"""
接收来自 HeartFChatting 的请求,将特定子心流的状态转换为 ABSENT。
通常在连续多次 "no_reply" 后被调用。
Args:
subflow_id: 需要转换状态的子心流 ID。
"""
async with self._lock:
subflow = self.subheartflows.get(subflow_id)
if not subflow:
logger.warning(f"[状态转换请求] 尝试转换不存在的子心流 {subflow_id} 到 ABSENT")
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:
logger.info(f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 转换为 ABSENT")
try:
await subflow.change_chat_state(ChatState.ABSENT)
logger.info(f"[状态转换请求] {stream_name} 状态已成功转换为 ABSENT")
except Exception as e:
logger.error(f"[状态转换请求] 转换 {stream_name} 到 ABSENT 时出错: {e}", exc_info=True)
elif current_state == ChatState.ABSENT:
logger.debug(f"[状态转换请求] {stream_name} 已处于 ABSENT 状态,无需转换")
else:
logger.warning(
f"[状态转换请求] 收到对 {stream_name} 的请求,但其状态为 {current_state.value} (非 FOCUSED),不执行转换"
)
# --- 结束新增 --- #

View File

@@ -2,7 +2,7 @@ import asyncio
import time
import traceback
import random # <-- 添加导入
from typing import List, Optional, Dict, Any, Deque
from typing import List, Optional, Dict, Any, Deque, Callable, Coroutine
from collections import deque
from src.plugins.chat.message import MessageRecv, BaseMessageInfo, MessageThinking, MessageSending
from src.plugins.chat.message import Seg # Local import needed after move
@@ -25,7 +25,6 @@ import contextlib
from src.plugins.utils.chat_message_builder import num_new_messages_since
from src.plugins.heartFC_chat.heartFC_Cycleinfo import CycleInfo
from .heartFC_sender import HeartFCSender
# --- End import ---
INITIAL_DURATION = 60.0
@@ -155,18 +154,30 @@ class HeartFChatting:
其生命周期现在由其关联的 SubHeartflow 的 FOCUSED 状态控制。
"""
def __init__(self, chat_id: str, sub_mind: SubMind, observations: Observation):
CONSECUTIVE_NO_REPLY_THRESHOLD = 4 # 连续不回复的阈值
def __init__(
self,
chat_id: str,
sub_mind: SubMind,
observations: Observation,
on_consecutive_no_reply_callback: Callable[[], Coroutine[None, None, None]]
):
"""
HeartFChatting 初始化函数
参数:
chat_id: 聊天流唯一标识符(如stream_id)
sub_mind: 关联的子思维
observations: 关联的观察列表
on_consecutive_no_reply_callback: 连续不回复达到阈值时调用的异步回调函数
"""
# 基础属性
self.stream_id: str = chat_id # 聊天流ID
self.chat_stream: Optional[ChatStream] = None # 关联的聊天流
self.sub_mind: SubMind = sub_mind # 关联的子思维
self.observations: List[Observation] = observations # 关联的观察列表,用于监控聊天流状态
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}]"
@@ -198,6 +209,8 @@ class HeartFChatting:
self._cycle_counter = 0
self._cycle_history: Deque[CycleInfo] = deque(maxlen=10) # 保留最近10个循环的信息
self._current_cycle: Optional[CycleInfo] = None
self._lian_xu_bu_hui_fu_ci_shu: int = 0 # <--- 新增:连续不回复计数器
self._shutting_down: bool = False # <--- 新增:关闭标志位
async def _initialize(self) -> bool:
"""
@@ -276,6 +289,12 @@ class HeartFChatting:
"""主循环,持续进行计划并可能回复消息,直到被外部取消。"""
try:
while True: # 主循环
# --- 在循环开始处检查关闭标志 ---
if self._shutting_down:
logger.info(f"{self.log_prefix} 检测到关闭标志,退出 HFC 循环。")
break
# --------------------------------
# 创建新的循环信息
self._cycle_counter += 1
self._current_cycle = CycleInfo(self._cycle_counter)
@@ -287,6 +306,12 @@ class HeartFChatting:
# 执行规划和处理阶段
async with self._get_cycle_context() as acquired_lock:
if not acquired_lock:
# 如果未能获取锁(理论上不太可能,除非 shutdown 过程中释放了但又被抢了?)
# 或者也可以在这里再次检查 self._shutting_down
if self._shutting_down:
break # 再次检查,确保退出
logger.warning(f"{self.log_prefix} 未能获取循环处理锁,跳过本次循环。")
await asyncio.sleep(0.1) # 短暂等待避免空转
continue
# 记录规划开始时间点
@@ -320,7 +345,11 @@ class HeartFChatting:
)
except asyncio.CancelledError:
logger.info(f"{self.log_prefix} HeartFChatting: 麦麦的认真水群(HFC)被取消了")
# 设置了关闭标志位后被取消是正常流程
if not self._shutting_down:
logger.warning(f"{self.log_prefix} HeartFChatting: 麦麦的认真水群(HFC)循环意外被取消")
else:
logger.info(f"{self.log_prefix} HeartFChatting: 麦麦的认真水群(HFC)循环已取消 (正常关闭)")
except Exception as e:
logger.error(f"{self.log_prefix} HeartFChatting: 意外错误: {e}")
logger.error(traceback.format_exc())
@@ -451,6 +480,8 @@ class HeartFChatting:
return await handler(reasoning, planner_start_db_time, cycle_timers), ""
except HeartFCError as e:
logger.error(f"{self.log_prefix} 处理{action}时出错: {e}")
# 出错时也重置计数器
self._lian_xu_bu_hui_fu_ci_shu = 0
return False, ""
async def _handle_text_reply(self, reasoning: str, emoji_query: str, cycle_timers: dict) -> tuple[bool, str]:
@@ -471,6 +502,8 @@ class HeartFChatting:
返回:
tuple[bool, str]: (是否回复成功, 思考消息ID)
"""
# 重置连续不回复计数器
self._lian_xu_bu_hui_fu_ci_shu = 0
# 获取锚点消息
anchor_message = await self._get_anchor_message()
@@ -544,8 +577,9 @@ class HeartFChatting:
处理不回复的情况
工作流程:
1. 等待新消息
2. 超时或收到新消息时返回
1. 等待新消息、超时或关闭信号
2. 根据等待结果更新连续不回复计数
3. 如果达到阈值,触发回调
参数:
reasoning: 不回复的原因
@@ -561,14 +595,36 @@ class HeartFChatting:
try:
with Timer("等待新消息", cycle_timers):
return await self._wait_for_new_message(observation, planner_start_db_time, self.log_prefix)
# 等待新消息、超时或关闭信号,并获取结果
await self._wait_for_new_message(observation, planner_start_db_time, self.log_prefix)
if not self._shutting_down:
self._lian_xu_bu_hui_fu_ci_shu += 1
logger.debug(f"{self.log_prefix} 连续不回复计数增加: {self._lian_xu_bu_hui_fu_ci_shu}/{self.CONSECUTIVE_NO_REPLY_THRESHOLD}")
# 检查是否达到阈值
if self._lian_xu_bu_hui_fu_ci_shu >= self.CONSECUTIVE_NO_REPLY_THRESHOLD:
logger.info(f"{self.log_prefix} 连续不回复达到阈值 ({self._lian_xu_bu_hui_fu_ci_shu}次),调用回调请求状态转换")
# 调用回调。注意:这里不重置计数器,依赖回调函数成功改变状态来隐式重置上下文。
await self.on_consecutive_no_reply_callback()
return True
except asyncio.CancelledError:
logger.info(f"{self.log_prefix} 等待被中断")
# 如果在等待过程中任务被取消(可能是因为 shutdown
logger.info(f"{self.log_prefix} 处理 'no_reply' 时等待被中断 (CancelledError)")
# 让异常向上传播,由 _hfc_loop 的异常处理逻辑接管
raise
except Exception as e: # 捕获调用管理器或其他地方可能发生的错误
logger.error(f"{self.log_prefix} 处理 'no_reply' 时发生错误: {e}")
logger.error(traceback.format_exc())
# 发生意外错误时,可以选择是否重置计数器,这里选择不重置
return False # 表示动作未成功
async def _wait_for_new_message(self, observation, planner_start_db_time: float, log_prefix: str) -> bool:
"""
等待新消息
等待新消息 或 检测到关闭信号
参数:
observation: 观察实例
@@ -576,19 +632,36 @@ class HeartFChatting:
log_prefix: 日志前缀
返回:
bool: 是否检测到新消息
bool: 是否检测到新消息 (如果因关闭信号退出则返回 False)
"""
wait_start_time = time.monotonic()
while True:
# --- 在每次循环开始时检查关闭标志 ---
if self._shutting_down:
logger.info(f"{log_prefix} 等待新消息时检测到关闭信号,中断等待。")
return False # 表示因为关闭而退出
# -----------------------------------
# 检查新消息
if await observation.has_new_messages_since(planner_start_db_time):
logger.info(f"{log_prefix} 检测到新消息")
return True
# 检查超时 (放在检查新消息和关闭之后)
if time.monotonic() - wait_start_time > 120:
logger.warning(f"{log_prefix} 等待超时(120秒)")
logger.warning(f"{log_prefix} 等待新消息超时(20秒)")
return False
await asyncio.sleep(1.5)
try:
# 短暂休眠,让其他任务有机会运行,并能更快响应取消或关闭
await asyncio.sleep(0.5) # 缩短休眠时间
except asyncio.CancelledError:
# 如果在休眠时被取消,再次检查关闭标志
# 如果是正常关闭,则不需要警告
if not self._shutting_down:
logger.warning(f"{log_prefix} _wait_for_new_message 的休眠被意外取消")
# 无论如何,重新抛出异常,让上层处理
raise
async def _log_cycle_timers(self, cycle_timers: dict, log_prefix: str):
"""记录循环周期的计时器结果"""
@@ -599,7 +672,9 @@ class HeartFChatting:
timer_strings.append(f"{name}: {formatted_time}")
if timer_strings:
logger.debug(f"{log_prefix} 该次决策耗时: {'; '.join(timer_strings)}")
# 在记录前检查关闭标志
if not self._shutting_down:
logger.debug(f"{log_prefix} 该次决策耗时: {'; '.join(timer_strings)}")
async def _handle_cycle_delay(self, action_taken_this_cycle: bool, cycle_start_time: float, log_prefix: str):
"""处理循环延迟"""
@@ -835,6 +910,7 @@ class HeartFChatting:
async def shutdown(self):
"""优雅关闭HeartFChatting实例取消活动循环任务"""
logger.info(f"{self.log_prefix} 正在关闭HeartFChatting...")
self._shutting_down = True # <-- 在开始关闭时设置标志位
# 取消循环任务
if self._loop_task and not self._loop_task.done():
@@ -865,6 +941,25 @@ class HeartFChatting:
action=action,
reasoning=reasoning,
)
# 在记录循环日志前检查关闭标志
if not self._shutting_down:
self._current_cycle.complete_cycle()
self._cycle_history.append(self._current_cycle)
# 记录循环信息和计时器结果
timer_strings = []
for name, elapsed in self._current_cycle.timers.items():
formatted_time = f"{elapsed * 1000:.2f}毫秒" if elapsed < 1 else f"{elapsed:.2f}"
timer_strings.append(f"{name}: {formatted_time}")
logger.debug(
f"{self.log_prefix} 第 #{self._current_cycle.cycle_id}次思考完成,"
f"耗时: {self._current_cycle.end_time - self._current_cycle.start_time:.2f}秒, "
f"动作: {self._current_cycle.action_type}"
+ (f"\n计时器详情: {'; '.join(timer_strings)}" if timer_strings else "")
)
return prompt
async def _build_planner_prompt(