mirror of https://github.com/Mai-with-u/MaiBot.git
fix(brain): dedupe side-effect actions per message
parent
13bfaf7469
commit
6079898a0e
|
|
@ -1,4 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import random
|
import random
|
||||||
|
|
@ -101,6 +102,9 @@ class BrainChatting:
|
||||||
# 最近一次是否成功进行了 reply,用于选择 BrainPlanner 的 Prompt
|
# 最近一次是否成功进行了 reply,用于选择 BrainPlanner 的 Prompt
|
||||||
self._last_successful_reply: bool = False
|
self._last_successful_reply: bool = False
|
||||||
|
|
||||||
|
# side-effect 动作幂等缓存,避免同一触发消息在短时间内重复执行。
|
||||||
|
self._recent_side_effect_actions: Dict[str, float] = {}
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""检查是否需要启动主循环,如果未激活则启动。"""
|
"""检查是否需要启动主循环,如果未激活则启动。"""
|
||||||
|
|
||||||
|
|
@ -161,6 +165,41 @@ class BrainChatting:
|
||||||
+ (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
|
+ (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _is_side_effect_action(self, action_type: str) -> bool:
|
||||||
|
non_side_effect_actions = {"reply", "wait", "wait_time", "listening", "complete_talk", "no_reply"}
|
||||||
|
return action_type not in non_side_effect_actions
|
||||||
|
|
||||||
|
def _build_side_effect_action_key(self, action_planner_info: ActionPlannerInfo) -> str:
|
||||||
|
action_data = dict(action_planner_info.action_data or {})
|
||||||
|
action_data.pop("loop_start_time", None)
|
||||||
|
|
||||||
|
target_message = action_planner_info.action_message
|
||||||
|
target_message_id = ""
|
||||||
|
if target_message is not None:
|
||||||
|
target_message_id = str(getattr(target_message, "message_id", "") or "")
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"action_type": action_planner_info.action_type,
|
||||||
|
"target_message_id": target_message_id,
|
||||||
|
"action_data": action_data,
|
||||||
|
}
|
||||||
|
return json.dumps(payload, ensure_ascii=False, sort_keys=True, default=str)
|
||||||
|
|
||||||
|
def _cleanup_recent_side_effect_actions(self, now: float) -> None:
|
||||||
|
dedupe_window_sec = 120.0
|
||||||
|
expired_keys = [
|
||||||
|
key
|
||||||
|
for key, ts in self._recent_side_effect_actions.items()
|
||||||
|
if now - ts > dedupe_window_sec
|
||||||
|
]
|
||||||
|
for key in expired_keys:
|
||||||
|
del self._recent_side_effect_actions[key]
|
||||||
|
|
||||||
|
def _is_duplicate_side_effect_action(self, key: str, now: float) -> bool:
|
||||||
|
dedupe_window_sec = 120.0
|
||||||
|
last_ts = self._recent_side_effect_actions.get(key)
|
||||||
|
return last_ts is not None and now - last_ts <= dedupe_window_sec
|
||||||
|
|
||||||
async def _loopbody(self): # sourcery skip: hoist-if-from-if
|
async def _loopbody(self): # sourcery skip: hoist-if-from-if
|
||||||
recent_messages_list = message_api.get_messages_by_time_in_chat(
|
recent_messages_list = message_api.get_messages_by_time_in_chat(
|
||||||
chat_id=self.stream_id,
|
chat_id=self.stream_id,
|
||||||
|
|
@ -580,6 +619,22 @@ class BrainChatting:
|
||||||
"""执行单个动作的通用函数"""
|
"""执行单个动作的通用函数"""
|
||||||
try:
|
try:
|
||||||
with Timer(f"动作{action_planner_info.action_type}", cycle_timers):
|
with Timer(f"动作{action_planner_info.action_type}", cycle_timers):
|
||||||
|
side_effect_action_key = ""
|
||||||
|
if self._is_side_effect_action(action_planner_info.action_type):
|
||||||
|
side_effect_action_key = self._build_side_effect_action_key(action_planner_info)
|
||||||
|
now = time.time()
|
||||||
|
self._cleanup_recent_side_effect_actions(now)
|
||||||
|
if self._is_duplicate_side_effect_action(side_effect_action_key, now):
|
||||||
|
logger.info(
|
||||||
|
f"{self.log_prefix} 跳过重复副作用动作: {action_planner_info.action_type}"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"action_type": action_planner_info.action_type,
|
||||||
|
"success": True,
|
||||||
|
"reply_text": "",
|
||||||
|
"command": "",
|
||||||
|
}
|
||||||
|
|
||||||
if action_planner_info.action_type == "complete_talk":
|
if action_planner_info.action_type == "complete_talk":
|
||||||
# 直接处理complete_talk逻辑,不再通过动作系统
|
# 直接处理complete_talk逻辑,不再通过动作系统
|
||||||
reason = action_planner_info.reasoning or "选择完成对话"
|
reason = action_planner_info.reasoning or "选择完成对话"
|
||||||
|
|
@ -783,6 +838,9 @@ class BrainChatting:
|
||||||
if success and action_planner_info.action_type != "reply":
|
if success and action_planner_info.action_type != "reply":
|
||||||
self._last_successful_reply = False
|
self._last_successful_reply = False
|
||||||
|
|
||||||
|
if success and side_effect_action_key:
|
||||||
|
self._recent_side_effect_actions[side_effect_action_key] = time.time()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"action_type": action_planner_info.action_type,
|
"action_type": action_planner_info.action_type,
|
||||||
"success": success,
|
"success": success,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue