feat:更简洁可靠的auto切换

This commit is contained in:
SengokuCola
2025-06-24 02:04:19 +08:00
parent 653a0fa613
commit 597f5025e5
4 changed files with 98 additions and 130 deletions

View File

@@ -58,6 +58,8 @@ class ActionModifier:
logger.debug(f"{self.log_prefix}开始完整动作修改流程")
# === 第一阶段:传统观察处理 ===
chat_content = None
if observations:
hfc_obs = None
chat_obs = None
@@ -78,7 +80,7 @@ class ActionModifier:
if hfc_obs:
obs = hfc_obs
# 获取适用于FOCUS模式的动作
all_actions = self.action_manager.get_using_actions_for_mode("focus")
all_actions = self.all_actions
action_changes = await self.analyze_loop_actions(obs)
if action_changes["add"] or action_changes["remove"]:
# 合并动作变更
@@ -94,9 +96,8 @@ class ActionModifier:
# 处理ChattingObservation - 传统的类型匹配检查
if chat_obs:
obs = chat_obs
# 检查动作的关联类型
chat_context = get_chat_manager().get_stream(obs.chat_id).context
chat_context = get_chat_manager().get_stream(chat_obs.chat_id).context
type_mismatched_actions = []
for action_name in all_actions.keys():
@@ -128,26 +129,13 @@ class ActionModifier:
f"{self.log_prefix}传统动作修改完成,当前使用动作: {list(self.action_manager.get_using_actions().keys())}"
)
# === chat_mode检查强制移除非auto模式下的exit_focus_chat ===
if global_config.chat.chat_mode != "auto":
if "exit_focus_chat" in self.action_manager.get_using_actions():
self.action_manager.remove_action_from_using("exit_focus_chat")
logger.info(
f"{self.log_prefix}移除动作: exit_focus_chat原因: chat_mode不为auto当前模式: {global_config.chat.chat_mode}"
)
# 注释已移除exit_focus_chat动作现在由no_reply动作处理频率检测退出专注模式
# === 第二阶段:激活类型判定 ===
# 如果提供了聊天上下文,则进行激活类型判定
if chat_content is not None:
logger.debug(f"{self.log_prefix}开始激活类型判定阶段")
# 保存exit_focus_chat动作如果存在
exit_focus_action = None
if "exit_focus_chat" in self.action_manager.get_using_actions():
exit_focus_action = self.action_manager.get_using_actions()["exit_focus_chat"]
self.action_manager.remove_action_from_using("exit_focus_chat")
logger.debug(f"{self.log_prefix}临时移除exit_focus_chat动作以进行激活类型判定")
# 获取当前使用的动作集经过第一阶段处理且适用于FOCUS模式
current_using_actions = self.action_manager.get_using_actions()
all_registered_actions = self.action_manager.get_registered_actions()
@@ -197,16 +185,7 @@ class ActionModifier:
reason = removal_reasons.get(action_name, "未知原因")
logger.info(f"{self.log_prefix}移除动作: {action_name},原因: {reason}")
# 恢复exit_focus_chat动作(如果之前存在)
if exit_focus_action:
# 只有在auto模式下才恢复exit_focus_chat动作
if global_config.chat.chat_mode == "auto":
self.action_manager.add_action_to_using("exit_focus_chat")
logger.debug(f"{self.log_prefix}恢复exit_focus_chat动作")
else:
logger.debug(
f"{self.log_prefix}跳过恢复exit_focus_chat动作原因: chat_mode不为auto当前模式: {global_config.chat.chat_mode}"
)
# 注释:已完全移除exit_focus_chat动作
logger.info(f"{self.log_prefix}激活类型判定完成,最终可用动作: {list(final_activated_actions.keys())}")
@@ -576,30 +555,13 @@ class ActionModifier:
if not recent_cycles:
return result
# 统计no_reply的数量
no_reply_count = 0
reply_sequence = [] # 记录最近的动作序列
for cycle in recent_cycles:
action_result = cycle.loop_plan_info.get("action_result", {})
action_type = action_result.get("action_type", "unknown")
if action_type == "no_reply":
no_reply_count += 1
reply_sequence.append(action_type == "reply")
# 检查no_reply比例
if len(recent_cycles) >= (4 * global_config.chat.exit_focus_threshold) and (
no_reply_count / len(recent_cycles)
) >= (0.7 * global_config.chat.exit_focus_threshold):
if global_config.chat.chat_mode == "auto":
result["add"].append("exit_focus_chat")
result["remove"].append("no_reply")
result["remove"].append("reply")
no_reply_ratio = no_reply_count / len(recent_cycles)
logger.info(
f"{self.log_prefix}检测到高no_reply比例: {no_reply_ratio:.2f}达到退出聊天阈值将添加exit_focus_chat并移除no_reply/reply动作"
)
# 计算连续回复的相关阈值
max_reply_num = int(global_config.focus_chat.consecutive_replies * 3.2)
@@ -613,7 +575,7 @@ class ActionModifier:
last_max_reply_num = reply_sequence[:]
# 详细打印阈值和序列信息,便于调试
logger.debug(
logger.info(
f"连续回复阈值: max={max_reply_num}, sec={sec_thres_reply_num}, one={one_thres_reply_num}"
f"最近reply序列: {last_max_reply_num}"
)

View File

@@ -563,21 +563,21 @@ class NormalChat:
self.interest_dict.pop(msg_id, None)
# 创建并行任务列表
tasks = []
coroutines = []
for msg_id, (message, interest_value, is_mentioned) in items_to_process:
task = process_single_message(msg_id, message, interest_value, is_mentioned)
tasks.append(task)
coroutine = process_single_message(msg_id, message, interest_value, is_mentioned)
coroutines.append(coroutine)
# 并行执行所有任务,限制并发数量避免资源过度消耗
if tasks:
if coroutines:
# 使用信号量控制并发数最多同时处理5个消息
semaphore = asyncio.Semaphore(5)
async def limited_process(task, sem):
async def limited_process(coroutine, sem):
async with sem:
await task
await coroutine
limited_tasks = [limited_process(task, semaphore) for task in tasks]
limited_tasks = [limited_process(coroutine, semaphore) for coroutine in coroutines]
await asyncio.gather(*limited_tasks, return_exceptions=True)
except asyncio.CancelledError:

View File

@@ -39,16 +39,6 @@
"type": "action",
"name": "emoji",
"description": "发送表情包辅助表达情绪"
},
{
"type": "action",
"name": "change_to_focus_chat",
"description": "切换到专注聊天,从普通模式切换到专注模式"
},
{
"type": "action",
"name": "exit_focus_chat",
"description": "退出专注聊天,从专注模式切换到普通模式"
}
]
}

View File

@@ -147,9 +147,10 @@ class NoReplyAction(BaseAction):
# 跳过LLM判断的配置
_skip_judge_when_tired = True
_skip_probability_light = 0.2 # 轻度疲惫跳过概率
_skip_probability_medium = 0.4 # 中度疲惫跳过概率
_skip_probability_heavy = 0.6 # 重度疲惫跳过概率
_skip_probability = 0.5
# 新增:回复频率退出专注模式的配置
_frequency_check_window = 600 # 频率检查窗口时间(秒)
# 动作参数定义
action_parameters = {"reason": "不回复的原因"}
@@ -214,6 +215,20 @@ class NoReplyAction(BaseAction):
)
return True, exit_reason
# **新增**:检查回复频率,决定是否退出专注模式
should_exit_focus = await self._check_frequency_and_exit_focus(current_time)
if should_exit_focus:
logger.info(f"{self.log_prefix} 检测到回复频率过高,退出专注模式")
# 标记退出专注模式
self.action_data["_system_command"] = "stop_focus_chat"
exit_reason = f"{global_config.bot.nickname}(你)发现自己回复太频繁了,决定退出专注模式,稍作休息"
await self.store_action_info(
action_build_into_prompt=True,
action_prompt_display=exit_reason,
action_done=True,
)
return True, exit_reason
# 检查是否有新消息
new_message_count = message_api.count_new_messages(
chat_id=self.chat_id, start_time=start_time, end_time=current_time
@@ -314,13 +329,14 @@ class NoReplyAction(BaseAction):
over_count = bot_message_count - talk_frequency_threshold
# 根据超过的数量设置不同的提示词和跳过概率
skip_probability = 0
if over_count <= 3:
frequency_block = "你感觉稍微有些累,回复的有点多了。\n"
elif over_count <= 5:
frequency_block = "你今天说话比较多,感觉有点疲惫,想要稍微休息一下。\n"
else:
frequency_block = "你发现自己说话太多了,感觉很累,想要安静一会儿,除非有重要的事情否则不想回复。\n"
skip_probability = self._skip_probability_heavy
skip_probability = self._skip_probability
# 根据配置和概率决定是否跳过LLM判断
if self._skip_judge_when_tired and random.random() < skip_probability:
@@ -464,6 +480,64 @@ class NoReplyAction(BaseAction):
)
return False, f"不回复动作执行失败: {e}"
async def _check_frequency_and_exit_focus(self, current_time: float) -> bool:
"""检查回复频率,决定是否退出专注模式
Args:
current_time: 当前时间戳
Returns:
bool: 是否应该退出专注模式
"""
try:
# 只在auto模式下进行频率检查
if global_config.chat.chat_mode != "auto":
return False
# 获取检查窗口内的所有消息
window_start_time = current_time - self._frequency_check_window
all_messages = message_api.get_messages_by_time_in_chat(
chat_id=self.chat_id,
start_time=window_start_time,
end_time=current_time,
)
if not all_messages:
return False
# 统计bot自己的回复数量
bot_message_count = 0
user_id = global_config.bot.qq_account
for message in all_messages:
sender_id = message.get("user_id", "")
if sender_id == user_id:
bot_message_count += 1
# 计算当前回复频率(每分钟回复数)
window_minutes = self._frequency_check_window / 60
current_frequency = bot_message_count / window_minutes
# 计算阈值频率:使用 exit_focus_threshold * 1.5
threshold_multiplier = global_config.chat.exit_focus_threshold * 1.5
threshold_frequency = global_config.chat.talk_frequency * threshold_multiplier
# 判断是否超过阈值
if current_frequency > threshold_frequency:
logger.info(
f"{self.log_prefix} 回复频率检查:当前频率 {current_frequency:.2f}/分钟,超过阈值 {threshold_frequency:.2f}/分钟 (exit_threshold={global_config.chat.exit_focus_threshold} * 1.5),准备退出专注模式"
)
return True
else:
logger.debug(
f"{self.log_prefix} 回复频率检查:当前频率 {current_frequency:.2f}/分钟,未超过阈值 {threshold_frequency:.2f}/分钟 (exit_threshold={global_config.chat.exit_focus_threshold} * 1.5)"
)
return False
except Exception as e:
logger.error(f"{self.log_prefix} 检查回复频率时出错: {e}")
return False
def _parse_llm_judge_response(self, response: str) -> tuple[str, str]:
"""解析LLM判断响应使用JSON格式提取判断结果和理由
@@ -596,66 +670,6 @@ class EmojiAction(BaseAction):
return False, f"表情发送失败: {str(e)}"
class ExitFocusChatAction(BaseAction):
"""退出专注聊天动作 - 从专注模式切换到普通模式"""
# 激活设置
focus_activation_type = ActionActivationType.NEVER
normal_activation_type = ActionActivationType.NEVER
mode_enable = ChatMode.FOCUS
parallel_action = False
# 动作基本信息
action_name = "exit_focus_chat"
action_description = "退出专注聊天,从专注模式切换到普通模式"
# LLM判断提示词
llm_judge_prompt = """
判定是否需要退出专注聊天的条件:
1. 很长时间没有回复,应该退出专注聊天
2. 当前内容不需要持续专注关注
3. 聊天内容已经完成,话题结束
请回答""""
"""
# 动作参数定义
action_parameters = {}
# 动作使用场景
action_require = [
"很长时间没有回复,你决定退出专注聊天",
"当前内容不需要持续专注关注,你决定退出专注聊天",
"聊天内容已经完成,你决定退出专注聊天",
]
# 关联类型
associated_types = []
async def execute(self) -> Tuple[bool, str]:
"""执行退出专注聊天动作"""
logger.info(f"{self.log_prefix} 决定退出专注聊天: {self.reasoning}")
try:
# 标记状态切换请求
self._mark_state_change()
# 重置NoReplyAction的连续计数器
NoReplyAction.reset_consecutive_count()
status_message = "决定退出专注聊天模式"
return True, status_message
except Exception as e:
logger.error(f"{self.log_prefix} 退出专注聊天动作执行失败: {e}")
return False, f"退出专注聊天失败: {str(e)}"
def _mark_state_change(self):
"""标记状态切换请求"""
# 通过action_data传递状态切换命令
self.action_data["_system_command"] = "stop_focus_chat"
logger.info(f"{self.log_prefix} 已标记状态切换命令: stop_focus_chat")
@register_plugin
class CoreActionsPlugin(BasePlugin):
@@ -757,6 +771,10 @@ class CoreActionsPlugin(BasePlugin):
skip_probability_heavy = self.get_config("no_reply.skip_probability_heavy", 0.6)
NoReplyAction._skip_probability_heavy = skip_probability_heavy
# 新增:频率检测相关配置
frequency_check_window = self.get_config("no_reply.frequency_check_window", 600)
NoReplyAction._frequency_check_window = frequency_check_window
# --- 根据配置注册组件 ---
components = []
if self.get_config("components.enable_reply", True):
@@ -765,14 +783,10 @@ class CoreActionsPlugin(BasePlugin):
components.append((NoReplyAction.get_action_info(), NoReplyAction))
if self.get_config("components.enable_emoji", True):
components.append((EmojiAction.get_action_info(), EmojiAction))
if self.get_config("components.enable_exit_focus", True):
components.append((ExitFocusChatAction.get_action_info(), ExitFocusChatAction))
# components.append((DeepReplyAction.get_action_info(), DeepReplyAction))
return components
# class DeepReplyAction(BaseAction):
# """回复动作 - 参与聊天回复"""
@@ -895,3 +909,5 @@ class CoreActionsPlugin(BasePlugin):
# data = reply[1]
# reply_text += data
# return reply_text