迁移:a247be0(ref:彻底合并normal和focus,完全基于planner决定target message)

This commit is contained in:
Windpicker-owo
2025-09-02 16:15:33 +08:00
parent 780cfbd7df
commit 8b82f4f5aa
8 changed files with 270 additions and 347 deletions

View File

@@ -49,15 +49,21 @@ class CycleProcessor:
action_message, action_message,
cycle_timers: Dict[str, float], cycle_timers: Dict[str, float],
thinking_id, thinking_id,
plan_result, actions,
) -> Tuple[Dict[str, Any], str, Dict[str, float]]: ) -> Tuple[Dict[str, Any], str, Dict[str, float]]:
with Timer("回复发送", cycle_timers): with Timer("回复发送", cycle_timers):
reply_text = await self.response_handler.send_response(response_set, reply_to_str, loop_start_time, action_message) reply_text = await self.response_handler.send_response(response_set, reply_to_str, loop_start_time, action_message)
# 存储reply action信息 # 存储reply action信息
person_info_manager = get_person_info_manager() person_info_manager = get_person_info_manager()
# 获取 platform如果不存在则从 chat_stream 获取,如果还是 None 则使用默认值
platform = action_message.get("chat_info_platform")
if platform is None:
platform = getattr(self.chat_stream, "platform", "unknown")
person_id = person_info_manager.get_person_id( person_id = person_info_manager.get_person_id(
action_message.get("chat_info_platform", ""), platform,
action_message.get("user_id", ""), action_message.get("user_id", ""),
) )
person_name = await person_info_manager.get_value(person_id, "person_name") person_name = await person_info_manager.get_value(person_id, "person_name")
@@ -76,7 +82,7 @@ class CycleProcessor:
# 构建循环信息 # 构建循环信息
loop_info: Dict[str, Any] = { loop_info: Dict[str, Any] = {
"loop_plan_info": { "loop_plan_info": {
"action_result": plan_result.get("action_result", {}), "action_result": actions,
}, },
"loop_action_info": { "loop_action_info": {
"action_taken": True, "action_taken": True,
@@ -88,12 +94,12 @@ class CycleProcessor:
return loop_info, reply_text, cycle_timers return loop_info, reply_text, cycle_timers
async def observe(self, message_data: Optional[Dict[str, Any]] = None) -> bool: async def observe(self,interest_value:float = 0.0) -> bool:
""" """
观察和处理单次思考循环的核心方法 观察和处理单次思考循环的核心方法
Args: Args:
message_data: 可选的消息数据字典,包含用户消息、平台信息等 interest_value: 兴趣值
Returns: Returns:
bool: 处理是否成功 bool: 处理是否成功
@@ -105,13 +111,40 @@ class CycleProcessor:
- 执行动作规划或直接回复 - 执行动作规划或直接回复
- 根据动作类型分发到相应的处理方法 - 根据动作类型分发到相应的处理方法
""" """
if not message_data: action_type = "no_action"
message_data = {} reply_text = "" # 初始化reply_text变量避免UnboundLocalError
reply_to_str = "" # 初始化reply_to_str变量
# 根据interest_value计算概率决定使用哪种planner模式
# interest_value越高越倾向于使用Normal模式
import random
import math
# 使用sigmoid函数将interest_value转换为概率
# 当interest_value为0时概率接近0使用Focus模式
# 当interest_value很高时概率接近1使用Normal模式
def calculate_normal_mode_probability(interest_val: float) -> float:
# 使用sigmoid函数调整参数使概率分布更合理
# 当interest_value = 0时概率约为0.1
# 当interest_value = 1时概率约为0.5
# 当interest_value = 2时概率约为0.8
# 当interest_value = 3时概率约为0.95
k = 2.0 # 控制曲线陡峭程度
x0 = 1.0 # 控制曲线中心点
return 1.0 / (1.0 + math.exp(-k * (interest_val - x0)))
normal_mode_probability = calculate_normal_mode_probability(interest_value)
# 根据概率决定使用哪种模式
if random.random() < normal_mode_probability:
mode = ChatMode.NORMAL
logger.info(f"{self.log_prefix} 基于兴趣值 {interest_value:.2f},概率 {normal_mode_probability:.2f}选择Normal planner模式")
else:
mode = ChatMode.FOCUS
logger.info(f"{self.log_prefix} 基于兴趣值 {interest_value:.2f},概率 {normal_mode_probability:.2f}选择Focus planner模式")
cycle_timers, thinking_id = self.cycle_tracker.start_cycle() cycle_timers, thinking_id = self.cycle_tracker.start_cycle()
logger.info( logger.info(f"{self.log_prefix} 开始第{self.context.cycle_counter}次思考")
f"{self.context.log_prefix} 开始第{self.context.cycle_counter}次思考[模式:{self.context.loop_mode}]"
)
if ENABLE_S4U: if ENABLE_S4U:
await send_typing() await send_typing()
@@ -127,75 +160,26 @@ class CycleProcessor:
logger.error(f"{self.context.log_prefix} 动作修改失败: {e}") logger.error(f"{self.context.log_prefix} 动作修改失败: {e}")
available_actions = {} available_actions = {}
is_mentioned_bot = message_data.get("is_mentioned", False) # 执行planner
at_bot_mentioned = (global_config.chat.mentioned_bot_inevitable_reply and is_mentioned_bot) or ( planner_info = self.action_planner.get_necessary_info()
global_config.chat.at_bot_inevitable_reply and is_mentioned_bot prompt_info = await self.action_planner.build_planner_prompt(
is_group_chat=planner_info[0],
chat_target_info=planner_info[1],
current_available_actions=planner_info[2],
) )
# 专注模式下提及bot必定回复
if self.context.loop_mode == ChatMode.FOCUS and at_bot_mentioned and "no_reply" in available_actions:
available_actions = {k: v for k, v in available_actions.items() if k != "no_reply"}
# 检查是否在normal模式下没有可用动作除了reply相关动作
skip_planner = False
if self.context.loop_mode == ChatMode.NORMAL:
non_reply_actions = {
k: v for k, v in available_actions.items() if k not in ["reply", "no_reply", "no_action"]
}
if not non_reply_actions:
skip_planner = True
logger.info(f"Normal模式下没有可用动作直接回复")
plan_result = self._get_direct_reply_plan(loop_start_time)
target_message = message_data
# Focus模式
if not skip_planner:
from src.plugin_system.core.event_manager import event_manager from src.plugin_system.core.event_manager import event_manager
from src.plugin_system.base.component_types import EventType from src.plugin_system import EventType
# 触发 ON_PLAN 事件 result = await event_manager.trigger_event(EventType.ON_PLAN,plugin_name="SYSTEM", stream_id=self.context.chat_stream)
result = await event_manager.trigger_event( if not result.all_continue_process():
EventType.ON_PLAN, plugin_name="SYSTEM", stream_id=self.context.stream_id raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于规划前中断了内容生成")
)
if result and not result.all_continue_process():
return
with Timer("规划器", cycle_timers): with Timer("规划器", cycle_timers):
plan_result, target_message = await self.action_planner.plan(mode=self.context.loop_mode) actions, _= await self.action_planner.plan(
mode=mode,
action_result = plan_result.get("action_result", {}) loop_start_time=loop_start_time,
available_actions=available_actions,
action_type = action_result.get("action_type", "error") )
action_data = action_result.get("action_data", {})
reasoning = action_result.get("reasoning", "未提供理由")
is_parallel = action_result.get("is_parallel", True)
action_data["loop_start_time"] = loop_start_time
action_message = message_data or target_message
# is_private_chat = self.context.chat_stream.group_info is None if self.context.chat_stream else False
# 重构后的动作处理逻辑:先汇总所有动作,然后并行执行
actions = []
# 1. 添加Planner取得的动作
actions.append({
"action_type": action_type,
"reasoning": reasoning,
"action_data": action_data,
"action_message": action_message,
"available_actions": available_actions # 添加这个字段
})
# 2. 如果不是reply动作且需要并行执行额外添加reply动作
if action_type != "reply" and is_parallel:
actions.append({
"action_type": "reply",
"action_message": action_message,
"available_actions": available_actions
})
async def execute_action(action_info): async def execute_action(action_info):
"""执行单个动作的通用函数""" """执行单个动作的通用函数"""
@@ -242,7 +226,6 @@ class CycleProcessor:
else: else:
# 执行回复动作 # 执行回复动作
reply_to_str = await self._build_reply_to_str(action_info["action_message"]) reply_to_str = await self._build_reply_to_str(action_info["action_message"])
request_type = "chat.replyer"
# 生成回复 # 生成回复
gather_timeout = global_config.chat.thinking_timeout gather_timeout = global_config.chat.thinking_timeout
@@ -252,7 +235,7 @@ class CycleProcessor:
message_data=action_info["action_message"], message_data=action_info["action_message"],
available_actions=action_info["available_actions"], available_actions=action_info["available_actions"],
reply_to=reply_to_str, reply_to=reply_to_str,
request_type=request_type, request_type="chat.replyer",
), ),
timeout=gather_timeout timeout=gather_timeout
) )
@@ -291,7 +274,7 @@ class CycleProcessor:
action_info["action_message"], action_info["action_message"],
cycle_timers, cycle_timers,
thinking_id, thinking_id,
plan_result, actions,
) )
return { return {
"action_type": "reply", "action_type": "reply",
@@ -301,6 +284,7 @@ class CycleProcessor:
} }
except Exception as e: except Exception as e:
logger.error(f"{self.log_prefix} 执行动作时出错: {e}") logger.error(f"{self.log_prefix} 执行动作时出错: {e}")
logger.error(f"{self.log_prefix} 错误信息: {traceback.format_exc()}")
return { return {
"action_type": action_info["action_type"], "action_type": action_info["action_type"],
"success": False, "success": False,
@@ -310,6 +294,7 @@ class CycleProcessor:
} }
# 创建所有动作的后台任务 # 创建所有动作的后台任务
action_tasks = [asyncio.create_task(execute_action(action)) for action in actions] action_tasks = [asyncio.create_task(execute_action(action)) for action in actions]
# 并行执行所有任务 # 并行执行所有任务
@@ -356,7 +341,7 @@ class CycleProcessor:
# 没有回复信息构建纯动作的loop_info # 没有回复信息构建纯动作的loop_info
loop_info = { loop_info = {
"loop_plan_info": { "loop_plan_info": {
"action_result": plan_result.get("action_result", {}), "action_result": actions,
}, },
"loop_action_info": { "loop_action_info": {
"action_taken": action_success, "action_taken": action_success,
@@ -367,29 +352,21 @@ class CycleProcessor:
} }
reply_text = action_reply_text reply_text = action_reply_text
self.last_action = action_type
if ENABLE_S4U: if ENABLE_S4U:
await stop_typing() await stop_typing()
self.context.chat_instance.cycle_tracker.end_cycle(loop_info, cycle_timers) self.context.chat_instance.cycle_tracker.end_cycle(loop_info, cycle_timers)
self.context.chat_instance.cycle_tracker.print_cycle_info(cycle_timers) self.context.chat_instance.cycle_tracker.print_cycle_info(cycle_timers)
if self.context.loop_mode == ChatMode.NORMAL: # await self.willing_manager.after_generate_reply_handle(message_data.get("message_id", ""))
await self.context.chat_instance.willing_manager.after_generate_reply_handle(message_data.get("message_id", "")) action_type = actions[0]["action_type"] if actions else "no_action"
# 管理no_reply计数器当执行了非no_reply动作时重置计数器 # 管理no_reply计数器当执行了非no_reply动作时重置计数器
if action_type != "no_reply" and action_type != "no_action": if action_type != "no_reply":
# no_reply逻辑已集成到heartFC_chat.py中直接重置计数器 # no_reply逻辑已集成到heartFC_chat.py中直接重置计数器
self.context.chat_instance.recent_interest_records.clear() self.context.chat_instance.recent_interest_records.clear()
self.context.no_reply_consecutive = 0 self.context.no_reply_consecutive = 0
logger.info(f"{self.log_prefix} 执行了{action_type}动作重置no_reply计数器") logger.debug(f"{self.log_prefix} 执行了{action_type}动作重置no_reply计数器")
return True return True
elif action_type == "no_action":
# 当执行回复动作时也重置no_reply计数
self.context.chat_instance.recent_interest_records.clear()
self.context.no_reply_consecutive = 0
logger.info(f"{self.log_prefix} 执行了回复动作重置no_reply计数器")
if action_type == "no_reply": if action_type == "no_reply":
self.context.no_reply_consecutive += 1 self.context.no_reply_consecutive += 1

View File

@@ -89,11 +89,27 @@ class CycleTracker:
formatted_time = f"{elapsed * 1000:.2f}毫秒" if elapsed < 1 else f"{elapsed:.2f}" formatted_time = f"{elapsed * 1000:.2f}毫秒" if elapsed < 1 else f"{elapsed:.2f}"
timer_strings.append(f"{name}: {formatted_time}") timer_strings.append(f"{name}: {formatted_time}")
# 获取动作类型,兼容新旧格式
action_type = "未知动作"
if hasattr(self, '_current_cycle_detail') and self._current_cycle_detail:
loop_plan_info = self._current_cycle_detail.loop_plan_info
if isinstance(loop_plan_info, dict):
action_result = loop_plan_info.get('action_result', {})
if isinstance(action_result, dict):
# 旧格式action_result是字典
action_type = action_result.get('action_type', '未知动作')
elif isinstance(action_result, list) and action_result:
# 新格式action_result是actions列表
action_type = action_result[0].get('action_type', '未知动作')
elif isinstance(loop_plan_info, list) and loop_plan_info:
# 直接是actions列表的情况
action_type = loop_plan_info[0].get('action_type', '未知动作')
if self.context.current_cycle_detail.end_time and self.context.current_cycle_detail.start_time: if self.context.current_cycle_detail.end_time and self.context.current_cycle_detail.start_time:
duration = self.context.current_cycle_detail.end_time - self.context.current_cycle_detail.start_time duration = self.context.current_cycle_detail.end_time - self.context.current_cycle_detail.start_time
logger.info( logger.info(
f"{self.context.log_prefix}{self.context.current_cycle_detail.cycle_id}次思考," f"{self.context.log_prefix}{self.context.current_cycle_detail.cycle_id}次思考,"
f"耗时: {duration:.1f}秒, " f"耗时: {duration:.1f}秒, "
f"选择动作: {self.context.current_cycle_detail.loop_plan_info.get('action_result', {}).get('action_type', '未知动作')}" f"选择动作: {action_type}"
+ (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
) )

View File

@@ -107,20 +107,11 @@ class EnergyManager:
else: else:
# 清醒时:处理能量衰减 # 清醒时:处理能量衰减
is_group_chat = self.context.chat_stream.group_info is not None is_group_chat = self.context.chat_stream.group_info is not None
if is_group_chat and global_config.chat.group_chat_mode != "auto": if is_group_chat:
if global_config.chat.group_chat_mode == "focus": self.context.energy_value = 25
self.context.loop_mode = ChatMode.FOCUS
self.context.energy_value = 35
elif global_config.chat.group_chat_mode == "normal":
self.context.loop_mode = ChatMode.NORMAL
self.context.energy_value = 15
continue
if self.context.loop_mode == ChatMode.NORMAL: await asyncio.sleep(12)
self.context.energy_value -= 0.3 self.context.energy_value -= 0.5
self.context.energy_value = max(self.context.energy_value, 0.3)
if self.context.loop_mode == ChatMode.FOCUS:
self.context.energy_value -= 0.6
self.context.energy_value = max(self.context.energy_value, 0.3) self.context.energy_value = max(self.context.energy_value, 0.3)
self._log_energy_change("能量值衰减") self._log_energy_change("能量值衰减")

View File

@@ -77,12 +77,7 @@ class HeartFChatting:
""" """
is_group_chat = self.context.chat_stream.group_info is not None if self.context.chat_stream else False is_group_chat = self.context.chat_stream.group_info is not None if self.context.chat_stream else False
if is_group_chat and global_config.chat.group_chat_mode != "auto": if is_group_chat and global_config.chat.group_chat_mode != "auto":
if global_config.chat.group_chat_mode == "focus": self.context.energy_value = 25
self.context.loop_mode = ChatMode.FOCUS
self.context.energy_value = 35
elif global_config.chat.group_chat_mode == "normal":
self.context.loop_mode = ChatMode.NORMAL
self.context.energy_value = 15
async def start(self): async def start(self):
""" """
@@ -241,20 +236,29 @@ class HeartFChatting:
if current_sleep_state == SleepState.SLEEPING: if current_sleep_state == SleepState.SLEEPING:
# 只有在纯粹的 SLEEPING 状态下才跳过消息处理 # 只有在纯粹的 SLEEPING 状态下才跳过消息处理
return has_new_messages return True
if current_sleep_state == SleepState.WOKEN_UP: if current_sleep_state == SleepState.WOKEN_UP:
logger.info(f"{self.context.log_prefix} 从睡眠中被唤醒,将处理积压的消息。") logger.info(f"{self.context.log_prefix} 从睡眠中被唤醒,将处理积压的消息。")
# 根据聊天模式处理新消息 # 根据聊天模式处理新消息
# 统一使用 _should_process_messages 判断是否应该处理 # 统一使用 _should_process_messages 判断是否应该处理
if not self._should_process_messages(recent_messages if has_new_messages else None): should_process,interest_value = await self._should_process_messages(recent_messages if has_new_messages else None)
if should_process:
earliest_message_data = recent_messages[0]
self.last_read_time = earliest_message_data.get("time")
await self.cycle_processor.observe(interest_value = interest_value)
else:
# Normal模式消息数量不足等待
await asyncio.sleep(0.5)
return True
if not await self._should_process_messages(recent_messages if has_new_messages else None):
return has_new_messages return has_new_messages
if self.context.loop_mode == ChatMode.FOCUS:
# 处理新消息 # 处理新消息
for message in recent_messages: for message in recent_messages:
await self.cycle_processor.observe(message) await self.cycle_processor.observe(interest_value = interest_value)
# 如果成功观察,增加能量值 # 如果成功观察,增加能量值
if has_new_messages: if has_new_messages:
@@ -262,20 +266,11 @@ class HeartFChatting:
logger.info(f"{self.context.log_prefix} 能量值增加,当前能量值:{self.context.energy_value:.1f}") logger.info(f"{self.context.log_prefix} 能量值增加,当前能量值:{self.context.energy_value:.1f}")
self._check_focus_exit() self._check_focus_exit()
elif self.context.loop_mode == ChatMode.NORMAL:
self._check_focus_entry(len(recent_messages))
# 检查是否有足够的新消息触发处理
if new_message_count >= self.context.focus_energy:
earliest_messages_data = recent_messages[0]
self.context.last_read_time = earliest_messages_data.get("time")
for message in recent_messages:
await self.normal_mode_handler.handle_message(message)
else: else:
# 无新消息时,只进行模式检查,不进行思考循环 # 无新消息时,只进行模式检查,不进行思考循环
if self.context.loop_mode == ChatMode.FOCUS:
self._check_focus_exit() self._check_focus_exit()
elif self.context.loop_mode == ChatMode.NORMAL:
self._check_focus_entry(0) # 传入0表示无新消息
# 更新上一帧的睡眠状态 # 更新上一帧的睡眠状态
self.context.was_sleeping = is_sleeping self.context.was_sleeping = is_sleeping
@@ -319,7 +314,6 @@ class HeartFChatting:
if self.context.energy_value <= 1: # 如果能量值小于等于1非强制情况 if self.context.energy_value <= 1: # 如果能量值小于等于1非强制情况
self.context.energy_value = 1 # 将能量值设置为1 self.context.energy_value = 1 # 将能量值设置为1
self.context.loop_mode = ChatMode.NORMAL # 切换到普通模式
def _check_focus_entry(self, new_message_count: int): def _check_focus_entry(self, new_message_count: int):
""" """
@@ -339,7 +333,6 @@ class HeartFChatting:
is_group_chat = not is_private_chat is_group_chat = not is_private_chat
if global_config.chat.force_focus_private and is_private_chat: if global_config.chat.force_focus_private and is_private_chat:
self.context.loop_mode = ChatMode.FOCUS
self.context.energy_value = 10 self.context.energy_value = 10
return return
@@ -350,15 +343,11 @@ class HeartFChatting:
if new_message_count > 3 / pow( if new_message_count > 3 / pow(
global_config.chat.focus_value, 0.5 global_config.chat.focus_value, 0.5
): # 如果新消息数超过阈值(基于专注值计算) ): # 如果新消息数超过阈值(基于专注值计算)
self.context.loop_mode = ChatMode.FOCUS # 进入专注模式
self.context.energy_value = ( self.context.energy_value = (
10 + (new_message_count / (3 / pow(global_config.chat.focus_value, 0.5))) * 10 10 + (new_message_count / (3 / pow(global_config.chat.focus_value, 0.5))) * 10
) # 根据消息数量计算能量值 ) # 根据消息数量计算能量值
return # 返回,不再检查其他条件 return # 返回,不再检查其他条件
if self.context.energy_value >= 30: # 如果能量值达到或超过30
self.context.loop_mode = ChatMode.FOCUS # 进入专注模式
def _handle_wakeup_messages(self, messages): def _handle_wakeup_messages(self, messages):
""" """
处理休眠状态下的消息,累积唤醒度 处理休眠状态下的消息,累积唤醒度
@@ -419,19 +408,57 @@ class HeartFChatting:
logger.info(f"{self.context.log_prefix} 兴趣度充足") logger.info(f"{self.context.log_prefix} 兴趣度充足")
self.context.focus_energy = 1 self.context.focus_energy = 1
def _should_process_messages(self, messages: List[Dict[str, Any]] = None) -> bool: async def _should_process_messages(self, new_message: List[Dict[str, Any]]) -> tuple[bool,float]:
""" """
统一判断是否应该处理消息的函数 统一判断是否应该处理消息的函数
根据当前循环模式和消息内容决定是否继续处理 根据当前循环模式和消息内容决定是否继续处理
""" """
if self.context.loop_mode == ChatMode.FOCUS: new_message_count = len(new_message)
if self.context.last_action == "no_reply": talk_frequency = global_config.chat.get_current_talk_frequency(self.context.chat_stream.stream_id)
if messages: modified_exit_count_threshold = self.context.focus_energy / talk_frequency
return self._execute_no_reply(messages)
return False
return True
return True if new_message_count >= modified_exit_count_threshold:
# 记录兴趣度到列表
total_interest = 0.0
for msg_dict in new_message:
interest_value = msg_dict.get("interest_value", 0.0)
if msg_dict.get("processed_plain_text", ""):
total_interest += interest_value
self.recent_interest_records.append(total_interest)
logger.info(
f"{self.context.log_prefix} 累计消息数量达到{new_message_count}条(>{modified_exit_count_threshold}),结束等待"
)
return True,total_interest/new_message_count
# 检查累计兴趣值
if new_message_count > 0:
accumulated_interest = 0.0
for msg_dict in new_message:
text = msg_dict.get("processed_plain_text", "")
interest_value = msg_dict.get("interest_value", 0.0)
if text:
accumulated_interest += interest_value
# 只在兴趣值变化时输出log
if not hasattr(self, "_last_accumulated_interest") or accumulated_interest != self._last_accumulated_interest:
logger.info(f"{self.context.log_prefix} breaking形式当前累计兴趣值: {accumulated_interest:.2f}, 当前聊天频率: {talk_frequency:.2f}")
self._last_accumulated_interest = accumulated_interest
if accumulated_interest >= 3 / talk_frequency:
# 记录兴趣度到列表
self.recent_interest_records.append(accumulated_interest)
logger.info(
f"{self.context.log_prefix} 累计兴趣值达到{accumulated_interest:.2f}(>{5 / talk_frequency}),结束等待"
)
return True,accumulated_interest/new_message_count
# 每10秒输出一次等待状态
if int(time.time() - self.last_read_time) > 0 and int(time.time() - self.last_read_time) % 10 == 0:
logger.info(
f"{self.context.log_prefix} 已等待{time.time() - self.last_read_time:.0f}秒,累计{new_message_count}条消息,继续等待..."
)
await asyncio.sleep(0.5)
return False,0.0
async def _execute_no_reply(self, new_message: List[Dict[str, Any]]) -> bool: async def _execute_no_reply(self, new_message: List[Dict[str, Any]]) -> bool:
"""执行breaking形式的no_reply原有逻辑""" """执行breaking形式的no_reply原有逻辑"""

View File

@@ -41,10 +41,6 @@ class HfcContext:
self.relationship_builder: Optional[RelationshipBuilder] = None self.relationship_builder: Optional[RelationshipBuilder] = None
self.expression_learner: Optional[ExpressionLearner] = None self.expression_learner: Optional[ExpressionLearner] = None
self.loop_mode = ChatMode.NORMAL
self.last_action = "no_action"
self.energy_value = self.chat_stream.energy_value self.energy_value = self.chat_stream.energy_value
self.sleep_pressure = self.chat_stream.sleep_pressure self.sleep_pressure = self.chat_stream.sleep_pressure
self.was_sleeping = False # 用于检测睡眠状态的切换 self.was_sleeping = False # 用于检测睡眠状态的切换

View File

@@ -162,7 +162,10 @@ class ActionPlanner:
return "回忆时出现了一些问题。" return "回忆时出现了一些问题。"
async def _build_action_options( async def _build_action_options(
self, current_available_actions: Dict[str, ActionInfo], mode: ChatMode, target_prompt: str = "" self,
current_available_actions: Dict[str, ActionInfo],
mode: ChatMode,
target_prompt: str = "",
) -> str: ) -> str:
""" """
构建动作选项 构建动作选项
@@ -185,12 +188,15 @@ class ActionPlanner:
param_text = "" param_text = ""
if action_info.action_parameters: if action_info.action_parameters:
param_text = "\n" + "\n".join( param_text = "\n" + "\n".join(
f' "{p_name}":"{p_desc}"' for p_name, p_desc in action_info.action_parameters.items() f' "{p_name}":"{p_desc}"'
for p_name, p_desc in action_info.action_parameters.items()
) )
require_text = "\n".join(f"- {req}" for req in action_info.action_require) require_text = "\n".join(f"- {req}" for req in action_info.action_require)
using_action_prompt = await global_prompt_manager.get_prompt_async("action_prompt") using_action_prompt = await global_prompt_manager.get_prompt_async(
"action_prompt"
)
action_options_block += using_action_prompt.format( action_options_block += using_action_prompt.format(
action_name=action_name, action_name=action_name,
action_description=action_info.description, action_description=action_info.description,
@@ -200,7 +206,10 @@ class ActionPlanner:
) )
return action_options_block return action_options_block
def find_message_by_id(self, message_id: str, message_id_list: list) -> Optional[Dict[str, Any]]: def find_message_by_id(
self, message_id: str, message_id_list: list
) -> Optional[Dict[str, Any]]:
# sourcery skip: use-next
""" """
根据message_id从message_id_list中查找对应的原始消息 根据message_id从message_id_list中查找对应的原始消息
@@ -231,7 +240,12 @@ class ActionPlanner:
# 假设消息列表是按时间顺序排列的,最后一个是最新的 # 假设消息列表是按时间顺序排列的,最后一个是最新的
return message_id_list[-1].get("message") return message_id_list[-1].get("message")
async def plan(self, mode: ChatMode = ChatMode.FOCUS) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]]]: async def plan(
self,
mode: ChatMode = ChatMode.FOCUS,
loop_start_time:float = 0.0,
available_actions: Optional[Dict[str, ActionInfo]] = None,
) -> Tuple[List[Dict[str, Any]], Optional[Dict[str, Any]]]:
""" """
规划器 (Planner): 使用LLM根据上下文决定做出什么动作。 规划器 (Planner): 使用LLM根据上下文决定做出什么动作。
""" """
@@ -297,31 +311,22 @@ class ActionPlanner:
# 将所有其他属性添加到action_data # 将所有其他属性添加到action_data
for key, value in parsed_json.items(): for key, value in parsed_json.items():
if key not in ["action", "reasoning"]: if key not in ["action", "reason"]:
action_data[key] = value action_data[key] = value
# 非no_reply动作需要target_message_id # 在FOCUS模式下非no_reply动作需要target_message_id
if action != "no_reply": if action != "no_reply":
if target_message_id := parsed_json.get("target_message_id"): if target_message_id := parsed_json.get("target_message_id"):
if isinstance(target_message_id, int):
target_message_id = str(target_message_id)
if isinstance(target_message_id, str) and not target_message_id.startswith("m"):
target_message_id = f"m{target_message_id}"
# 根据target_message_id查找原始消息 # 根据target_message_id查找原始消息
target_message = self.find_message_by_id(target_message_id, message_id_list) target_message = self.find_message_by_id(target_message_id, message_id_list)
# 如果获取的target_message为None输出warning并重新plan # 如果获取的target_message为None输出warning并重新plan
if target_message is None: if target_message is None:
self.plan_retry_count += 1 self.plan_retry_count += 1
logger.warning( logger.warning(f"{self.log_prefix}无法找到target_message_id '{target_message_id}' 对应的消息,重试次数: {self.plan_retry_count}/{self.max_plan_retries}")
f"{self.log_prefix}无法找到target_message_id '{target_message_id}' 对应的消息,重试次数: {self.plan_retry_count}/{self.max_plan_retries}"
)
# 如果连续三次plan均为None输出error并选取最新消息 # 如果连续三次plan均为None输出error并选取最新消息
if self.plan_retry_count >= self.max_plan_retries: if self.plan_retry_count >= self.max_plan_retries:
logger.error( logger.error(f"{self.log_prefix}连续{self.max_plan_retries}次plan获取target_message失败选择最新消息作为target_message")
f"{self.log_prefix}连续{self.max_plan_retries}次plan获取target_message失败选择最新消息作为target_message"
)
target_message = self.get_latest_message(message_id_list) target_message = self.get_latest_message(message_id_list)
self.plan_retry_count = 0 # 重置计数器 self.plan_retry_count = 0 # 重置计数器
else: else:
@@ -334,11 +339,8 @@ class ActionPlanner:
logger.warning(f"{self.log_prefix}动作'{action}'缺少target_message_id") logger.warning(f"{self.log_prefix}动作'{action}'缺少target_message_id")
if action == "no_action": if action == "no_action":
reasoning = "normal决定不使用额外动作" reasoning = "normal决定不使用额外动作"
elif mode == ChatMode.PROACTIVE and action == "do_nothing":
pass # 在PROACTIVE模式下do_nothing是有效动作
elif action != "no_reply" and action != "reply" and action not in current_available_actions: elif action != "no_reply" and action != "reply" and action not in current_available_actions:
logger.warning( logger.warning(
f"{self.log_prefix}LLM 返回了当前不可用或无效的动作: '{action}' (可用: {list(current_available_actions.keys())}),将强制使用 'no_reply'" f"{self.log_prefix}LLM 返回了当前不可用或无效的动作: '{action}' (可用: {list(current_available_actions.keys())}),将强制使用 'no_reply'"
@@ -346,69 +348,18 @@ class ActionPlanner:
reasoning = f"LLM 返回了当前不可用的动作 '{action}' (可用: {list(current_available_actions.keys())})。原始理由: {reasoning}" reasoning = f"LLM 返回了当前不可用的动作 '{action}' (可用: {list(current_available_actions.keys())})。原始理由: {reasoning}"
action = "no_reply" action = "no_reply"
# 检查no_reply是否可用如果不可用则使用reply作为终极回退
if "no_reply" not in current_available_actions:
if "reply" in current_available_actions:
action = "reply"
reasoning += " (no_reply不可用使用reply作为回退)"
logger.warning(f"{self.log_prefix}no_reply不可用使用reply作为回退")
else:
# 如果连reply都不可用使用第一个可用的动作
if current_available_actions:
action = list(current_available_actions.keys())[0]
reasoning += f" (no_reply和reply都不可用使用{action}作为回退)"
logger.warning(f"{self.log_prefix}no_reply和reply都不可用使用{action}作为回退")
else:
# 如果没有任何可用动作,这是一个严重错误
logger.error(f"{self.log_prefix}没有任何可用动作,系统状态异常")
action = "no_reply" # 仍然尝试no_reply让上层处理
# 对no_reply动作本身也进行可用性检查
elif action == "no_reply" and "no_reply" not in current_available_actions:
if "reply" in current_available_actions:
action = "reply"
reasoning = f"no_reply不可用自动回退到reply。原因: {reasoning}"
logger.warning(f"{self.log_prefix}no_reply不可用自动回退到reply")
elif current_available_actions:
action = list(current_available_actions.keys())[0]
reasoning = f"no_reply不可用自动回退到{action}。原因: {reasoning}"
logger.warning(f"{self.log_prefix}no_reply不可用自动回退到{action}")
else:
logger.error(f"{self.log_prefix}没有任何可用动作保持no_reply让上层处理")
except Exception as json_e: except Exception as json_e:
logger.warning(f"{self.log_prefix}解析LLM响应JSON失败 {json_e}. LLM原始输出: '{llm_content}'") logger.warning(f"{self.log_prefix}解析LLM响应JSON失败 {json_e}. LLM原始输出: '{llm_content}'")
traceback.print_exc() traceback.print_exc()
reasoning = f"解析LLM响应JSON失败: {json_e}. 将使用默认动作 'no_reply'." reasoning = f"解析LLM响应JSON失败: {json_e}. 将使用默认动作 'no_reply'."
action = "no_reply" action = "no_reply"
# 检查no_reply是否可用
if "no_reply" not in current_available_actions:
if "reply" in current_available_actions:
action = "reply"
reasoning += " (no_reply不可用使用reply)"
elif current_available_actions:
action = list(current_available_actions.keys())[0]
reasoning += f" (no_reply不可用使用{action})"
except Exception as outer_e: except Exception as outer_e:
logger.error(f"{self.log_prefix}Planner 处理过程中发生意外错误,规划失败,将执行 no_reply: {outer_e}") logger.error(f"{self.log_prefix}Planner 处理过程中发生意外错误,规划失败,将执行 no_reply: {outer_e}")
traceback.print_exc() traceback.print_exc()
action = "no_reply" action = "no_reply"
reasoning = f"Planner 内部处理错误: {outer_e}" reasoning = f"Planner 内部处理错误: {outer_e}"
# 检查no_reply是否可用
current_available_actions = self.action_manager.get_using_actions()
if "no_reply" not in current_available_actions:
if "reply" in current_available_actions:
action = "reply"
reasoning += " (no_reply不可用使用reply)"
elif current_available_actions:
action = list(current_available_actions.keys())[0]
reasoning += f" (no_reply不可用使用{action})"
else:
logger.error(f"{self.log_prefix}严重错误:没有任何可用动作")
is_parallel = False is_parallel = False
if mode == ChatMode.NORMAL and action in current_available_actions: if mode == ChatMode.NORMAL and action in current_available_actions:
is_parallel = current_available_actions[action].parallel_action is_parallel = current_available_actions[action].parallel_action
@@ -422,17 +373,19 @@ class ActionPlanner:
actions.append({ actions.append({
"action_type": action, "action_type": action,
"reasoning": reasoning, "reasoning": reasoning,
"timestamp": time.time(), "action_data": action_data,
"is_parallel": is_parallel, "action_message": target_message,
} "available_actions": available_actions # 添加这个字段
})
return ( if action != "reply" and is_parallel:
{ actions.append({
"action_result": action_result, "action_type": "reply",
"action_prompt": prompt, "action_message": target_message,
}, "available_actions": available_actions
target_message, })
)
return actions,target_message
async def build_planner_prompt( async def build_planner_prompt(
self, self,
@@ -448,15 +401,21 @@ class ActionPlanner:
time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" time_block = f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
bot_name = global_config.bot.nickname bot_name = global_config.bot.nickname
bot_nickname = ( bot_nickname = (
f",也有人叫你{','.join(global_config.bot.alias_names)}" if global_config.bot.alias_names else "" f",也有人叫你{','.join(global_config.bot.alias_names)}"
if global_config.bot.alias_names
else ""
) )
bot_core_personality = global_config.personality.personality_core bot_core_personality = global_config.personality.personality_core
identity_block = f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}" identity_block = (
f"你的名字是{bot_name}{bot_nickname},你{bot_core_personality}"
)
schedule_block = "" schedule_block = ""
if global_config.schedule.enable: if global_config.schedule.enable:
if current_activity := schedule_manager.get_current_activity(): if current_activity := schedule_manager.get_current_activity():
schedule_block = f"你当前正在:{current_activity},但注意它与群聊的聊天无关。" schedule_block = (
f"你当前正在:{current_activity},但注意它与群聊的聊天无关。"
)
mood_block = "" mood_block = ""
if global_config.mood.enable_mood: if global_config.mood.enable_mood:
@@ -466,9 +425,13 @@ class ActionPlanner:
# --- 根据模式构建不同的Prompt --- # --- 根据模式构建不同的Prompt ---
if mode == ChatMode.PROACTIVE: if mode == ChatMode.PROACTIVE:
long_term_memory_block = await self._get_long_term_memory_context() long_term_memory_block = await self._get_long_term_memory_context()
action_options_text = await self._build_action_options(current_available_actions, mode) action_options_text = await self._build_action_options(
current_available_actions, mode
)
prompt_template = await global_prompt_manager.get_prompt_async("proactive_planner_prompt") prompt_template = await global_prompt_manager.get_prompt_async(
"proactive_planner_prompt"
)
prompt = prompt_template.format( prompt = prompt_template.format(
time_block=time_block, time_block=time_block,
identity_block=identity_block, identity_block=identity_block,
@@ -501,24 +464,35 @@ class ActionPlanner:
limit=5, limit=5,
) )
actions_before_now_block = build_readable_actions(actions=actions_before_now) actions_before_now_block = build_readable_actions(
actions_before_now_block = f"你刚刚选择并执行过的action是\n{actions_before_now_block}" actions=actions_before_now
)
actions_before_now_block = (
f"你刚刚选择并执行过的action是\n{actions_before_now_block}"
)
if refresh_time:
self.last_obs_time_mark = time.time() self.last_obs_time_mark = time.time()
if mode == ChatMode.FOCUS:
mentioned_bonus = "" mentioned_bonus = ""
if global_config.chat.mentioned_bot_inevitable_reply: if global_config.chat.mentioned_bot_inevitable_reply:
mentioned_bonus = "\n- 有人提到你" mentioned_bonus = "\n- 有人提到你"
if global_config.chat.at_bot_inevitable_reply: if global_config.chat.at_bot_inevitable_reply:
mentioned_bonus = "\n- 有人提到你或者at你" mentioned_bonus = "\n- 有人提到你或者at你"
if mode == ChatMode.FOCUS:
by_what = "聊天内容" by_what = "聊天内容"
target_prompt = '\n "target_message_id":"触发action的消息id"' target_prompt = '\n "target_message_id":"触发action的消息id"'
no_action_block = f"""重要说明: no_action_block = f"""重要说明:
- 'no_reply' 表示只进行不进行回复,等待合适的回复时机(由系统直接处理) - 'no_reply' 表示只进行不进行回复,等待合适的回复时机
- 当你刚刚发送了消息没有人回复时选择no_reply - 当你刚刚发送了消息没有人回复时选择no_reply
- 当你一次发送了太多消息为了避免打扰聊天节奏选择no_reply - 当你一次发送了太多消息为了避免打扰聊天节奏选择no_reply
动作reply
动作描述:参与聊天回复,发送文本进行表达
- 你想要闲聊或者随便附和{mentioned_bonus}
- 如果你刚刚进行了回复,不要对同一个话题重复回应
- 不要回复自己发送的消息
{{ {{
"action": "no_reply", "action": "no_reply",
"reason":"不回复的原因" "reason":"不回复的原因"
@@ -527,27 +501,46 @@ class ActionPlanner:
else: # NORMAL Mode else: # NORMAL Mode
by_what = "聊天内容和用户的最新消息" by_what = "聊天内容和用户的最新消息"
target_prompt = "" target_prompt = ""
no_action_block = """重要说明: no_action_block = f"""重要说明:
- 'reply' 表示只进行普通聊天回复,不执行任何额外动作 - 'reply' 表示只进行普通聊天回复,不执行任何额外动作
- 其他action表示在普通回复的基础上执行相应的额外动作 - 其他action表示在普通回复的基础上执行相应的额外动作
"""
动作reply
动作描述:参与聊天回复,发送文本进行表达
- 你想要闲聊或者随便附
- {mentioned_bonus}
- 如果你刚刚进行了回复,不要对同一个话题重复回应
- 不要回复自己发送的消息
{{
"action": "reply",
"target_message_id":"触发action的消息id",
"reason":"回复的原因"
}}"""
chat_context_description = "你现在正在一个群聊中" chat_context_description = "你现在正在一个群聊中"
if not is_group_chat and chat_target_info: if not is_group_chat and chat_target_info:
chat_target_name = ( chat_target_name = (
chat_target_info.get("person_name") or chat_target_info.get("user_nickname") or "对方" chat_target_info.get("person_name")
or chat_target_info.get("user_nickname")
or "对方"
) )
chat_context_description = f"你正在和 {chat_target_name} 私聊" chat_context_description = f"你正在和 {chat_target_name} 私聊"
action_options_block = await self._build_action_options(current_available_actions, mode, target_prompt) action_options_block = await self._build_action_options(
current_available_actions, mode, target_prompt
)
moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。" moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。"
custom_prompt_block = "" custom_prompt_block = ""
if global_config.custom_prompt.planner_custom_prompt_content: if global_config.custom_prompt.planner_custom_prompt_content:
custom_prompt_block = global_config.custom_prompt.planner_custom_prompt_content custom_prompt_block = (
global_config.custom_prompt.planner_custom_prompt_content
)
planner_prompt_template = await global_prompt_manager.get_prompt_async("planner_prompt") planner_prompt_template = await global_prompt_manager.get_prompt_async(
"planner_prompt"
)
prompt = planner_prompt_template.format( prompt = planner_prompt_template.format(
schedule_block=schedule_block, schedule_block=schedule_block,
mood_block=mood_block, mood_block=mood_block,
@@ -575,7 +568,9 @@ class ActionPlanner:
""" """
is_group_chat = True is_group_chat = True
is_group_chat, chat_target_info = get_chat_type_and_target_info(self.chat_id) is_group_chat, chat_target_info = get_chat_type_and_target_info(self.chat_id)
logger.debug(f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}") logger.debug(
f"{self.log_prefix}获取到聊天信息 - 群聊: {is_group_chat}, 目标信息: {chat_target_info}"
)
current_available_actions_dict = self.action_manager.get_using_actions() current_available_actions_dict = self.action_manager.get_using_actions()
@@ -586,9 +581,13 @@ class ActionPlanner:
current_available_actions = {} current_available_actions = {}
for action_name in current_available_actions_dict: for action_name in current_available_actions_dict:
if action_name in all_registered_actions: if action_name in all_registered_actions:
current_available_actions[action_name] = all_registered_actions[action_name] current_available_actions[action_name] = all_registered_actions[
action_name
]
else: else:
logger.warning(f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到") logger.warning(
f"{self.log_prefix}使用中的动作 {action_name} 未在已注册动作中找到"
)
# 将no_reply作为系统级特殊动作添加到可用动作中 # 将no_reply作为系统级特殊动作添加到可用动作中
# no_reply虽然是系统级决策但需要让规划器认为它是可用的 # no_reply虽然是系统级决策但需要让规划器认为它是可用的

View File

@@ -1,80 +0,0 @@
from typing import Tuple
# 导入新插件系统
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
# 导入依赖的系统组件
from src.common.logger import get_logger
from src.plugin_system.apis import generator_api
logger = get_logger("reply_action")
class ReplyAction(BaseAction):
"""基本回复动作:确保系统始终有一个可用的回退动作!!!"""
focus_activation_type = ActionActivationType.ALWAYS
normal_activation_type = ActionActivationType.ALWAYS
mode_enable = ChatMode.ALL
parallel_action = False
# 动作基本信息
action_name = "reply"
action_description = "进行基本回复"
# 动作参数定义
action_parameters = {}
# 动作使用场景
action_require = [""]
# 关联类型
associated_types = []
async def execute(self) -> Tuple[bool, str]:
"""执行回复动作"""
try:
reason = self.action_data.get("reason", "")
logger.info(f"{self.log_prefix} 执行基本回复动作,原因: {reason}")
# 获取当前消息和上下文
if not self.chat_stream or not self.chat_stream.get_latest_message():
logger.warning(f"{self.log_prefix} 没有可回复的消息")
return False, ""
latest_message = self.chat_stream.get_latest_message()
# 使用生成器API生成回复
success, reply_set, _ = await generator_api.generate_reply(
target_message=latest_message.processed_plain_text,
chat_stream=self.chat_stream,
reasoning=reason,
action_message={},
)
if success and reply_set:
# 提取回复文本
reply_text = ""
for message_type, content in reply_set:
if message_type == "text":
reply_text += content
break
if reply_text:
logger.info(f"{self.log_prefix} 回复生成成功: {reply_text[:50]}...")
return True, reply_text
else:
logger.warning(f"{self.log_prefix} 生成的回复为空")
return False, ""
else:
logger.warning(f"{self.log_prefix} 回复生成失败")
return False, ""
except Exception as e:
logger.error(f"{self.log_prefix} 执行回复动作时发生异常: {e}")
import traceback
traceback.print_exc()
return False, ""

View File

@@ -16,7 +16,6 @@ from src.plugin_system.base.config_types import ConfigField
from src.common.logger import get_logger from src.common.logger import get_logger
# 导入API模块 - 标准Python包方式 # 导入API模块 - 标准Python包方式
from src.plugins.built_in.core_actions.reply import ReplyAction
from src.plugins.built_in.core_actions.emoji import EmojiAction from src.plugins.built_in.core_actions.emoji import EmojiAction
from src.plugins.built_in.core_actions.anti_injector_manager import AntiInjectorStatusCommand from src.plugins.built_in.core_actions.anti_injector_manager import AntiInjectorStatusCommand
@@ -68,8 +67,6 @@ class CoreActionsPlugin(BasePlugin):
# --- 根据配置注册组件 --- # --- 根据配置注册组件 ---
components = [] components = []
if self.get_config("components.enable_reply", True):
components.append((ReplyAction.get_action_info(), ReplyAction))
if self.get_config("components.enable_emoji", True): if self.get_config("components.enable_emoji", True):
components.append((EmojiAction.get_action_info(), EmojiAction)) components.append((EmojiAction.get_action_info(), EmojiAction))
if self.get_config("components.enable_anti_injector_manager", True): if self.get_config("components.enable_anti_injector_manager", True):