fix:修复无法退出专注模式的问题,移除无用插件鸡肋

This commit is contained in:
SengokuCola
2025-06-12 20:54:43 +08:00
parent 9a4a29aa85
commit 3725760b06
5 changed files with 11 additions and 290 deletions

View File

@@ -1,126 +0,0 @@
from abc import ABC, abstractmethod
from typing import Tuple, Dict, Type
from src.common.logger import get_logger
logger = get_logger("base_action")
# 全局动作注册表
_ACTION_REGISTRY: Dict[str, Type["BaseAction"]] = {}
_DEFAULT_ACTIONS: Dict[str, str] = {}
# 动作激活类型枚举
class ActionActivationType:
ALWAYS = "always" # 默认参与到planner
LLM_JUDGE = "llm_judge" # LLM判定是否启动该action到planner
RANDOM = "random" # 随机启用action到planner
KEYWORD = "keyword" # 关键词触发启用action到planner
# 聊天模式枚举
class ChatMode:
FOCUS = "focus" # Focus聊天模式
NORMAL = "normal" # Normal聊天模式
ALL = "all" # 所有聊天模式
def register_action(cls):
"""
动作注册装饰器
用法:
@register_action
class MyAction(BaseAction):
action_name = "my_action"
action_description = "我的动作"
focus_activation_type = ActionActivationType.ALWAYS
normal_activation_type = ActionActivationType.ALWAYS
mode_enable = ChatMode.ALL
parallel_action = False
...
"""
# 检查类是否有必要的属性
if not hasattr(cls, "action_name") or not hasattr(cls, "action_description"):
logger.error(f"动作类 {cls.__name__} 缺少必要的属性: action_name 或 action_description")
return cls
action_name = cls.action_name
action_description = cls.action_description
is_enabled = getattr(cls, "enable_plugin", True) # 默认启用插件
if not action_name or not action_description:
logger.error(f"动作类 {cls.__name__} 的 action_name 或 action_description 为空")
return cls
# 将动作类注册到全局注册表
_ACTION_REGISTRY[action_name] = cls
# 如果启用插件,添加到默认动作集
if is_enabled:
_DEFAULT_ACTIONS[action_name] = action_description
logger.info(f"已注册动作: {action_name} -> {cls.__name__},插件启用: {is_enabled}")
return cls
class BaseAction(ABC):
"""动作基类接口
所有具体的动作类都应该继承这个基类并实现handle_action方法。
"""
def __init__(self, action_data: dict, reasoning: str, cycle_timers: dict, thinking_id: str):
"""初始化动作
Args:
action_data: 动作数据
reasoning: 执行该动作的理由
cycle_timers: 计时器字典
thinking_id: 思考ID
"""
# 每个动作必须实现
self.action_name: str = "base_action"
self.action_description: str = "基础动作"
self.action_parameters: dict = {}
self.action_require: list[str] = []
# 动作激活类型设置
# Focus模式下的激活类型默认为always
self.focus_activation_type: str = ActionActivationType.ALWAYS
# Normal模式下的激活类型默认为always
self.normal_activation_type: str = ActionActivationType.ALWAYS
# 随机激活的概率(0.0-1.0)用于RANDOM激活类型
self.random_activation_probability: float = 0.3
# LLM判定的提示词用于LLM_JUDGE激活类型
self.llm_judge_prompt: str = ""
# 关键词触发列表用于KEYWORD激活类型
self.activation_keywords: list[str] = []
# 关键词匹配是否区分大小写
self.keyword_case_sensitive: bool = False
# 模式启用设置:指定在哪些聊天模式下启用此动作
# 可选值: "focus"(仅Focus模式), "normal"(仅Normal模式), "all"(所有模式)
self.mode_enable: str = ChatMode.ALL
# 并行执行设置仅在Normal模式下生效设置为True的动作可以与回复动作并行执行
# 而不是替代回复动作适用于图片生成、TTS、禁言等不需要覆盖回复的动作
self.parallel_action: bool = False
self.associated_types: list[str] = []
self.enable_plugin: bool = True # 是否启用插件,默认启用
self.action_data = action_data
self.reasoning = reasoning
self.cycle_timers = cycle_timers
self.thinking_id = thinking_id
@abstractmethod
async def handle_action(self) -> Tuple[bool, str]:
"""处理动作的抽象方法,需要被子类实现
Returns:
Tuple[bool, str]: (是否执行成功, 回复文本)
"""
pass

View File

@@ -1,139 +0,0 @@
from typing import Tuple, Dict, Any, Optional
from src.chat.actions.base_action import BaseAction, register_action, ActionActivationType, ChatMode # noqa F401
from src.common.logger import get_logger
import os
import inspect
import toml # 导入 toml 库
from abc import abstractmethod
# 导入新插件系统的API模块
from src.plugin_system.apis.message_api import MessageAPI
from src.plugin_system.apis.llm_api import LLMAPI
from src.plugin_system.apis.database_api import DatabaseAPI
from src.plugin_system.apis.config_api import ConfigAPI
from src.plugin_system.apis.utils_api import UtilsAPI
from src.plugin_system.apis.stream_api import StreamAPI
from src.plugin_system.apis.hearflow_api import HearflowAPI
# 以下为类型注解需要
from src.chat.message_receive.chat_stream import ChatStream # noqa
from src.chat.focus_chat.expressors.default_expressor import DefaultExpressor # noqa
from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer # noqa
from src.chat.focus_chat.info.obs_info import ObsInfo # noqa
logger = get_logger("plugin_action")
class PluginAction(BaseAction, MessageAPI, LLMAPI, DatabaseAPI, ConfigAPI, UtilsAPI, StreamAPI, HearflowAPI):
"""插件动作基类(旧版兼容)
封装了主程序内部依赖提供简化的API接口给插件开发者
⚠️ 此类已弃用,建议使用新的插件系统:
- 新基类src.plugin_system.base.BaseAction
- 新APIsrc.plugin_system.plugin_api
- 新注册:@register_component 装饰器
"""
action_config_file_name: Optional[str] = None # 插件可以覆盖此属性来指定配置文件名
# 默认激活类型设置,插件可以覆盖
focus_activation_type = ActionActivationType.ALWAYS
normal_activation_type = ActionActivationType.ALWAYS
random_activation_probability: float = 0.3
llm_judge_prompt: str = ""
activation_keywords: list[str] = []
keyword_case_sensitive: bool = False
# 默认模式启用设置 - 插件动作默认在所有模式下可用,插件可以覆盖
mode_enable = ChatMode.ALL
def __init__(
self,
action_data: dict,
reasoning: str,
cycle_timers: dict,
thinking_id: str,
global_config: Optional[dict] = None,
**kwargs,
):
"""初始化插件动作基类"""
super().__init__(action_data, reasoning, cycle_timers, thinking_id)
# 存储内部服务和对象引用
self._services = {}
self.config: Dict[str, Any] = {} # 用于存储插件自身的配置
# 从kwargs提取必要的内部服务
if "observations" in kwargs:
self._services["observations"] = kwargs["observations"]
if "expressor" in kwargs:
self._services["expressor"] = kwargs["expressor"]
if "chat_stream" in kwargs:
self._services["chat_stream"] = kwargs["chat_stream"]
if "replyer" in kwargs:
self._services["replyer"] = kwargs["replyer"]
self.log_prefix = kwargs.get("log_prefix", "")
self._load_plugin_config() # 初始化时加载插件配置
def _load_plugin_config(self):
"""
加载插件自身的配置文件。
配置文件应与插件模块在同一目录下。
插件可以通过覆盖 `action_config_file_name` 类属性来指定文件名。
如果 `action_config_file_name` 未指定,则不加载配置。
仅支持 TOML (.toml) 格式。
"""
if not self.action_config_file_name:
logger.debug(
f"{self.log_prefix} 插件 {self.__class__.__name__} 未指定 action_config_file_name不加载插件配置。"
)
return
try:
plugin_module_path = inspect.getfile(self.__class__)
plugin_dir = os.path.dirname(plugin_module_path)
config_file_path = os.path.join(plugin_dir, self.action_config_file_name)
if not os.path.exists(config_file_path):
logger.warning(
f"{self.log_prefix} 插件 {self.__class__.__name__} 的配置文件 {config_file_path} 不存在。"
)
return
file_ext = os.path.splitext(self.action_config_file_name)[1].lower()
if file_ext == ".toml":
with open(config_file_path, "r", encoding="utf-8") as f:
self.config = toml.load(f) or {}
logger.info(f"{self.log_prefix} 插件 {self.__class__.__name__} 的配置已从 {config_file_path} 加载。")
else:
logger.warning(
f"{self.log_prefix} 不支持的插件配置文件格式: {file_ext}。仅支持 .toml。插件配置未加载。"
)
self.config = {} # 确保未加载时为空字典
return
except Exception as e:
logger.error(
f"{self.log_prefix} 加载插件 {self.__class__.__name__} 的配置文件 {self.action_config_file_name} 时出错: {e}"
)
self.config = {} # 出错时确保 config 是一个空字典
@abstractmethod
async def process(self) -> Tuple[bool, str]:
"""插件处理逻辑,子类必须实现此方法
Returns:
Tuple[bool, str]: (是否执行成功, 回复文本)
"""
pass
async def handle_action(self) -> Tuple[bool, str]:
"""实现BaseAction的抽象方法调用子类的process方法
Returns:
Tuple[bool, str]: (是否执行成功, 回复文本)
"""
return await self.process()

View File

@@ -1,17 +0,0 @@
from src.chat.actions.plugin_api.message_api import MessageAPI
from src.chat.actions.plugin_api.llm_api import LLMAPI
from src.chat.actions.plugin_api.database_api import DatabaseAPI
from src.chat.actions.plugin_api.config_api import ConfigAPI
from src.chat.actions.plugin_api.utils_api import UtilsAPI
from src.chat.actions.plugin_api.stream_api import StreamAPI
from src.chat.actions.plugin_api.hearflow_api import HearflowAPI
__all__ = [
"MessageAPI",
"LLMAPI",
"DatabaseAPI",
"ConfigAPI",
"UtilsAPI",
"StreamAPI",
"HearflowAPI",
]

View File

@@ -588,12 +588,18 @@ class HeartFChatting:
return False, "", "" return False, "", ""
# 处理动作并获取结果 # 处理动作并获取结果
result = await action_handler.handle_action() success, reply_text = await action_handler.handle_action()
if len(result) == 3: if len(result) == 3:
success, reply_text, command = result success, reply_text, command = result
else: else:
success, reply_text = result success, reply_text = result
command = "" command = ""
# 检查action_data中是否有系统命令优先使用系统命令
if "_system_command" in action_data:
command = action_data["_system_command"]
logger.debug(f"{self.log_prefix} 从action_data中获取系统命令: {command}")
logger.debug(f"{self.log_prefix} 麦麦执行了'{action}', 返回结果'{success}', '{reply_text}', '{command}'") logger.debug(f"{self.log_prefix} 麦麦执行了'{action}', 返回结果'{success}', '{reply_text}', '{command}'")
return success, reply_text, command return success, reply_text, command

View File

@@ -287,13 +287,10 @@ class ExitFocusChatAction(BaseAction):
logger.info(f"{self.log_prefix} 决定退出专注聊天: {self.reasoning}") logger.info(f"{self.log_prefix} 决定退出专注聊天: {self.reasoning}")
try: try:
# 转换状态 - 这里返回特殊的命令标识 # 标记状态切换请求
status_message = ""
# 通过返回值中的特殊标识来通知系统执行状态切换
# 系统会识别这个返回值并执行相应的状态切换逻辑
self._mark_state_change() self._mark_state_change()
status_message = "决定退出专注聊天模式"
return True, status_message return True, status_message
except Exception as e: except Exception as e:
@@ -304,7 +301,7 @@ class ExitFocusChatAction(BaseAction):
"""标记状态切换请求""" """标记状态切换请求"""
# 通过action_data传递状态切换命令 # 通过action_data传递状态切换命令
self.action_data["_system_command"] = "stop_focus_chat" self.action_data["_system_command"] = "stop_focus_chat"
logger.debug(f"{self.log_prefix} 已标记状态切换命令: stop_focus_chat") logger.info(f"{self.log_prefix} 已标记状态切换命令: stop_focus_chat")
@register_plugin @register_plugin