From ab61b1bb22ca2827bbca8f2a852ec07f590272cd Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Thu, 10 Jul 2025 16:46:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=92=E4=BB=B6=E7=B3=BB=E7=BB=9Finfo?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=8C=E8=A7=81changes.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changes.md | 18 ++++++++ plugins/hello_world_plugin/plugin.py | 2 + plugins/take_picture_plugin/plugin.py | 2 + src/chat/heart_flow/chat_state_info.py | 2 +- src/chat/message_receive/chat_stream.py | 1 + src/chat/normal_chat/normal_chat.py | 4 +- src/chat/planner_actions/action_manager.py | 7 +-- src/chat/planner_actions/action_modifier.py | 50 +++++---------------- src/plugin_system/base/base_action.py | 3 +- src/plugin_system/base/base_plugin.py | 40 +++++++++++++---- src/plugin_system/base/component_types.py | 1 + src/plugin_system/core/plugin_manager.py | 31 +++++++------ src/plugins/built_in/core_actions/plugin.py | 2 + src/plugins/built_in/tts_plugin/plugin.py | 2 + src/plugins/built_in/vtb_plugin/plugin.py | 2 + 15 files changed, 99 insertions(+), 68 deletions(-) create mode 100644 changes.md diff --git a/changes.md b/changes.md new file mode 100644 index 000000000..85760965f --- /dev/null +++ b/changes.md @@ -0,0 +1,18 @@ +# 插件API与规范修改 + +1. 现在`plugin_system`的`__init__.py`文件中包含了所有插件API的导入,用户可以直接使用`from plugin_system import *`来导入所有API。 + +2. register_plugin函数现在转移到了`plugin_system.apis.plugin_register_api`模块中,用户可以通过`from plugin_system.apis.plugin_register_api import register_plugin`来导入。 + +3. 现在强制要求的property如下: + - `plugin_name`: 插件名称,必须是唯一的。(与文件夹相同) + - `enable_plugin`: 是否启用插件,默认为`True`。 + - `dependencies`: 插件依赖的其他插件列表,默认为空。**现在并不检查(也许)** + - `python_dependencies`: 插件依赖的Python包列表,默认为空。**现在并不检查** + - `config_file_name`: 插件配置文件名,默认为`config.toml`。 + - `config_schema`: 插件配置文件的schema,用于自动生成配置文件。 + +# 插件系统修改 +1. 现在所有的匹配模式不再是关键字了,而是枚举类。**(可能有遗漏)** +2. 修复了一下显示插件信息不显示的问题。同时精简了一下显示内容 +3. 修复了插件系统混用了`plugin_name`和`display_name`的问题。现在所有的插件信息都使用`display_name`来显示,而内部标识仍然使用`plugin_name`。**(可能有遗漏)** \ No newline at end of file diff --git a/plugins/hello_world_plugin/plugin.py b/plugins/hello_world_plugin/plugin.py index eaca35489..dc9b8571c 100644 --- a/plugins/hello_world_plugin/plugin.py +++ b/plugins/hello_world_plugin/plugin.py @@ -103,6 +103,8 @@ class HelloWorldPlugin(BasePlugin): # 插件基本信息 plugin_name = "hello_world_plugin" # 内部标识符 enable_plugin = True + dependencies = [] # 插件依赖列表 + python_dependencies = [] # Python包依赖列表 config_file_name = "config.toml" # 配置文件名 # 配置节描述 diff --git a/plugins/take_picture_plugin/plugin.py b/plugins/take_picture_plugin/plugin.py index 15406ca16..bbe189526 100644 --- a/plugins/take_picture_plugin/plugin.py +++ b/plugins/take_picture_plugin/plugin.py @@ -443,6 +443,8 @@ class TakePicturePlugin(BasePlugin): plugin_name = "take_picture_plugin" # 内部标识符 enable_plugin = True + dependencies = [] # 插件依赖列表 + python_dependencies = [] # Python包依赖列表 config_file_name = "config.toml" # 配置节描述 diff --git a/src/chat/heart_flow/chat_state_info.py b/src/chat/heart_flow/chat_state_info.py index 871516d49..9f137a953 100644 --- a/src/chat/heart_flow/chat_state_info.py +++ b/src/chat/heart_flow/chat_state_info.py @@ -5,7 +5,7 @@ class ChatState(enum.Enum): ABSENT = "没在看群" NORMAL = "随便水群" FOCUSED = "认真水群" - + def __str__(self): return self.name diff --git a/src/chat/message_receive/chat_stream.py b/src/chat/message_receive/chat_stream.py index a82acc413..355cca1e6 100644 --- a/src/chat/message_receive/chat_stream.py +++ b/src/chat/message_receive/chat_stream.py @@ -39,6 +39,7 @@ class ChatMessageContext: return self.message def check_types(self, types: list) -> bool: + # sourcery skip: invert-any-all, use-any, use-next """检查消息类型""" if not self.message.message_info.format_info.accept_format: return False diff --git a/src/chat/normal_chat/normal_chat.py b/src/chat/normal_chat/normal_chat.py index b5e9890eb..4d28c5d88 100644 --- a/src/chat/normal_chat/normal_chat.py +++ b/src/chat/normal_chat/normal_chat.py @@ -561,7 +561,9 @@ class NormalChat: available_actions = None if self.enable_planner: try: - await self.action_modifier.modify_actions(mode=ChatMode.NORMAL, message_content=message.processed_plain_text) + await self.action_modifier.modify_actions( + mode=ChatMode.NORMAL, message_content=message.processed_plain_text + ) available_actions = self.action_manager.get_using_actions_for_mode(ChatMode.NORMAL) except Exception as e: logger.warning(f"[{self.stream_name}] 获取available_actions失败: {e}") diff --git a/src/chat/planner_actions/action_manager.py b/src/chat/planner_actions/action_manager.py index 3937d1d14..e4dabd22f 100644 --- a/src/chat/planner_actions/action_manager.py +++ b/src/chat/planner_actions/action_manager.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional, Type, Any +from typing import Dict, List, Optional, Type from src.plugin_system.base.base_action import BaseAction from src.chat.message_receive.chat_stream import ChatStream from src.common.logger import get_logger @@ -7,6 +7,7 @@ from src.plugin_system.base.component_types import ComponentType, ActionActivati logger = get_logger("action_manager") + class ActionManager: """ 动作管理器,用于管理各种类型的动作 @@ -73,7 +74,7 @@ class ActionManager: "activation_keywords": action_info.activation_keywords, "keyword_case_sensitive": action_info.keyword_case_sensitive, # 模式和并行设置 - "mode_enable": action_info.mode_enable.value, + "mode_enable": action_info.mode_enable, "parallel_action": action_info.parallel_action, # 插件信息 "_plugin_name": getattr(action_info, "plugin_name", ""), @@ -187,7 +188,7 @@ class ActionManager: enabled_actions = {} for action_name, action_info in self._using_actions.items(): - action_mode = action_info.mode_enable + action_mode = action_info["mode_enable"] # 检查动作是否在当前模式下启用 if action_mode in [ChatMode.ALL, mode]: diff --git a/src/chat/planner_actions/action_modifier.py b/src/chat/planner_actions/action_modifier.py index 4b15cbdb0..6b0e6a633 100644 --- a/src/chat/planner_actions/action_modifier.py +++ b/src/chat/planner_actions/action_modifier.py @@ -2,16 +2,16 @@ import random import asyncio import hashlib import time -from typing import List, Optional, Any, Dict +from typing import List, Any, Dict from src.common.logger import get_logger from src.config.config import global_config from src.llm_models.utils_model import LLMRequest from src.chat.focus_chat.focus_loop_info import FocusLoopInfo -from src.chat.message_receive.chat_stream import get_chat_manager +from src.chat.message_receive.chat_stream import get_chat_manager, ChatMessageContext from src.chat.planner_actions.action_manager import ActionManager from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat, build_readable_messages -from src.plugin_system.base.component_types import ChatMode +from src.plugin_system.base.component_types import ChatMode, ActionInfo logger = get_logger("action_manager") @@ -48,7 +48,7 @@ class ActionModifier: loop_info=None, mode: ChatMode = ChatMode.FOCUS, message_content: str = "", - ): + ): # sourcery skip: use-named-expression """ 动作修改流程,整合传统观察处理和新的激活类型判定 @@ -129,15 +129,14 @@ class ActionModifier: f"{self.log_prefix}{mode}模式动作修改流程结束,最终可用动作: {list(self.action_manager.get_using_actions_for_mode(mode).keys())}||移除记录: {removals_summary}" ) - def _check_action_associated_types(self, all_actions, chat_context): + def _check_action_associated_types(self, all_actions: Dict[str, ActionInfo], chat_context: ChatMessageContext): type_mismatched_actions = [] for action_name, data in all_actions.items(): - if data.get("associated_types"): - if not chat_context.check_types(data["associated_types"]): - associated_types_str = ", ".join(data["associated_types"]) - reason = f"适配器不支持(需要: {associated_types_str})" - type_mismatched_actions.append((action_name, reason)) - logger.debug(f"{self.log_prefix}决定移除动作: {action_name},原因: {reason}") + if data["associated_types"] and not chat_context.check_types(data["associated_types"]): + associated_types_str = ", ".join(data["associated_types"]) + reason = f"适配器不支持(需要: {associated_types_str})" + type_mismatched_actions.append((action_name, reason)) + logger.debug(f"{self.log_prefix}决定移除动作: {action_name},原因: {reason}") return type_mismatched_actions async def _get_deactivated_actions_by_type( @@ -205,35 +204,6 @@ class ActionModifier: return deactivated_actions - async def process_actions_for_planner( - self, observed_messages_str: str = "", chat_context: Optional[str] = None, extra_context: Optional[str] = None - ) -> Dict[str, Any]: - """ - [已废弃] 此方法现在已被整合到 modify_actions() 中 - - 为了保持向后兼容性而保留,但建议直接使用 ActionManager.get_using_actions() - 规划器应该直接从 ActionManager 获取最终的可用动作集,而不是调用此方法 - - 新的架构: - 1. 主循环调用 modify_actions() 处理完整的动作管理流程 - 2. 规划器直接使用 ActionManager.get_using_actions() 获取最终动作集 - """ - logger.warning( - f"{self.log_prefix}process_actions_for_planner() 已废弃,建议规划器直接使用 ActionManager.get_using_actions()" - ) - - # 为了向后兼容,仍然返回当前使用的动作集 - current_using_actions = self.action_manager.get_using_actions() - all_registered_actions = self.action_manager.get_registered_actions() - - # 构建完整的动作信息 - result = {} - for action_name in current_using_actions.keys(): - if action_name in all_registered_actions: - result[action_name] = all_registered_actions[action_name] - - return result - def _generate_context_hash(self, chat_content: str) -> str: """生成上下文的哈希值用于缓存""" context_content = f"{chat_content}" diff --git a/src/plugin_system/base/base_action.py b/src/plugin_system/base/base_action.py index cc5cbc261..42e36b64d 100644 --- a/src/plugin_system/base/base_action.py +++ b/src/plugin_system/base/base_action.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod from typing import Tuple, Optional from src.common.logger import get_logger +from src.chat.message_receive.chat_stream import ChatStream from src.plugin_system.base.component_types import ActionActivationType, ChatMode, ActionInfo, ComponentType from src.plugin_system.apis import send_api, database_api, message_api import time @@ -31,7 +32,7 @@ class BaseAction(ABC): reasoning: str, cycle_timers: dict, thinking_id: str, - chat_stream=None, + chat_stream: ChatStream = None, log_prefix: str = "", shutting_down: bool = False, plugin_config: dict = None, diff --git a/src/plugin_system/base/base_plugin.py b/src/plugin_system/base/base_plugin.py index a9aae4347..b8112a490 100644 --- a/src/plugin_system/base/base_plugin.py +++ b/src/plugin_system/base/base_plugin.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Dict, List, Type, Optional, Any, Union +from typing import Dict, List, Type, Any, Union import os import inspect import toml @@ -29,18 +29,41 @@ class BasePlugin(ABC): """ # 插件基本信息(子类必须定义) - plugin_name: str = "" # 插件内部标识符(如 "hello_world_plugin") - enable_plugin: bool = False # 是否启用插件 - dependencies: List[str] = [] # 依赖的其他插件 - python_dependencies: List[PythonDependency] = [] # Python包依赖 - config_file_name: Optional[str] = None # 配置文件名 + @property + @abstractmethod + def plugin_name(self) -> str: + return "" # 插件内部标识符(如 "hello_world_plugin") + + @property + @abstractmethod + def enable_plugin(self) -> bool: + return True # 是否启用插件 + + @property + @abstractmethod + def dependencies(self) -> List[str]: + return [] # 依赖的其他插件 + + @property + @abstractmethod + def python_dependencies(self) -> List[PythonDependency]: + return [] # Python包依赖 + + @property + @abstractmethod + def config_file_name(self) -> str: + return "" # 配置文件名 # manifest文件相关 manifest_file_name: str = "_manifest.json" # manifest文件名 manifest_data: Dict[str, Any] = {} # manifest数据 # 配置定义 - config_schema: Dict[str, Union[Dict[str, ConfigField], str]] = {} + @property + @abstractmethod + def config_schema(self) -> Dict[str, Union[Dict[str, ConfigField], str]]: + return {} + config_section_descriptions: Dict[str, str] = {} def __init__(self, plugin_dir: str = None): @@ -70,7 +93,8 @@ class BasePlugin(ABC): # 创建插件信息对象 self.plugin_info = PluginInfo( - name=self.display_name, # 使用显示名称 + name=self.plugin_name, + display_name=self.display_name, description=self.plugin_description, version=self.plugin_version, author=self.plugin_author, diff --git a/src/plugin_system/base/component_types.py b/src/plugin_system/base/component_types.py index f720823c6..771fba422 100644 --- a/src/plugin_system/base/component_types.py +++ b/src/plugin_system/base/component_types.py @@ -126,6 +126,7 @@ class CommandInfo(ComponentInfo): class PluginInfo: """插件信息""" + display_name: str # 插件显示名称 name: str # 插件名称 description: str # 插件描述 version: str = "1.0.0" # 插件版本 diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py index a30a3028b..9d6bd805c 100644 --- a/src/plugin_system/core/plugin_manager.py +++ b/src/plugin_system/core/plugin_manager.py @@ -85,16 +85,17 @@ class PluginManager: total_failed_registration = 0 for plugin_name in self.plugin_classes.keys(): - if self.load_registered_plugin_classes(plugin_name): + load_status, count = self.load_registered_plugin_classes(plugin_name) + if load_status: total_registered += 1 else: - total_failed_registration += 1 + total_failed_registration += count self._show_stats(total_registered, total_failed_registration) return total_registered, total_failed_registration - def load_registered_plugin_classes(self, plugin_name: str) -> bool: + def load_registered_plugin_classes(self, plugin_name: str) -> Tuple[bool, int]: # sourcery skip: extract-duplicate-method, extract-method """ 加载已经注册的插件类 @@ -102,7 +103,7 @@ class PluginManager: plugin_class: Type[BasePlugin] = self.plugin_classes.get(plugin_name) if not plugin_class: logger.error(f"插件 {plugin_name} 的插件类未注册或不存在") - return False + return False, 1 try: # 使用记录的插件目录路径 plugin_dir = self.plugin_paths.get(plugin_name) @@ -116,7 +117,7 @@ class PluginManager: # 检查插件是否启用 if not plugin_instance.enable_plugin: logger.info(f"插件 {plugin_name} 已禁用,跳过加载") - return False + return False, 0 # 检查版本兼容性 is_compatible, compatibility_error = self._check_plugin_version_compatibility( @@ -125,22 +126,22 @@ class PluginManager: if not is_compatible: self.failed_plugins[plugin_name] = compatibility_error logger.error(f"❌ 插件加载失败: {plugin_name} - {compatibility_error}") - return False + return False, 1 if plugin_instance.register_plugin(): self.loaded_plugins[plugin_name] = plugin_instance self._show_plugin_components(plugin_name) - return True + return True, 1 else: self.failed_plugins[plugin_name] = "插件注册失败" logger.error(f"❌ 插件注册失败: {plugin_name}") - return False + return False, 1 except FileNotFoundError as e: # manifest文件缺失 error_msg = f"缺少manifest文件: {str(e)}" self.failed_plugins[plugin_name] = error_msg logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") - return False + return False, 1 except ValueError as e: # manifest文件格式错误或验证失败 @@ -148,7 +149,7 @@ class PluginManager: error_msg = f"manifest验证失败: {str(e)}" self.failed_plugins[plugin_name] = error_msg logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") - return False + return False, 1 except Exception as e: # 其他错误 @@ -156,7 +157,7 @@ class PluginManager: self.failed_plugins[plugin_name] = error_msg logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") logger.debug("详细错误信息: ", exc_info=True) - return False + return False, 1 def unload_registered_plugin_module(self, plugin_name: str) -> None: """ @@ -489,14 +490,16 @@ class PluginManager: info_parts = [part for part in [version_info, author_info, license_info] if part] extra_info = f" ({', '.join(info_parts)})" if info_parts else "" - logger.info(f" 📦 {plugin_name}{extra_info}") + logger.info(f" 📦 {plugin_info.display_name}{extra_info}") # Manifest信息 if plugin_info.manifest_data: + """ if plugin_info.keywords: logger.info(f" 🏷️ 关键词: {', '.join(plugin_info.keywords)}") if plugin_info.categories: logger.info(f" 📁 分类: {', '.join(plugin_info.categories)}") + """ if plugin_info.homepage_url: logger.info(f" 🌐 主页: {plugin_info.homepage_url}") @@ -533,9 +536,9 @@ class PluginManager: plugins_in_dir.append(plugin_name) if plugins_in_dir: - logger.info(f" 📁 {directory}: {len(plugins_in_dir)}个插件 ({', '.join(plugins_in_dir)})") + logger.info(f" 📁 {directory}: {len(plugins_in_dir)}个插件 ({', '.join(plugins_in_dir)})") else: - logger.info(f" 📁 {directory}: 0个插件") + logger.info(f" 📁 {directory}: 0个插件") # 失败信息 if total_failed_registration > 0: diff --git a/src/plugins/built_in/core_actions/plugin.py b/src/plugins/built_in/core_actions/plugin.py index 2b7194063..b15e72522 100644 --- a/src/plugins/built_in/core_actions/plugin.py +++ b/src/plugins/built_in/core_actions/plugin.py @@ -136,6 +136,8 @@ class CoreActionsPlugin(BasePlugin): # 插件基本信息 plugin_name = "core_actions" # 内部标识符 enable_plugin = True + dependencies = [] # 插件依赖列表 + python_dependencies = [] # Python包依赖列表 config_file_name = "config.toml" # 配置节描述 diff --git a/src/plugins/built_in/tts_plugin/plugin.py b/src/plugins/built_in/tts_plugin/plugin.py index 5f563e966..7d45f4d30 100644 --- a/src/plugins/built_in/tts_plugin/plugin.py +++ b/src/plugins/built_in/tts_plugin/plugin.py @@ -109,6 +109,8 @@ class TTSPlugin(BasePlugin): # 插件基本信息 plugin_name = "tts_plugin" # 内部标识符 enable_plugin = True + dependencies = [] # 插件依赖列表 + python_dependencies = [] # Python包依赖列表 config_file_name = "config.toml" # 配置节描述 diff --git a/src/plugins/built_in/vtb_plugin/plugin.py b/src/plugins/built_in/vtb_plugin/plugin.py index 2932205b5..e18841f03 100644 --- a/src/plugins/built_in/vtb_plugin/plugin.py +++ b/src/plugins/built_in/vtb_plugin/plugin.py @@ -110,6 +110,8 @@ class VTBPlugin(BasePlugin): # 插件基本信息 plugin_name = "vtb_plugin" # 内部标识符 enable_plugin = True + dependencies = [] # 插件依赖列表 + python_dependencies = [] # Python包依赖列表 config_file_name = "config.toml" # 配置节描述