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

This commit is contained in:
Windpicker-owo
2025-09-02 16:15:33 +08:00
parent 255e3627b4
commit ac143a1201
8 changed files with 279 additions and 353 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],
# 专注模式下提及bot必定回复 current_available_actions=planner_info[2],
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.base.component_types import EventType
# 触发 ON_PLAN 事件
result = await event_manager.trigger_event(
EventType.ON_PLAN, plugin_name="SYSTEM", stream_id=self.context.stream_id
) )
if result and not result.all_continue_process(): from src.plugin_system.core.event_manager import event_manager
return from src.plugin_system import EventType
result = await event_manager.trigger_event(EventType.ON_PLAN,plugin_name="SYSTEM", stream_id=self.context.chat_stream)
if not result.all_continue_process():
raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于规划前中断了内容生成")
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,
@@ -366,8 +351,6 @@ 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()
@@ -375,21 +358,15 @@ class CycleProcessor:
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,21 +107,12 @@ 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) 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._log_energy_change("能量值衰减") self._log_energy_change("能量值衰减")
self.context.save_context_state() self.context.save_context_state()

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,41 +236,41 @@ 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(interest_value = interest_value)
await self.cycle_processor.observe(message)
# 如果成功观察,增加能量值
# 如果成功观察,增加能量值 if has_new_messages:
if has_new_messages: self.context.energy_value += 1 / global_config.chat.focus_value
self.context.energy_value += 1 / global_config.chat.focus_value 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,20 +408,58 @@ 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原有逻辑"""
new_message_count = len(new_message) new_message_count = len(new_message)

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

@@ -1,7 +1,7 @@
import orjson import orjson
import time import time
import traceback import traceback
from typing import Dict, Any, Optional, Tuple from typing import Dict, Any, Optional, Tuple, List
from rich.traceback import install from rich.traceback import install
from datetime import datetime from datetime import datetime
from json_repair import repair_json from json_repair import repair_json
@@ -148,7 +148,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:
""" """
构建动作选项 构建动作选项
@@ -171,12 +174,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,
@@ -186,7 +192,9 @@ 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 # sourcery skip: use-next
""" """
根据message_id从message_id_list中查找对应的原始消息 根据message_id从message_id_list中查找对应的原始消息
@@ -218,7 +226,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根据上下文决定做出什么动作。
""" """
@@ -279,36 +292,27 @@ class ActionPlanner:
parsed_json = {} parsed_json = {}
action = parsed_json.get("action", "no_reply") action = parsed_json.get("action", "no_reply")
reasoning = parsed_json.get("reasoning", "未提供原因") reasoning = parsed_json.get("reason", "未提供原因")
# 将所有其他属性添加到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
# 在FOCUS模式下非no_reply动作需要target_message_id # 在FOCUS模式下非no_reply动作需要target_message_id
if mode == ChatMode.FOCUS and 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 # target_message = None
# 如果获取的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:
@@ -318,12 +322,11 @@ class ActionPlanner:
# 成功获取到target_message重置计数器 # 成功获取到target_message重置计数器
self.plan_retry_count = 0 self.plan_retry_count = 0
else: else:
logger.warning(f"{self.log_prefix}FOCUS模式下动作'{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'"
@@ -331,94 +334,51 @@ 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
action_result = {
action_data["loop_start_time"] = loop_start_time
actions = []
# 1. 添加Planner取得的动作
actions.append({
"action_type": action, "action_type": action,
"action_data": action_data,
"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:
"action_result": action_result, actions.append({
"action_prompt": prompt, "action_type": "reply",
}, "action_message": target_message,
target_message, "available_actions": available_actions
) })
return actions,target_message
async def build_planner_prompt( async def build_planner_prompt(
self, self,
is_group_chat: bool, # Now passed as argument is_group_chat: bool, # Now passed as argument
chat_target_info: Optional[dict], # Now passed as argument chat_target_info: Optional[dict], # Now passed as argument
current_available_actions: Dict[str, ActionInfo], current_available_actions: Dict[str, ActionInfo],
refresh_time :bool = False,
mode: ChatMode = ChatMode.FOCUS, mode: ChatMode = ChatMode.FOCUS,
) -> tuple[str, list]: # sourcery skip: use-join ) -> tuple[str, list]: # sourcery skip: use-join
"""构建 Planner LLM 的提示词 (获取模板并填充数据)""" """构建 Planner LLM 的提示词 (获取模板并填充数据)"""
@@ -427,15 +387,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:
@@ -445,9 +411,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,
@@ -480,22 +450,27 @@ 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}"
)
self.last_obs_time_mark = time.time() if refresh_time:
self.last_obs_time_mark = time.time()
mentioned_bonus = ""
if global_config.chat.mentioned_bot_inevitable_reply:
mentioned_bonus = "\n- 有人提到你"
if global_config.chat.at_bot_inevitable_reply:
mentioned_bonus = "\n- 有人提到你或者at你"
if mode == ChatMode.FOCUS: if mode == ChatMode.FOCUS:
mentioned_bonus = ""
if global_config.chat.mentioned_bot_inevitable_reply:
mentioned_bonus = "\n- 有人提到你"
if global_config.chat.at_bot_inevitable_reply:
mentioned_bonus = "\n- 有人提到你或者at你"
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
@@ -503,6 +478,7 @@ class ActionPlanner:
动作描述:参与聊天回复,发送文本进行表达 动作描述:参与聊天回复,发送文本进行表达
- 你想要闲聊或者随便附和{mentioned_bonus} - 你想要闲聊或者随便附和{mentioned_bonus}
- 如果你刚刚进行了回复,不要对同一个话题重复回应 - 如果你刚刚进行了回复,不要对同一个话题重复回应
- 不要回复自己发送的消息
{{ {{
"action": "reply", "action": "reply",
"target_message_id":"触发action的消息id", "target_message_id":"触发action的消息id",
@@ -513,26 +489,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,
@@ -560,7 +556,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()
@@ -571,9 +569,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

@@ -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):

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, ""