MaiBot/src/experimental/PFC/actions.py

186 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import time
import asyncio
import datetime
import traceback
from typing import Optional, TYPE_CHECKING
from src.common.logger_manager import get_logger
from .pfc_types import ConversationState # 调整导入路径
from .observation_info import ObservationInfo # 调整导入路径
from .conversation_info import ConversationInfo # 调整导入路径
# 导入工厂类
from .action_factory import StandardActionFactory # 调整导入路径
if TYPE_CHECKING:
from .conversation import Conversation # 调整导入路径
logger = get_logger("pfc_actions") # 模块级别日志记录器
async def handle_action(
conversation_instance: "Conversation",
action: str,
reason: str,
observation_info: Optional[ObservationInfo],
conversation_info: Optional[ConversationInfo],
):
"""
处理由 ActionPlanner 规划出的具体行动。
使用 ActionFactory 创建并执行相应的处理器。
"""
# 检查对话实例是否已初始化
if not conversation_instance._initialized:
logger.error(f"[私聊][{conversation_instance.private_name}] 尝试在未初始化状态下处理动作 '{action}'")
return
# 检查 observation_info 是否为空
if not observation_info:
logger.error(f"[私聊][{conversation_instance.private_name}] ObservationInfo 为空,无法处理动作 '{action}'")
# 如果 conversation_info 和 done_action 存在且不为空
if conversation_info and hasattr(conversation_info, "done_action") and conversation_info.done_action:
# 更新最后一个动作记录的状态和原因
if conversation_info.done_action: # 再次检查列表是否不为空
conversation_info.done_action[-1].update(
{"status": "error", "final_reason": "ObservationInfo is None"}
)
conversation_instance.state = ConversationState.ERROR # 设置对话状态为错误
return
# 检查 conversation_info 是否为空
if not conversation_info:
logger.error(f"[私聊][{conversation_instance.private_name}] ConversationInfo 为空,无法处理动作 '{action}'")
conversation_instance.state = ConversationState.ERROR # 设置对话状态为错误
return
logger.info(f"[私聊][{conversation_instance.private_name}] 开始处理动作: {action}, 原因: {reason}")
action_start_time = time.time() # 记录动作开始时间
# 当前动作记录
current_action_record = {
"action": action, # 动作类型
"plan_reason": reason, # 规划原因
"status": "start", # 初始状态为 "start"
"time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # 当前时间
"final_reason": None, # 最终原因,默认为 None
}
# 如果 done_action 不存在或为空,则初始化
if not hasattr(conversation_info, "done_action") or conversation_info.done_action is None:
conversation_info.done_action = []
conversation_info.done_action.append(current_action_record) # 添加当前动作记录
action_index = len(conversation_info.done_action) - 1 # 获取当前动作记录的索引
action_successful: bool = False # 动作是否成功,默认为 False
final_status: str = "recall" # 最终状态,默认为 "recall"
final_reason: str = "动作未成功执行" # 最终原因,默认为 "动作未成功执行"
factory = StandardActionFactory() # 创建标准动作工厂实例
action_handler = factory.create_action_handler(action, conversation_instance) # 创建动作处理器
try:
# 执行动作处理器
action_successful, final_status, final_reason = await action_handler.execute(
reason, observation_info, conversation_info, action_start_time, current_action_record
)
# 动作执行后的逻辑 (例如更新 last_successful_reply_action 等)
# 此部分之前位于每个 if/elif 块内部
# 如果动作不是回复类型的动作
if action not in ["direct_reply", "send_new_message", "say_goodbye", "send_memes"]:
conversation_info.last_successful_reply_action = None # 清除上次成功回复动作
conversation_info.last_reply_rejection_reason = None # 清除上次回复拒绝原因
conversation_info.last_rejected_reply_content = None # 清除上次拒绝的回复内容
# 如果动作不是发送表情包或发送表情包失败,则清除表情查询
if action != "send_memes" or not action_successful:
if hasattr(conversation_info, "current_emoji_query"):
conversation_info.current_emoji_query = None
except asyncio.CancelledError: # 捕获任务取消错误
logger.warning(f"[私聊][{conversation_instance.private_name}] 处理动作 '{action}' 时被取消。")
final_status = "cancelled" # 设置最终状态为 "cancelled"
final_reason = "动作处理被取消"
# 如果 conversation_info 存在
if conversation_info:
conversation_info.last_successful_reply_action = None # 清除上次成功回复动作
raise # 重新抛出异常,由循环处理
except Exception as handle_err: # 捕获其他异常
logger.error(f"[私聊][{conversation_instance.private_name}] 处理动作 '{action}' 时出错: {handle_err}")
logger.error(f"[私聊][{conversation_instance.private_name}] {traceback.format_exc()}")
final_status = "error" # 设置最终状态为 "error"
final_reason = f"处理动作时出错: {handle_err}"
conversation_instance.state = ConversationState.ERROR # 设置对话状态为错误
# 如果 conversation_info 存在
if conversation_info:
conversation_info.last_successful_reply_action = None # 清除上次成功回复动作
action_successful = False # 确保动作为不成功
finally:
# 更新动作历史记录
# 检查 done_action 属性是否存在且不为空,并且索引有效
if (
hasattr(conversation_info, "done_action")
and conversation_info.done_action
and action_index < len(conversation_info.done_action)
):
# 如果动作成功且最终状态不是 "done" 或 "done_no_reply",则设置为 "done"
if action_successful and final_status not in ["done", "done_no_reply"]:
final_status = "done"
# 如果动作成功且最终原因未设置或为默认值
if action_successful and (not final_reason or final_reason == "动作未成功执行"):
final_reason = f"动作 {action} 成功完成"
# 如果是发送表情包且 current_emoji_query 存在(理想情况下从处理器获取描述)
if action == "send_memes" and conversation_info.current_emoji_query:
pass # 占位符 - 表情描述最好从处理器的执行结果中获取并用于原因
# 更新动作记录
conversation_info.done_action[action_index].update(
{
"status": final_status, # 最终状态
"time_completed": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # 完成时间
"final_reason": final_reason, # 最终原因
"duration_ms": int((time.time() - action_start_time) * 1000), # 持续时间(毫秒)
}
)
else: # 如果无法更新动作历史记录
logger.error(
f"[私聊][{conversation_instance.private_name}] 无法更新动作历史记录done_action 无效或索引 {action_index} 超出范围。"
)
# 根据最终状态设置对话状态
if final_status in ["done", "done_no_reply", "recall"]:
conversation_instance.state = ConversationState.ANALYZING # 设置为分析中
elif final_status in ["error", "max_checker_attempts_failed"]:
conversation_instance.state = ConversationState.ERROR # 设置为错误
# 其他状态如 LISTENING, WAITING, IGNORED, ENDED 在各自的处理器内部或由循环设置。
# 此处移至 try 块以确保即使在发生异常之前也运行
# 如果动作不是回复类型的动作
if action not in ["direct_reply", "send_new_message", "say_goodbye", "send_memes"]:
if conversation_info: # 再次检查 conversation_info 是否不为 None
conversation_info.last_successful_reply_action = None # 清除上次成功回复动作
conversation_info.last_reply_rejection_reason = None # 清除上次回复拒绝原因
conversation_info.last_rejected_reply_content = None # 清除上次拒绝的回复内容
# 如果动作不是发送表情包或发送表情包失败
if action != "send_memes" or not action_successful:
# 如果 conversation_info 存在且有 current_emoji_query 属性
if conversation_info and hasattr(conversation_info, "current_emoji_query"):
conversation_info.current_emoji_query = None # 清除当前表情查询
log_final_reason_msg = final_reason if final_reason else "无明确原因" # 记录的最终原因消息
# 如果最终状态为 "done",动作成功,且是直接回复或发送新消息,并且有生成的回复
if (
final_status == "done"
and action_successful
and action in ["direct_reply", "send_new_message"]
and hasattr(conversation_instance, "generated_reply")
and conversation_instance.generated_reply
):
log_final_reason_msg += f" (发送内容: '{conversation_instance.generated_reply[:30]}...')"
# elif final_status == "done" and action_successful and action == "send_memes":
# 表情包的日志记录在其处理器内部或通过下面的通用日志处理
logger.info(
f"[私聊][{conversation_instance.private_name}] 动作 '{action}' 处理完成。最终状态: {final_status}, 原因: {log_final_reason_msg}"
)