feat:为复读增加硬限制
This commit is contained in:
@@ -8,9 +8,22 @@ from src.plugins.moods.moods import MoodManager
|
|||||||
logger = get_logger("mai_state")
|
logger = get_logger("mai_state")
|
||||||
|
|
||||||
|
|
||||||
# enable_unlimited_hfc_chat = True
|
# -- 状态相关的可配置参数 (可以从 glocal_config 加载) --
|
||||||
enable_unlimited_hfc_chat = False
|
enable_unlimited_hfc_chat = True # 调试用:无限专注聊天
|
||||||
|
# enable_unlimited_hfc_chat = False
|
||||||
|
prevent_offline_state = True # 调试用:防止进入离线状态
|
||||||
|
|
||||||
|
# 不同状态下普通聊天的最大消息数
|
||||||
|
MAX_NORMAL_CHAT_NUM_PEEKING = 30
|
||||||
|
MAX_NORMAL_CHAT_NUM_NORMAL = 40
|
||||||
|
MAX_NORMAL_CHAT_NUM_FOCUSED = 30
|
||||||
|
|
||||||
|
# 不同状态下专注聊天的最大消息数
|
||||||
|
MAX_FOCUSED_CHAT_NUM_PEEKING = 20
|
||||||
|
MAX_FOCUSED_CHAT_NUM_NORMAL = 30
|
||||||
|
MAX_FOCUSED_CHAT_NUM_FOCUSED = 40
|
||||||
|
|
||||||
|
# -- 状态定义 --
|
||||||
|
|
||||||
class MaiState(enum.Enum):
|
class MaiState(enum.Enum):
|
||||||
"""
|
"""
|
||||||
@@ -34,11 +47,11 @@ class MaiState(enum.Enum):
|
|||||||
if self == MaiState.OFFLINE:
|
if self == MaiState.OFFLINE:
|
||||||
return 0
|
return 0
|
||||||
elif self == MaiState.PEEKING:
|
elif self == MaiState.PEEKING:
|
||||||
return 30
|
return MAX_NORMAL_CHAT_NUM_PEEKING
|
||||||
elif self == MaiState.NORMAL_CHAT:
|
elif self == MaiState.NORMAL_CHAT:
|
||||||
return 40
|
return MAX_NORMAL_CHAT_NUM_NORMAL
|
||||||
elif self == MaiState.FOCUSED_CHAT:
|
elif self == MaiState.FOCUSED_CHAT:
|
||||||
return 30
|
return MAX_NORMAL_CHAT_NUM_FOCUSED
|
||||||
|
|
||||||
def get_focused_chat_max_num(self):
|
def get_focused_chat_max_num(self):
|
||||||
# 调试用
|
# 调试用
|
||||||
@@ -48,11 +61,11 @@ class MaiState(enum.Enum):
|
|||||||
if self == MaiState.OFFLINE:
|
if self == MaiState.OFFLINE:
|
||||||
return 0
|
return 0
|
||||||
elif self == MaiState.PEEKING:
|
elif self == MaiState.PEEKING:
|
||||||
return 20
|
return MAX_FOCUSED_CHAT_NUM_PEEKING
|
||||||
elif self == MaiState.NORMAL_CHAT:
|
elif self == MaiState.NORMAL_CHAT:
|
||||||
return 30
|
return MAX_FOCUSED_CHAT_NUM_NORMAL
|
||||||
elif self == MaiState.FOCUSED_CHAT:
|
elif self == MaiState.FOCUSED_CHAT:
|
||||||
return 40
|
return MAX_FOCUSED_CHAT_NUM_FOCUSED
|
||||||
|
|
||||||
|
|
||||||
class MaiStateInfo:
|
class MaiStateInfo:
|
||||||
@@ -110,7 +123,6 @@ class MaiStateManager:
|
|||||||
"""管理 Mai 的整体状态转换逻辑"""
|
"""管理 Mai 的整体状态转换逻辑"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# MaiStateManager doesn't hold the state itself, it operates on a MaiStateInfo instance.
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def check_and_decide_next_state(self, current_state_info: MaiStateInfo) -> Optional[MaiState]:
|
def check_and_decide_next_state(self, current_state_info: MaiStateInfo) -> Optional[MaiState]:
|
||||||
@@ -129,6 +141,13 @@ class MaiStateManager:
|
|||||||
time_since_last_min_check = current_time - current_state_info.last_min_check_time
|
time_since_last_min_check = current_time - current_state_info.last_min_check_time
|
||||||
next_state: Optional[MaiState] = None
|
next_state: Optional[MaiState] = None
|
||||||
|
|
||||||
|
# 辅助函数:根据 prevent_offline_state 标志调整目标状态
|
||||||
|
def _resolve_offline(candidate_state: MaiState) -> MaiState:
|
||||||
|
if prevent_offline_state and candidate_state == MaiState.OFFLINE:
|
||||||
|
logger.debug(f"阻止进入 OFFLINE,改为 PEEKING")
|
||||||
|
return MaiState.PEEKING
|
||||||
|
return candidate_state
|
||||||
|
|
||||||
if current_status == MaiState.OFFLINE:
|
if current_status == MaiState.OFFLINE:
|
||||||
logger.info("当前[离线],没看手机,思考要不要上线看看......")
|
logger.info("当前[离线],没看手机,思考要不要上线看看......")
|
||||||
elif current_status == MaiState.PEEKING:
|
elif current_status == MaiState.PEEKING:
|
||||||
@@ -141,61 +160,71 @@ class MaiStateManager:
|
|||||||
# 1. 麦麦每分钟都有概率离线
|
# 1. 麦麦每分钟都有概率离线
|
||||||
if time_since_last_min_check >= 60:
|
if time_since_last_min_check >= 60:
|
||||||
if current_status != MaiState.OFFLINE:
|
if current_status != MaiState.OFFLINE:
|
||||||
if random.random() < 0.03: # 3% 概率切换到 OFFLINE,20分钟有50%的概率还在线
|
if random.random() < 0.03: # 3% 概率切换到 OFFLINE
|
||||||
logger.debug(f"突然不想聊了,从 {current_status.value} 切换到 离线")
|
potential_next = MaiState.OFFLINE
|
||||||
next_state = MaiState.OFFLINE
|
resolved_next = _resolve_offline(potential_next)
|
||||||
|
logger.debug(f"规则1:概率触发下线,resolve 为 {resolved_next.value}")
|
||||||
|
# 只有当解析后的状态与当前状态不同时才设置 next_state
|
||||||
|
if resolved_next != current_status:
|
||||||
|
next_state = resolved_next
|
||||||
|
|
||||||
# 2. 状态持续时间规则 (如果没有自行下线)
|
# 2. 状态持续时间规则 (只有在规则1没有触发状态改变时才检查)
|
||||||
if next_state is None:
|
if next_state is None:
|
||||||
|
time_limit_exceeded = False
|
||||||
|
choices_list = []
|
||||||
|
weights = []
|
||||||
|
rule_id = ""
|
||||||
|
|
||||||
if current_status == MaiState.OFFLINE:
|
if current_status == MaiState.OFFLINE:
|
||||||
# OFFLINE 最多保持一分钟
|
# 注意:即使 prevent_offline_state=True,也可能从初始的 OFFLINE 状态启动
|
||||||
# 目前是一个调试值,可以修改
|
|
||||||
if time_in_current_status >= 60:
|
if time_in_current_status >= 60:
|
||||||
|
time_limit_exceeded = True
|
||||||
|
rule_id = "2.1 (From OFFLINE)"
|
||||||
weights = [30, 30, 20, 20]
|
weights = [30, 30, 20, 20]
|
||||||
choices_list = [MaiState.PEEKING, MaiState.NORMAL_CHAT, MaiState.FOCUSED_CHAT, MaiState.OFFLINE]
|
choices_list = [MaiState.PEEKING, MaiState.NORMAL_CHAT, MaiState.FOCUSED_CHAT, MaiState.OFFLINE]
|
||||||
next_state_candidate = random.choices(choices_list, weights=weights, k=1)[0]
|
|
||||||
if next_state_candidate != MaiState.OFFLINE:
|
|
||||||
next_state = next_state_candidate
|
|
||||||
logger.debug(f"上线!开始 {next_state.value}")
|
|
||||||
else:
|
|
||||||
# 继续离线状态
|
|
||||||
next_state = MaiState.OFFLINE
|
|
||||||
|
|
||||||
elif current_status == MaiState.PEEKING:
|
elif current_status == MaiState.PEEKING:
|
||||||
if time_in_current_status >= 600: # PEEKING 最多持续 600 秒
|
if time_in_current_status >= 600: # PEEKING 最多持续 600 秒
|
||||||
|
time_limit_exceeded = True
|
||||||
|
rule_id = "2.2 (From PEEKING)"
|
||||||
weights = [70, 20, 10]
|
weights = [70, 20, 10]
|
||||||
choices_list = [MaiState.OFFLINE, MaiState.NORMAL_CHAT, MaiState.FOCUSED_CHAT]
|
choices_list = [MaiState.OFFLINE, MaiState.NORMAL_CHAT, MaiState.FOCUSED_CHAT]
|
||||||
next_state = random.choices(choices_list, weights=weights, k=1)[0]
|
|
||||||
logger.debug(f"手机看完了,接下来 {next_state.value}")
|
|
||||||
|
|
||||||
elif current_status == MaiState.NORMAL_CHAT:
|
elif current_status == MaiState.NORMAL_CHAT:
|
||||||
if time_in_current_status >= 300: # NORMAL_CHAT 最多持续 300 秒
|
if time_in_current_status >= 300: # NORMAL_CHAT 最多持续 300 秒
|
||||||
|
time_limit_exceeded = True
|
||||||
|
rule_id = "2.3 (From NORMAL_CHAT)"
|
||||||
weights = [50, 50]
|
weights = [50, 50]
|
||||||
choices_list = [MaiState.OFFLINE, MaiState.FOCUSED_CHAT]
|
choices_list = [MaiState.OFFLINE, MaiState.FOCUSED_CHAT]
|
||||||
next_state = random.choices(choices_list, weights=weights, k=1)[0]
|
|
||||||
if next_state == MaiState.FOCUSED_CHAT:
|
|
||||||
logger.debug(f"继续深入聊天, {next_state.value}")
|
|
||||||
else:
|
|
||||||
logger.debug(f"聊完了,接下来 {next_state.value}")
|
|
||||||
|
|
||||||
elif current_status == MaiState.FOCUSED_CHAT:
|
elif current_status == MaiState.FOCUSED_CHAT:
|
||||||
if time_in_current_status >= 600: # FOCUSED_CHAT 最多持续 600 秒
|
if time_in_current_status >= 600: # FOCUSED_CHAT 最多持续 600 秒
|
||||||
|
time_limit_exceeded = True
|
||||||
|
rule_id = "2.4 (From FOCUSED_CHAT)"
|
||||||
weights = [80, 20]
|
weights = [80, 20]
|
||||||
choices_list = [MaiState.OFFLINE, MaiState.NORMAL_CHAT]
|
choices_list = [MaiState.OFFLINE, MaiState.NORMAL_CHAT]
|
||||||
next_state = random.choices(choices_list, weights=weights, k=1)[0]
|
|
||||||
logger.debug(f"深入聊天结束,接下来 {next_state.value}")
|
|
||||||
|
|
||||||
|
if time_limit_exceeded:
|
||||||
|
next_state_candidate = random.choices(choices_list, weights=weights, k=1)[0]
|
||||||
|
resolved_candidate = _resolve_offline(next_state_candidate)
|
||||||
|
logger.debug(f"规则{rule_id}:时间到,随机选择 {next_state_candidate.value},resolve 为 {resolved_candidate.value}")
|
||||||
|
next_state = resolved_candidate # 直接使用解析后的状态
|
||||||
|
|
||||||
|
# 注意:enable_unlimited_hfc_chat 优先级高于 prevent_offline_state
|
||||||
|
# 如果触发了这个,它会覆盖上面规则2设置的 next_state
|
||||||
if enable_unlimited_hfc_chat:
|
if enable_unlimited_hfc_chat:
|
||||||
logger.debug("调试用:开挂了,强制切换到专注聊天")
|
logger.debug("调试用:开挂了,强制切换到专注聊天")
|
||||||
next_state = MaiState.FOCUSED_CHAT
|
next_state = MaiState.FOCUSED_CHAT
|
||||||
|
|
||||||
|
# --- 最终决策 --- #
|
||||||
# 如果决定了下一个状态,且这个状态与当前状态不同,则返回下一个状态
|
# 如果决定了下一个状态,且这个状态与当前状态不同,则返回下一个状态
|
||||||
if next_state is not None and next_state != current_status:
|
if next_state is not None and next_state != current_status:
|
||||||
return next_state
|
return next_state
|
||||||
# 如果决定保持 OFFLINE (next_state == MaiState.OFFLINE) 且当前也是 OFFLINE,
|
# 如果决定保持 OFFLINE (next_state == MaiState.OFFLINE) 且当前也是 OFFLINE,
|
||||||
# 并且是由于持续时间规则触发的,返回 OFFLINE 以便调用者可以重置计时器
|
# 并且是由于持续时间规则触发的,返回 OFFLINE 以便调用者可以重置计时器。
|
||||||
|
# 注意:这个分支只有在 prevent_offline_state = False 时才可能被触发。
|
||||||
elif next_state == MaiState.OFFLINE and current_status == MaiState.OFFLINE and time_in_current_status >= 60:
|
elif next_state == MaiState.OFFLINE and current_status == MaiState.OFFLINE and time_in_current_status >= 60:
|
||||||
logger.debug("决定保持 OFFLINE (持续时间规则),返回 OFFLINE 以提示重置计时器。")
|
logger.debug("决定保持 OFFLINE (持续时间规则),返回 OFFLINE 以提示重置计时器。")
|
||||||
return MaiState.OFFLINE # Return OFFLINE to signal caller that timer reset might be needed
|
return MaiState.OFFLINE # Return OFFLINE to signal caller that timer reset might be needed
|
||||||
else:
|
else:
|
||||||
|
# 1. next_state is None (没有触发任何转换规则)
|
||||||
|
# 2. next_state is not None 但等于 current_status (例如规则1想切OFFLINE但被resolve成PEEKING,而当前已经是PEEKING)
|
||||||
|
# 3. next_state is OFFLINE, current is OFFLINE, 但不是因为时间规则触发 (例如初始状态还没到60秒)
|
||||||
return None # 没有状态转换发生或无需重置计时器
|
return None # 没有状态转换发生或无需重置计时器
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
import random # <--- 添加导入
|
||||||
from typing import List, Optional, Dict, Any, Deque, Callable, Coroutine
|
from typing import List, Optional, Dict, Any, Deque, Callable, Coroutine
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from src.plugins.chat.message import MessageRecv, BaseMessageInfo, MessageThinking, MessageSending
|
from src.plugins.chat.message import MessageRecv, BaseMessageInfo, MessageThinking, MessageSending
|
||||||
@@ -31,6 +32,8 @@ from src.individuality.individuality import Individuality
|
|||||||
|
|
||||||
INITIAL_DURATION = 60.0
|
INITIAL_DURATION = 60.0
|
||||||
|
|
||||||
|
WAITING_TIME_THRESHOLD = 300 # 等待新消息时间阈值,单位秒
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger("interest") # Logger Name Changed
|
logger = get_logger("interest") # Logger Name Changed
|
||||||
|
|
||||||
@@ -45,10 +48,11 @@ class ActionManager:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
# 初始化为默认动作集
|
# 初始化为默认动作集
|
||||||
self._available_actions: Dict[str, str] = DEFAULT_ACTIONS.copy()
|
self._available_actions: Dict[str, str] = DEFAULT_ACTIONS.copy()
|
||||||
|
self._original_actions_backup: Optional[Dict[str, str]] = None # 用于临时移除时的备份
|
||||||
|
|
||||||
def get_available_actions(self) -> Dict[str, str]:
|
def get_available_actions(self) -> Dict[str, str]:
|
||||||
"""获取当前可用的动作集"""
|
"""获取当前可用的动作集"""
|
||||||
return self._available_actions
|
return self._available_actions.copy() # 返回副本以防外部修改
|
||||||
|
|
||||||
def add_action(self, action_name: str, description: str) -> bool:
|
def add_action(self, action_name: str, description: str) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -81,6 +85,30 @@ class ActionManager:
|
|||||||
del self._available_actions[action_name]
|
del self._available_actions[action_name]
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def temporarily_remove_actions(self, actions_to_remove: List[str]):
|
||||||
|
"""
|
||||||
|
临时移除指定的动作,备份原始动作集。
|
||||||
|
如果已经有备份,则不重复备份。
|
||||||
|
"""
|
||||||
|
if self._original_actions_backup is None:
|
||||||
|
self._original_actions_backup = self._available_actions.copy()
|
||||||
|
|
||||||
|
actions_actually_removed = []
|
||||||
|
for action_name in actions_to_remove:
|
||||||
|
if action_name in self._available_actions:
|
||||||
|
del self._available_actions[action_name]
|
||||||
|
actions_actually_removed.append(action_name)
|
||||||
|
# logger.debug(f"临时移除了动作: {actions_actually_removed}") # 可选日志
|
||||||
|
|
||||||
|
def restore_actions(self):
|
||||||
|
"""
|
||||||
|
恢复之前备份的原始动作集。
|
||||||
|
"""
|
||||||
|
if self._original_actions_backup is not None:
|
||||||
|
self._available_actions = self._original_actions_backup.copy()
|
||||||
|
self._original_actions_backup = None
|
||||||
|
# logger.debug("恢复了原始动作集") # 可选日志
|
||||||
|
|
||||||
def clear_actions(self):
|
def clear_actions(self):
|
||||||
"""清空所有动作"""
|
"""清空所有动作"""
|
||||||
self._available_actions.clear()
|
self._available_actions.clear()
|
||||||
@@ -151,7 +179,7 @@ class HeartFChatting:
|
|||||||
其生命周期现在由其关联的 SubHeartflow 的 FOCUSED 状态控制。
|
其生命周期现在由其关联的 SubHeartflow 的 FOCUSED 状态控制。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CONSECUTIVE_NO_REPLY_THRESHOLD = 5 # 连续不回复的阈值
|
CONSECUTIVE_NO_REPLY_THRESHOLD = 3 # 连续不回复的阈值
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -214,6 +242,7 @@ class HeartFChatting:
|
|||||||
self._current_cycle: Optional[CycleInfo] = None
|
self._current_cycle: Optional[CycleInfo] = None
|
||||||
self._lian_xu_bu_hui_fu_ci_shu: int = 0 # <--- 新增:连续不回复计数器
|
self._lian_xu_bu_hui_fu_ci_shu: int = 0 # <--- 新增:连续不回复计数器
|
||||||
self._shutting_down: bool = False # <--- 新增:关闭标志位
|
self._shutting_down: bool = False # <--- 新增:关闭标志位
|
||||||
|
self._lian_xu_deng_dai_shi_jian: float = 0.0 # <--- 新增:累计等待时间
|
||||||
|
|
||||||
async def _initialize(self) -> bool:
|
async def _initialize(self) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -489,6 +518,7 @@ class HeartFChatting:
|
|||||||
logger.error(f"{self.log_prefix} 处理{action}时出错: {e}")
|
logger.error(f"{self.log_prefix} 处理{action}时出错: {e}")
|
||||||
# 出错时也重置计数器
|
# 出错时也重置计数器
|
||||||
self._lian_xu_bu_hui_fu_ci_shu = 0
|
self._lian_xu_bu_hui_fu_ci_shu = 0
|
||||||
|
self._lian_xu_deng_dai_shi_jian = 0.0 # 重置累计等待时间
|
||||||
return False, ""
|
return False, ""
|
||||||
|
|
||||||
async def _handle_text_reply(self, reasoning: str, emoji_query: str, cycle_timers: dict) -> tuple[bool, str]:
|
async def _handle_text_reply(self, reasoning: str, emoji_query: str, cycle_timers: dict) -> tuple[bool, str]:
|
||||||
@@ -511,6 +541,7 @@ class HeartFChatting:
|
|||||||
"""
|
"""
|
||||||
# 重置连续不回复计数器
|
# 重置连续不回复计数器
|
||||||
self._lian_xu_bu_hui_fu_ci_shu = 0
|
self._lian_xu_bu_hui_fu_ci_shu = 0
|
||||||
|
self._lian_xu_deng_dai_shi_jian = 0.0 # 重置累计等待时间
|
||||||
|
|
||||||
# 获取锚点消息
|
# 获取锚点消息
|
||||||
anchor_message = await self._get_anchor_message()
|
anchor_message = await self._get_anchor_message()
|
||||||
@@ -566,6 +597,7 @@ class HeartFChatting:
|
|||||||
bool: 是否发送成功
|
bool: 是否发送成功
|
||||||
"""
|
"""
|
||||||
logger.info(f"{self.log_prefix} 决定回复表情({emoji_query}): {reasoning}")
|
logger.info(f"{self.log_prefix} 决定回复表情({emoji_query}): {reasoning}")
|
||||||
|
self._lian_xu_deng_dai_shi_jian = 0.0 # 重置累计等待时间(即使不计数也保持一致性)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
anchor = await self._get_anchor_message()
|
anchor = await self._get_anchor_message()
|
||||||
@@ -601,23 +633,41 @@ class HeartFChatting:
|
|||||||
observation = self.observations[0] if self.observations else None
|
observation = self.observations[0] if self.observations else None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
dang_qian_deng_dai = 0.0 # 初始化本次等待时间
|
||||||
with Timer("等待新消息", cycle_timers):
|
with Timer("等待新消息", cycle_timers):
|
||||||
# 等待新消息、超时或关闭信号,并获取结果
|
# 等待新消息、超时或关闭信号,并获取结果
|
||||||
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)
|
||||||
|
# 从计时器获取实际等待时间
|
||||||
|
dang_qian_deng_dai = cycle_timers.get("等待新消息", 0.0)
|
||||||
|
|
||||||
|
|
||||||
if not self._shutting_down:
|
if not self._shutting_down:
|
||||||
self._lian_xu_bu_hui_fu_ci_shu += 1
|
self._lian_xu_bu_hui_fu_ci_shu += 1
|
||||||
|
self._lian_xu_deng_dai_shi_jian += dang_qian_deng_dai # 累加等待时间
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{self.log_prefix} 连续不回复计数增加: {self._lian_xu_bu_hui_fu_ci_shu}/{self.CONSECUTIVE_NO_REPLY_THRESHOLD}"
|
f"{self.log_prefix} 连续不回复计数增加: {self._lian_xu_bu_hui_fu_ci_shu}/{self.CONSECUTIVE_NO_REPLY_THRESHOLD}, "
|
||||||
|
f"本次等待: {dang_qian_deng_dai:.2f}秒, 累计等待: {self._lian_xu_deng_dai_shi_jian:.2f}秒"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 检查是否达到阈值
|
# 检查是否同时达到次数和时间阈值
|
||||||
if self._lian_xu_bu_hui_fu_ci_shu >= self.CONSECUTIVE_NO_REPLY_THRESHOLD:
|
time_threshold = 0.66 * WAITING_TIME_THRESHOLD * self.CONSECUTIVE_NO_REPLY_THRESHOLD
|
||||||
|
if (self._lian_xu_bu_hui_fu_ci_shu >= self.CONSECUTIVE_NO_REPLY_THRESHOLD and
|
||||||
|
self._lian_xu_deng_dai_shi_jian >= time_threshold):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"{self.log_prefix} 连续不回复达到阈值 ({self._lian_xu_bu_hui_fu_ci_shu}次),调用回调请求状态转换"
|
f"{self.log_prefix} 连续不回复达到阈值 ({self._lian_xu_bu_hui_fu_ci_shu}次) "
|
||||||
|
f"且累计等待时间达到 {self._lian_xu_deng_dai_shi_jian:.2f}秒 (阈值 {time_threshold}秒),"
|
||||||
|
f"调用回调请求状态转换"
|
||||||
)
|
)
|
||||||
# 调用回调。注意:这里不重置计数器,依赖回调函数成功改变状态来隐式重置上下文。
|
# 调用回调。注意:这里不重置计数器和时间,依赖回调函数成功改变状态来隐式重置上下文。
|
||||||
await self.on_consecutive_no_reply_callback()
|
await self.on_consecutive_no_reply_callback()
|
||||||
|
elif self._lian_xu_bu_hui_fu_ci_shu >= self.CONSECUTIVE_NO_REPLY_THRESHOLD:
|
||||||
|
# 仅次数达到阈值,但时间未达到
|
||||||
|
logger.debug(
|
||||||
|
f"{self.log_prefix} 连续不回复次数达到阈值 ({self._lian_xu_bu_hui_fu_ci_shu}次) "
|
||||||
|
f"但累计等待时间 {self._lian_xu_deng_dai_shi_jian:.2f}秒 未达到时间阈值 ({time_threshold}秒),暂不调用回调"
|
||||||
|
)
|
||||||
|
# else: 次数和时间都未达到阈值,不做处理
|
||||||
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -658,8 +708,8 @@ class HeartFChatting:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# 检查超时 (放在检查新消息和关闭之后)
|
# 检查超时 (放在检查新消息和关闭之后)
|
||||||
if time.monotonic() - wait_start_time > 120:
|
if time.monotonic() - wait_start_time > WAITING_TIME_THRESHOLD:
|
||||||
logger.warning(f"{log_prefix} 等待新消息超时(20秒)")
|
logger.warning(f"{log_prefix} 等待新消息超时({WAITING_TIME_THRESHOLD}秒)")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -737,9 +787,49 @@ class HeartFChatting:
|
|||||||
|
|
||||||
参数:
|
参数:
|
||||||
current_mind: 子思维的当前思考结果
|
current_mind: 子思维的当前思考结果
|
||||||
|
cycle_timers: 计时器字典
|
||||||
|
is_re_planned: 是否为重新规划
|
||||||
"""
|
"""
|
||||||
logger.info(f"{self.log_prefix}[Planner] 开始{'重新' if is_re_planned else ''}执行规划器")
|
logger.info(f"{self.log_prefix}[Planner] 开始{'重新' if is_re_planned else ''}执行规划器")
|
||||||
|
|
||||||
|
# --- 新增:检查历史动作并调整可用动作 ---
|
||||||
|
lian_xu_wen_ben_hui_fu = 0 # 连续文本回复次数
|
||||||
|
actions_to_remove_temporarily = []
|
||||||
|
probability_roll = random.random() # 在循环外掷骰子一次,用于概率判断
|
||||||
|
|
||||||
|
# 反向遍历最近的循环历史
|
||||||
|
for cycle in reversed(self._cycle_history):
|
||||||
|
# 只关心实际执行了动作的循环
|
||||||
|
if cycle.action_taken:
|
||||||
|
if cycle.action_type == "text_reply":
|
||||||
|
lian_xu_wen_ben_hui_fu += 1
|
||||||
|
else:
|
||||||
|
break # 遇到非文本回复,中断计数
|
||||||
|
# 检查最近的3个循环即可,避免检查过多历史 (如果历史很长)
|
||||||
|
if len(self._cycle_history) > 0 and cycle.cycle_id <= self._cycle_history[0].cycle_id + (len(self._cycle_history) - 4):
|
||||||
|
break
|
||||||
|
|
||||||
|
logger.debug(f"{self.log_prefix}[Planner] 检测到连续文本回复次数: {lian_xu_wen_ben_hui_fu}")
|
||||||
|
|
||||||
|
# 根据连续次数决定临时移除哪些动作
|
||||||
|
if lian_xu_wen_ben_hui_fu >= 3:
|
||||||
|
logger.info(f"{self.log_prefix}[Planner] 连续回复 >= 3 次,强制移除 text_reply 和 emoji_reply")
|
||||||
|
actions_to_remove_temporarily.extend(["text_reply", "emoji_reply"])
|
||||||
|
elif lian_xu_wen_ben_hui_fu == 2:
|
||||||
|
if probability_roll < 0.8: # 80% 概率
|
||||||
|
logger.info(f"{self.log_prefix}[Planner] 连续回复 2 次,80% 概率移除 text_reply 和 emoji_reply (触发)")
|
||||||
|
actions_to_remove_temporarily.extend(["text_reply", "emoji_reply"])
|
||||||
|
else:
|
||||||
|
logger.info(f"{self.log_prefix}[Planner] 连续回复 2 次,80% 概率移除 text_reply 和 emoji_reply (未触发)")
|
||||||
|
elif lian_xu_wen_ben_hui_fu == 1:
|
||||||
|
if probability_roll < 0.4: # 40% 概率
|
||||||
|
logger.info(f"{self.log_prefix}[Planner] 连续回复 1 次,40% 概率移除 text_reply (触发)")
|
||||||
|
actions_to_remove_temporarily.append("text_reply")
|
||||||
|
else:
|
||||||
|
logger.info(f"{self.log_prefix}[Planner] 连续回复 1 次,40% 概率移除 text_reply (未触发)")
|
||||||
|
# 如果 lian_xu_wen_ben_hui_fu == 0,则不移除任何动作
|
||||||
|
# --- 结束:检查历史动作 ---
|
||||||
|
|
||||||
# 获取观察信息
|
# 获取观察信息
|
||||||
observation = self.observations[0]
|
observation = self.observations[0]
|
||||||
if is_re_planned:
|
if is_re_planned:
|
||||||
@@ -754,6 +844,11 @@ class HeartFChatting:
|
|||||||
emoji_query = "" # <--- 在这里初始化 emoji_query
|
emoji_query = "" # <--- 在这里初始化 emoji_query
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# --- 新增:应用临时动作移除 ---
|
||||||
|
if actions_to_remove_temporarily:
|
||||||
|
self.action_manager.temporarily_remove_actions(actions_to_remove_temporarily)
|
||||||
|
logger.debug(f"{self.log_prefix}[Planner] 临时移除的动作: {actions_to_remove_temporarily}, 当前可用: {list(self.action_manager.get_available_actions().keys())}")
|
||||||
|
|
||||||
# --- 构建提示词 ---
|
# --- 构建提示词 ---
|
||||||
replan_prompt_str = ""
|
replan_prompt_str = ""
|
||||||
if is_re_planned:
|
if is_re_planned:
|
||||||
@@ -767,6 +862,7 @@ class HeartFChatting:
|
|||||||
# --- 调用 LLM ---
|
# --- 调用 LLM ---
|
||||||
try:
|
try:
|
||||||
planner_tools = self.action_manager.get_planner_tool_definition()
|
planner_tools = self.action_manager.get_planner_tool_definition()
|
||||||
|
logger.debug(f"{self.log_prefix}[Planner] 本次使用的工具定义: {planner_tools}") # 记录本次使用的工具
|
||||||
_response_text, _reasoning_content, tool_calls = await self.planner_llm.generate_response_tool_async(
|
_response_text, _reasoning_content, tool_calls = await self.planner_llm.generate_response_tool_async(
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
tools=planner_tools,
|
tools=planner_tools,
|
||||||
@@ -810,15 +906,23 @@ class HeartFChatting:
|
|||||||
extracted_action = arguments.get("action", "no_reply")
|
extracted_action = arguments.get("action", "no_reply")
|
||||||
# 验证动作
|
# 验证动作
|
||||||
if extracted_action not in self.action_manager.get_available_actions():
|
if extracted_action not in self.action_manager.get_available_actions():
|
||||||
|
# 如果LLM返回了一个此时不该用的动作(因为被临时移除了)
|
||||||
|
# 或者完全无效的动作
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"{self.log_prefix}[Planner] LLM返回了未授权的动作: {extracted_action},使用默认动作no_reply"
|
f"{self.log_prefix}[Planner] LLM返回了当前不可用或无效的动作: {extracted_action},将强制使用 'no_reply'"
|
||||||
)
|
)
|
||||||
action = "no_reply"
|
action = "no_reply"
|
||||||
reasoning = f"LLM返回了未授权的动作: {extracted_action}"
|
reasoning = f"LLM返回了当前不可用的动作: {extracted_action}"
|
||||||
emoji_query = ""
|
emoji_query = ""
|
||||||
llm_error = False # 视为非LLM错误,只是逻辑修正
|
llm_error = False # 视为逻辑修正而非 LLM 错误
|
||||||
|
# --- 检查 'no_reply' 是否也恰好被移除了 (极端情况) ---
|
||||||
|
if "no_reply" not in self.action_manager.get_available_actions():
|
||||||
|
logger.error(f"{self.log_prefix}[Planner] 严重错误:'no_reply' 动作也不可用!无法执行任何动作。")
|
||||||
|
action = "error" # 回退到错误状态
|
||||||
|
reasoning = "无法执行任何有效动作,包括 no_reply"
|
||||||
|
llm_error = True
|
||||||
else:
|
else:
|
||||||
# 动作有效,使用提取的值
|
# 动作有效且可用,使用提取的值
|
||||||
action = extracted_action
|
action = extracted_action
|
||||||
reasoning = arguments.get("reasoning", "未提供理由")
|
reasoning = arguments.get("reasoning", "未提供理由")
|
||||||
emoji_query = arguments.get("emoji_query", "")
|
emoji_query = arguments.get("emoji_query", "")
|
||||||
@@ -837,8 +941,18 @@ class HeartFChatting:
|
|||||||
reasoning = f"验证工具调用失败: {error_msg}"
|
reasoning = f"验证工具调用失败: {error_msg}"
|
||||||
logger.warning(f"{self.log_prefix}[Planner] {reasoning}")
|
logger.warning(f"{self.log_prefix}[Planner] {reasoning}")
|
||||||
else: # not valid_tool_calls
|
else: # not valid_tool_calls
|
||||||
|
# 如果没有有效的工具调用,我们需要检查 'no_reply' 是否是当前唯一可用的动作
|
||||||
|
available_actions = list(self.action_manager.get_available_actions().keys())
|
||||||
|
if available_actions == ["no_reply"]:
|
||||||
|
logger.info(f"{self.log_prefix}[Planner] LLM未返回工具调用,但当前唯一可用动作是 'no_reply',将执行 'no_reply'")
|
||||||
|
action = "no_reply"
|
||||||
|
reasoning = "LLM未返回工具调用,且当前仅 'no_reply' 可用"
|
||||||
|
emoji_query = ""
|
||||||
|
llm_error = False # 视为逻辑选择而非错误
|
||||||
|
else:
|
||||||
reasoning = "LLM未返回有效的工具调用"
|
reasoning = "LLM未返回有效的工具调用"
|
||||||
logger.warning(f"{self.log_prefix}[Planner] {reasoning}")
|
logger.warning(f"{self.log_prefix}[Planner] {reasoning}")
|
||||||
|
# llm_error 保持为 True
|
||||||
# 如果 llm_error 仍然是 True,说明在处理过程中有错误发生
|
# 如果 llm_error 仍然是 True,说明在处理过程中有错误发生
|
||||||
|
|
||||||
except Exception as llm_e:
|
except Exception as llm_e:
|
||||||
@@ -847,6 +961,12 @@ class HeartFChatting:
|
|||||||
action = "error"
|
action = "error"
|
||||||
reasoning = f"Planner内部处理错误: {llm_e}"
|
reasoning = f"Planner内部处理错误: {llm_e}"
|
||||||
llm_error = True
|
llm_error = True
|
||||||
|
# --- 新增:确保动作恢复 ---
|
||||||
|
finally:
|
||||||
|
if actions_to_remove_temporarily: # 只有当确实移除了动作时才需要恢复
|
||||||
|
self.action_manager.restore_actions()
|
||||||
|
logger.debug(f"{self.log_prefix}[Planner] 恢复了原始动作集, 当前可用: {list(self.action_manager.get_available_actions().keys())}")
|
||||||
|
# --- 结束:确保动作恢复 ---
|
||||||
# --- 结束 LLM 决策 --- #
|
# --- 结束 LLM 决策 --- #
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ def init_prompt():
|
|||||||
- 遵守回复原则
|
- 遵守回复原则
|
||||||
- 必须调用工具并包含action和reasoning
|
- 必须调用工具并包含action和reasoning
|
||||||
- 你可以选择文字回复(text_reply),纯表情回复(emoji_reply),不回复(no_reply)
|
- 你可以选择文字回复(text_reply),纯表情回复(emoji_reply),不回复(no_reply)
|
||||||
|
- 并不是所有选择都可用
|
||||||
- 选择text_reply或emoji_reply时必须提供emoji_query
|
- 选择text_reply或emoji_reply时必须提供emoji_query
|
||||||
- 保持回复自然,符合日常聊天习惯""",
|
- 保持回复自然,符合日常聊天习惯""",
|
||||||
"planner_prompt",
|
"planner_prompt",
|
||||||
|
|||||||
Reference in New Issue
Block a user