feat:重构插件api

This commit is contained in:
SengokuCola
2025-06-10 15:28:36 +08:00
parent 2edece11ea
commit 4d32b3052f
30 changed files with 2429 additions and 471 deletions

View File

@@ -6,14 +6,14 @@ import inspect
import toml # 导入 toml 库 import toml # 导入 toml 库
from abc import abstractmethod from abc import abstractmethod
# 导入拆分后的API模块 # 导入新插件系统的API模块
from src.chat.actions.plugin_api.message_api import MessageAPI from src.plugin_system.apis.message_api import MessageAPI
from src.chat.actions.plugin_api.llm_api import LLMAPI from src.plugin_system.apis.llm_api import LLMAPI
from src.chat.actions.plugin_api.database_api import DatabaseAPI from src.plugin_system.apis.database_api import DatabaseAPI
from src.chat.actions.plugin_api.config_api import ConfigAPI from src.plugin_system.apis.config_api import ConfigAPI
from src.chat.actions.plugin_api.utils_api import UtilsAPI from src.plugin_system.apis.utils_api import UtilsAPI
from src.chat.actions.plugin_api.stream_api import StreamAPI from src.plugin_system.apis.stream_api import StreamAPI
from src.chat.actions.plugin_api.hearflow_api import HearflowAPI from src.plugin_system.apis.hearflow_api import HearflowAPI
# 以下为类型注解需要 # 以下为类型注解需要
from src.chat.message_receive.chat_stream import ChatStream # noqa from src.chat.message_receive.chat_stream import ChatStream # noqa
@@ -25,9 +25,14 @@ logger = get_logger("plugin_action")
class PluginAction(BaseAction, MessageAPI, LLMAPI, DatabaseAPI, ConfigAPI, UtilsAPI, StreamAPI, HearflowAPI): class PluginAction(BaseAction, MessageAPI, LLMAPI, DatabaseAPI, ConfigAPI, UtilsAPI, StreamAPI, HearflowAPI):
"""插件动作基类 """插件动作基类(旧版兼容)
封装了主程序内部依赖提供简化的API接口给插件开发者 封装了主程序内部依赖提供简化的API接口给插件开发者
⚠️ 此类已弃用,建议使用新的插件系统:
- 新基类src.plugin_system.base.BaseAction
- 新APIsrc.plugin_system.plugin_api
- 新注册:@register_component 装饰器
""" """
action_config_file_name: Optional[str] = None # 插件可以覆盖此属性来指定配置文件名 action_config_file_name: Optional[str] = None # 插件可以覆盖此属性来指定配置文件名

View File

@@ -10,7 +10,7 @@ from src.experimental.PFC.pfc_manager import PFCManager
from src.chat.focus_chat.heartflow_message_processor import HeartFCMessageReceiver from src.chat.focus_chat.heartflow_message_processor import HeartFCMessageReceiver
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.config.config import global_config from src.config.config import global_config
from src.chat.command.command_handler import command_manager # 导入命令管理器 from src.plugin_system.core.component_registry import component_registry # 导入新插件系统
# 定义日志配置 # 定义日志配置
@@ -48,6 +48,54 @@ class ChatBot:
except Exception as e: except Exception as e:
logger.error(f"创建PFC聊天失败: {e}") logger.error(f"创建PFC聊天失败: {e}")
async def _process_commands_with_new_system(self, message: MessageRecv):
"""使用新插件系统处理命令"""
try:
if not message.processed_plain_text:
await message.process()
text = message.processed_plain_text
# 使用新的组件注册中心查找命令
command_result = component_registry.find_command_by_text(text)
if command_result:
command_class, matched_groups = command_result
# 创建命令实例
command_instance = command_class(message)
command_instance.set_matched_groups(matched_groups)
try:
# 执行命令
success, response = await command_instance.execute()
# 记录命令执行结果
if success:
logger.info(f"命令执行成功: {command_class.__name__}")
else:
logger.warning(f"命令执行失败: {command_class.__name__} - {response}")
return True, response, False # 找到命令,不继续处理
except Exception as e:
logger.error(f"执行命令时出错: {command_class.__name__} - {e}")
import traceback
logger.error(traceback.format_exc())
try:
await command_instance.send_reply(f"命令执行出错: {str(e)}")
except Exception as send_error:
logger.error(f"发送错误消息失败: {send_error}")
return True, str(e), False # 命令出错,不继续处理
# 没有找到命令,继续处理消息
return False, None, True
except Exception as e:
logger.error(f"处理命令时出错: {e}")
return False, None, True # 出错时继续处理消息
async def message_process(self, message_data: Dict[str, Any]) -> None: async def message_process(self, message_data: Dict[str, Any]) -> None:
"""处理转化后的统一格式消息 """处理转化后的统一格式消息
这个函数本质是预处理一些数据,根据配置信息和消息内容,预处理消息,并分发到合适的消息处理器中 这个函数本质是预处理一些数据,根据配置信息和消息内容,预处理消息,并分发到合适的消息处理器中
@@ -91,8 +139,8 @@ class ChatBot:
# 处理消息内容,生成纯文本 # 处理消息内容,生成纯文本
await message.process() await message.process()
# 命令处理 - 在消息处理的早期阶段检查并处理命令 # 命令处理 - 使用新插件系统检查并处理命令
is_command, cmd_result, continue_process = await command_manager.process_command(message) is_command, cmd_result, continue_process = await self._process_commands_with_new_system(message)
# 如果是命令且不需要继续处理,则直接返回 # 如果是命令且不需要继续处理,则直接返回
if is_command and not continue_process: if is_command and not continue_process:

View File

@@ -22,6 +22,11 @@ from .api.main import start_api_server
# 导入actions模块确保装饰器被执行 # 导入actions模块确保装饰器被执行
import src.chat.actions.default_actions # noqa import src.chat.actions.default_actions # noqa
# 导入新的插件管理器
from src.plugin_system.core.plugin_manager import plugin_manager
# 导入消息API和traceback模块
from src.common.message import global_api
import traceback
# 条件导入记忆系统 # 条件导入记忆系统
if global_config.memory.enable_memory: if global_config.memory.enable_memory:
@@ -45,8 +50,6 @@ class MainSystem:
self.individuality: Individuality = individuality self.individuality: Individuality = individuality
# 使用消息API替代直接的FastAPI实例 # 使用消息API替代直接的FastAPI实例
from src.common.message import global_api
self.app: MessageServer = global_api self.app: MessageServer = global_api
self.server: Server = global_server self.server: Server = global_server
@@ -134,29 +137,15 @@ class MainSystem:
raise raise
def _load_all_actions(self): def _load_all_actions(self):
"""加载所有actions和commands使用统一的插件加载器""" """加载所有actions和commands使用的插件系统"""
try: try:
# 导入统一的插件加载器 # 使用新的插件管理器加载所有插件
from src.plugins.plugin_loader import plugin_loader plugin_count, component_count = plugin_manager.load_all_plugins()
# 使用统一的插件加载器加载所有插件组件 logger.success(f"插件系统加载成功: {plugin_count} 个插件,{component_count} 个组件")
loaded_actions, loaded_commands = plugin_loader.load_all_plugins()
# 加载命令处理系统
try:
# 导入命令处理系统
logger.success("命令处理系统加载成功")
except Exception as e:
logger.error(f"加载命令处理系统失败: {e}")
import traceback
logger.error(traceback.format_exc())
except Exception as e: except Exception as e:
logger.error(f"加载插件失败: {e}") logger.error(f"加载插件失败: {e}")
import traceback
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
async def schedule_tasks(self): async def schedule_tasks(self):

169
src/plugin_system/README.md Normal file
View File

@@ -0,0 +1,169 @@
# MaiBot 插件系统 - 重构版
## 目录结构说明
经过重构,插件系统现在采用清晰的**系统核心**与**插件内容**分离的架构:
```
src/
├── plugin_system/ # 🔧 系统核心 - 插件框架本身
│ ├── __init__.py # 统一导出接口
│ ├── core/ # 核心管理
│ │ ├── plugin_manager.py
│ │ ├── component_registry.py
│ │ └── __init__.py
│ ├── apis/ # API接口
│ │ ├── plugin_api.py # 统一API聚合
│ │ ├── message_api.py
│ │ ├── llm_api.py
│ │ ├── database_api.py
│ │ ├── config_api.py
│ │ ├── utils_api.py
│ │ ├── stream_api.py
│ │ ├── hearflow_api.py
│ │ └── __init__.py
│ ├── base/ # 基础类
│ │ ├── base_plugin.py
│ │ ├── base_action.py
│ │ ├── base_command.py
│ │ ├── component_types.py
│ │ └── __init__.py
│ └── registry/ # 注册相关(预留)
└── plugins/ # 🔌 插件内容 - 具体的插件实现
├── built_in/ # 内置插件
│ ├── system_actions/ # 系统内置Action
│ └── system_commands/# 系统内置Command
└── examples/ # 示例插件
└── simple_plugin/
├── plugin.py
└── config.toml
```
## 架构优势
### 1. 职责清晰
- **`src/plugin_system/`** - 系统提供的框架、API和基础设施
- **`src/plugins/`** - 用户开发或使用的具体插件
### 2. 导入简化
```python
# 统一导入接口
from src.plugin_system import (
BasePlugin, register_plugin, BaseAction, BaseCommand,
ActionInfo, CommandInfo, PluginAPI
)
```
### 3. 模块化设计
- 各个子模块都有清晰的职责和接口
- 支持按需导入特定功能
- 便于维护和扩展
## 快速开始
### 创建简单插件
```python
from src.plugin_system import BasePlugin, register_plugin, BaseAction, ActionInfo
class MyAction(BaseAction):
async def execute(self):
return True, "Hello from my plugin!"
@register_plugin
class MyPlugin(BasePlugin):
plugin_name = "my_plugin"
plugin_description = "我的第一个插件"
def get_plugin_components(self):
return [(
ActionInfo(name="my_action", description="我的动作"),
MyAction
)]
```
### 使用系统API
```python
class MyAction(BaseAction):
async def execute(self):
# 发送消息
await self.api.send_text_to_group(
self.api.get_service("chat_stream"),
"Hello World!"
)
# 数据库操作
data = await self.api.db_get("table", "key")
# LLM调用
response = await self.api.llm_text_request("你好")
return True, response
```
## 兼容性迁移
### 现有Action迁移
```python
# 旧方式
from src.chat.actions.base_action import BaseAction, register_action
# 新方式
from src.plugin_system import BaseAction, register_plugin
from src.plugin_system.base.component_types import ActionInfo
# 将Action封装到Plugin中
@register_plugin
class MyActionPlugin(BasePlugin):
plugin_name = "my_action_plugin"
def get_plugin_components(self):
return [(ActionInfo(...), MyAction)]
```
### 现有Command迁移
```python
# 旧方式
from src.chat.command.command_handler import BaseCommand, register_command
# 新方式
from src.plugin_system import BaseCommand, register_plugin
from src.plugin_system.base.component_types import CommandInfo
# 将Command封装到Plugin中
@register_plugin
class MyCommandPlugin(BasePlugin):
plugin_name = "my_command_plugin"
def get_plugin_components(self):
return [(CommandInfo(...), MyCommand)]
```
## 扩展指南
### 添加新的组件类型
1.`component_types.py` 中定义新的组件类型
2.`component_registry.py` 中添加对应的注册逻辑
3. 创建对应的基类
### 添加新的API
1.`apis/` 目录下创建新的API模块
2.`plugin_api.py` 中集成新API
3. 更新 `__init__.py` 导出接口
## 最佳实践
1. **单一插件包含相关组件** - 一个插件可以包含多个相关的Action和Command
2. **使用配置文件** - 通过TOML配置文件管理插件行为
3. **合理的组件命名** - 使用描述性的组件名称
4. **充分的错误处理** - 在组件中妥善处理异常
5. **详细的文档** - 为插件和组件编写清晰的文档
## 内置插件规划
- **系统核心插件** - 将现有的内置Action/Command迁移为系统插件
- **工具插件** - 常用的工具和实用功能
- **示例插件** - 帮助开发者学习的示例代码
这个重构保持了向后兼容性,同时提供了更清晰、更易维护的架构。

View File

@@ -0,0 +1,47 @@
"""
MaiBot 插件系统
提供统一的插件开发和管理框架
"""
# 导出主要的公共接口
from src.plugin_system.base.base_plugin import BasePlugin, register_plugin
from src.plugin_system.base.base_action import BaseAction
from src.plugin_system.base.base_command import BaseCommand
from src.plugin_system.base.component_types import (
ComponentType, ActionActivationType, ChatMode,
ComponentInfo, ActionInfo, CommandInfo, PluginInfo
)
from src.plugin_system.apis.plugin_api import PluginAPI, create_plugin_api, create_command_api
from src.plugin_system.core.plugin_manager import plugin_manager
from src.plugin_system.core.component_registry import component_registry
__version__ = "1.0.0"
__all__ = [
# 基础类
'BasePlugin',
'BaseAction',
'BaseCommand',
# 类型定义
'ComponentType',
'ActionActivationType',
'ChatMode',
'ComponentInfo',
'ActionInfo',
'CommandInfo',
'PluginInfo',
# API接口
'PluginAPI',
'create_plugin_api',
'create_command_api',
# 管理器
'plugin_manager',
'component_registry',
# 装饰器
'register_plugin',
]

View File

@@ -0,0 +1,172 @@
# API使用指南
插件系统提供了多种API访问方式根据使用场景选择合适的API类。
## 📊 API分类
### 🔗 ActionAPI - 需要Action依赖
**适用场景**在Action组件中使用需要访问聊天上下文
```python
from src.plugin_system.apis import ActionAPI
class MyAction(BaseAction):
async def execute(self):
# Action已内置ActionAPI可以直接使用
await self.api.send_message("text", "Hello")
await self.api.store_action_info(action_prompt_display="执行了动作")
```
**包含功能**
- ✅ 发送消息需要chat_stream、expressor等
- ✅ 数据库操作需要thinking_id、action_data等
### 🔧 IndependentAPI - 独立功能
**适用场景**在Command组件中使用或需要独立工具功能
```python
from src.plugin_system.apis import IndependentAPI
class MyCommand(BaseCommand):
async def execute(self):
# 创建独立API实例
api = IndependentAPI(log_prefix="[MyCommand]")
# 使用独立功能
models = api.get_available_models()
config = api.get_global_config("some_key")
timestamp = api.get_timestamp()
```
**包含功能**
- ✅ LLM模型调用
- ✅ 配置读取
- ✅ 工具函数时间、文件、ID生成等
- ✅ 聊天流查询
- ✅ 心流状态控制
### ⚡ StaticAPI - 静态访问
**适用场景**:简单工具调用,不需要实例化
```python
from src.plugin_system.apis import StaticAPI
# 直接调用静态方法
models = StaticAPI.get_available_models()
config = StaticAPI.get_global_config("bot.nickname")
timestamp = StaticAPI.get_timestamp()
unique_id = StaticAPI.generate_unique_id()
# 异步方法
result = await StaticAPI.generate_with_model(prompt, model_config)
chat_stream = StaticAPI.get_chat_stream_by_group_id("123456")
```
## 🎯 使用建议
### Action组件开发
```python
class MyAction(BaseAction):
# 激活条件直接在类中定义
focus_activation_type = ActionActivationType.KEYWORD
activation_keywords = ["测试"]
async def execute(self):
# 使用内置的ActionAPI
success = await self.api.send_message("text", "处理中...")
# 存储执行记录
await self.api.store_action_info(
action_prompt_display="执行了测试动作"
)
return True, "完成"
```
### Command组件开发
```python
class MyCommand(BaseCommand):
# 命令模式直接在类中定义
command_pattern = r"^/test\s+(?P<param>\w+)$"
command_help = "测试命令"
async def execute(self):
# 使用独立API
api = IndependentAPI(log_prefix="[TestCommand]")
# 获取配置
max_length = api.get_global_config("test.max_length", 100)
# 生成内容(如果需要)
if api.get_available_models():
models = api.get_available_models()
first_model = list(models.values())[0]
success, response, _, _ = await api.generate_with_model(
"生成测试回复", first_model
)
if success:
await self.send_reply(response)
```
### 独立工具使用
```python
# 不在插件环境中的独立使用
from src.plugin_system.apis import StaticAPI
def some_utility_function():
# 获取配置
bot_name = StaticAPI.get_global_config("bot.nickname", "Bot")
# 生成ID
request_id = StaticAPI.generate_unique_id()
# 格式化时间
current_time = StaticAPI.format_time()
return f"{bot_name}_{request_id}_{current_time}"
```
## 🔄 迁移指南
### 从原PluginAPI迁移
**原来的用法**
```python
# 原来需要导入完整PluginAPI
from src.plugin_system.apis import PluginAPI
api = PluginAPI(chat_stream=..., expressor=...)
await api.send_message("text", "Hello")
config = api.get_global_config("key")
```
**新的用法**
```python
# 方式1继续使用原PluginAPI不变
from src.plugin_system.apis import PluginAPI
# 方式2使用分类API推荐
from src.plugin_system.apis import ActionAPI, IndependentAPI
# Action相关功能
action_api = ActionAPI(chat_stream=..., expressor=...)
await action_api.send_message("text", "Hello")
# 独立功能
config = IndependentAPI().get_global_config("key")
# 或者
config = StaticAPI.get_global_config("key")
```
## 📋 API对照表
| 功能类别 | 原PluginAPI | ActionAPI | IndependentAPI | StaticAPI |
|---------|-------------|-----------|----------------|-----------|
| 发送消息 | ✅ | ✅ | ❌ | ❌ |
| 数据库操作 | ✅ | ✅ | ❌ | ❌ |
| LLM调用 | ✅ | ❌ | ✅ | ✅ |
| 配置读取 | ✅ | ❌ | ✅ | ✅ |
| 工具函数 | ✅ | ❌ | ✅ | ✅ |
| 聊天流查询 | ✅ | ❌ | ✅ | ✅ |
| 心流控制 | ✅ | ❌ | ✅ | ✅ |
这样的分类让插件开发者可以更明确地知道需要什么样的API避免不必要的依赖注入。

View File

@@ -0,0 +1,37 @@
"""
插件API模块
提供插件可以使用的各种API接口
"""
from src.plugin_system.apis.plugin_api import PluginAPI, create_plugin_api, create_command_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
# 新增分类的API聚合
from src.plugin_system.apis.action_apis import ActionAPI
from src.plugin_system.apis.independent_apis import IndependentAPI, StaticAPI
__all__ = [
# 原有统一API
'PluginAPI',
'create_plugin_api',
'create_command_api',
# 原有单独API
'MessageAPI',
'LLMAPI',
'DatabaseAPI',
'ConfigAPI',
'UtilsAPI',
'StreamAPI',
'HearflowAPI',
# 新增分类API
'ActionAPI', # 需要Action依赖的API
'IndependentAPI', # 独立API
'StaticAPI', # 静态API
]

View File

@@ -0,0 +1,85 @@
"""
Action相关API聚合模块
聚合了需要Action组件依赖的API这些API需要通过Action初始化时注入的服务对象才能正常工作。
包括MessageAPI、DatabaseAPI等需要chat_stream、expressor等服务的API。
"""
from src.plugin_system.apis.message_api import MessageAPI
from src.plugin_system.apis.database_api import DatabaseAPI
from src.common.logger_manager import get_logger
logger = get_logger("action_apis")
class ActionAPI(MessageAPI, DatabaseAPI):
"""
Action相关API聚合类
聚合了需要Action组件依赖的API功能。这些API需要以下依赖
- _services: 包含chat_stream、expressor、replyer、observations等服务对象
- log_prefix: 日志前缀
- thinking_id: 思考ID
- cycle_timers: 计时器
- action_data: Action数据
使用场景:
- 在Action组件中使用需要发送消息、存储数据等功能
- 需要访问聊天上下文和执行环境的操作
"""
def __init__(self,
chat_stream=None,
expressor=None,
replyer=None,
observations=None,
log_prefix: str = "[ActionAPI]",
thinking_id: str = "",
cycle_timers: dict = None,
action_data: dict = None):
"""
初始化Action相关API
Args:
chat_stream: 聊天流对象
expressor: 表达器对象
replyer: 回复器对象
observations: 观察列表
log_prefix: 日志前缀
thinking_id: 思考ID
cycle_timers: 计时器字典
action_data: Action数据
"""
# 存储依赖对象
self._services = {
"chat_stream": chat_stream,
"expressor": expressor,
"replyer": replyer,
"observations": observations or []
}
self.log_prefix = log_prefix
self.thinking_id = thinking_id
self.cycle_timers = cycle_timers or {}
self.action_data = action_data or {}
logger.debug(f"{self.log_prefix} ActionAPI 初始化完成")
def set_chat_stream(self, chat_stream):
"""设置聊天流对象"""
self._services["chat_stream"] = chat_stream
logger.debug(f"{self.log_prefix} 设置聊天流")
def set_expressor(self, expressor):
"""设置表达器对象"""
self._services["expressor"] = expressor
logger.debug(f"{self.log_prefix} 设置表达器")
def set_replyer(self, replyer):
"""设置回复器对象"""
self._services["replyer"] = replyer
logger.debug(f"{self.log_prefix} 设置回复器")
def set_observations(self, observations):
"""设置观察列表"""
self._services["observations"] = observations or []
logger.debug(f"{self.log_prefix} 设置观察列表")

View File

@@ -0,0 +1,132 @@
"""
独立API聚合模块
聚合了不需要Action组件依赖的API这些API可以独立使用不需要注入服务对象。
包括LLMAPI、ConfigAPI、UtilsAPI、StreamAPI、HearflowAPI等独立功能的API。
"""
from src.plugin_system.apis.llm_api import LLMAPI
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.common.logger_manager import get_logger
logger = get_logger("independent_apis")
class IndependentAPI(LLMAPI, ConfigAPI, UtilsAPI, StreamAPI, HearflowAPI):
"""
独立API聚合类
聚合了不需要Action组件依赖的API功能。这些API的特点
- 不需要chat_stream、expressor等服务对象
- 可以独立调用不依赖Action执行上下文
- 主要是工具类方法和配置查询方法
包含的API
- LLMAPI: LLM模型调用仅需要全局配置
- ConfigAPI: 配置读取(使用全局配置)
- UtilsAPI: 工具方法(文件操作、时间处理等)
- StreamAPI: 聊天流查询使用ChatManager
- HearflowAPI: 心流状态控制使用heartflow
使用场景:
- 在Command组件中使用
- 独立的工具函数调用
- 配置查询和系统状态检查
"""
def __init__(self, log_prefix: str = "[IndependentAPI]"):
"""
初始化独立API
Args:
log_prefix: 日志前缀,用于区分不同的调用来源
"""
self.log_prefix = log_prefix
logger.debug(f"{self.log_prefix} IndependentAPI 初始化完成")
# 提供便捷的静态访问方式
class StaticAPI:
"""
静态API类
提供完全静态的API访问方式不需要实例化适合简单的工具调用。
"""
# LLM相关
@staticmethod
def get_available_models():
"""获取可用的LLM模型"""
api = LLMAPI()
return api.get_available_models()
@staticmethod
async def generate_with_model(prompt: str, model_config: dict, **kwargs):
"""使用LLM生成内容"""
api = LLMAPI()
api.log_prefix = "[StaticAPI]"
return await api.generate_with_model(prompt, model_config, **kwargs)
# 配置相关
@staticmethod
def get_global_config(key: str, default=None):
"""获取全局配置"""
api = ConfigAPI()
return api.get_global_config(key, default)
@staticmethod
async def get_user_id_by_name(person_name: str):
"""根据用户名获取用户ID"""
api = ConfigAPI()
return await api.get_user_id_by_person_name(person_name)
# 工具相关
@staticmethod
def get_timestamp():
"""获取当前时间戳"""
api = UtilsAPI()
return api.get_timestamp()
@staticmethod
def format_time(timestamp=None, format_str="%Y-%m-%d %H:%M:%S"):
"""格式化时间"""
api = UtilsAPI()
return api.format_time(timestamp, format_str)
@staticmethod
def generate_unique_id():
"""生成唯一ID"""
api = UtilsAPI()
return api.generate_unique_id()
# 聊天流相关
@staticmethod
def get_chat_stream_by_group_id(group_id: str, platform: str = "qq"):
"""通过群ID获取聊天流"""
api = StreamAPI()
api.log_prefix = "[StaticAPI]"
return api.get_chat_stream_by_group_id(group_id, platform)
@staticmethod
def get_all_group_chat_streams(platform: str = "qq"):
"""获取所有群聊聊天流"""
api = StreamAPI()
api.log_prefix = "[StaticAPI]"
return api.get_all_group_chat_streams(platform)
# 心流相关
@staticmethod
async def get_sub_hearflow_by_chat_id(chat_id: str):
"""获取子心流"""
api = HearflowAPI()
api.log_prefix = "[StaticAPI]"
return await api.get_sub_hearflow_by_chat_id(chat_id)
@staticmethod
async def set_sub_hearflow_chat_state(chat_id: str, target_state):
"""设置子心流状态"""
api = HearflowAPI()
api.log_prefix = "[StaticAPI]"
return await api.set_sub_hearflow_chat_state(chat_id, target_state)

View File

@@ -173,15 +173,19 @@ class MessageAPI:
bool: 是否发送成功 bool: 是否发送成功
""" """
try: try:
expressor: DefaultExpressor = self._services.get("expressor") # 安全获取服务和日志前缀
chat_stream: ChatStream = self._services.get("chat_stream") services = getattr(self, '_services', {})
log_prefix = getattr(self, 'log_prefix', '[MessageAPI]')
expressor: DefaultExpressor = services.get("expressor")
chat_stream: ChatStream = services.get("chat_stream")
if not expressor or not chat_stream: if not expressor or not chat_stream:
logger.error(f"{self.log_prefix} 无法发送消息:缺少必要的内部服务") logger.error(f"{log_prefix} 无法发送消息:缺少必要的内部服务")
return False return False
# 获取锚定消息(如果有) # 获取锚定消息(如果有)
observations = self._services.get("observations", []) observations = services.get("observations", [])
if len(observations) > 0: if len(observations) > 0:
chatting_observation: ChattingObservation = next( chatting_observation: ChattingObservation = next(
@@ -197,7 +201,7 @@ class MessageAPI:
# 如果没有找到锚点消息,创建一个占位符 # 如果没有找到锚点消息,创建一个占位符
if not anchor_message: if not anchor_message:
logger.info(f"{self.log_prefix} 未找到锚点消息,创建占位符") logger.info(f"{log_prefix} 未找到锚点消息,创建占位符")
anchor_message = await create_empty_anchor_message( anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream chat_stream.platform, chat_stream.group_info, chat_stream
) )
@@ -217,7 +221,8 @@ class MessageAPI:
return success return success
except Exception as e: except Exception as e:
logger.error(f"{self.log_prefix} 发送消息时出错: {e}") log_prefix = getattr(self, 'log_prefix', '[MessageAPI]')
logger.error(f"{log_prefix} 发送消息时出错: {e}")
traceback.print_exc() traceback.print_exc()
return False return False
@@ -231,18 +236,22 @@ class MessageAPI:
Returns: Returns:
bool: 是否发送成功 bool: 是否发送成功
""" """
expressor: DefaultExpressor = self._services.get("expressor") # 安全获取服务和日志前缀
chat_stream: ChatStream = self._services.get("chat_stream") services = getattr(self, '_services', {})
log_prefix = getattr(self, 'log_prefix', '[MessageAPI]')
expressor: DefaultExpressor = services.get("expressor")
chat_stream: ChatStream = services.get("chat_stream")
if not expressor or not chat_stream: if not expressor or not chat_stream:
logger.error(f"{self.log_prefix} 无法发送消息:缺少必要的内部服务") logger.error(f"{log_prefix} 无法发送消息:缺少必要的内部服务")
return False return False
# 构造简化的动作数据 # 构造简化的动作数据
reply_data = {"text": text, "target": target or "", "emojis": []} reply_data = {"text": text, "target": target or "", "emojis": []}
# 获取锚定消息(如果有) # 获取锚定消息(如果有)
observations = self._services.get("observations", []) observations = services.get("observations", [])
# 查找 ChattingObservation 实例 # 查找 ChattingObservation 实例
chatting_observation = None chatting_observation = None
@@ -252,14 +261,14 @@ class MessageAPI:
break break
if not chatting_observation: if not chatting_observation:
logger.warning(f"{self.log_prefix} 未找到 ChattingObservation 实例,创建占位符") logger.warning(f"{log_prefix} 未找到 ChattingObservation 实例,创建占位符")
anchor_message = await create_empty_anchor_message( anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream chat_stream.platform, chat_stream.group_info, chat_stream
) )
else: else:
anchor_message = chatting_observation.search_message_by_text(reply_data["target"]) anchor_message = chatting_observation.search_message_by_text(reply_data["target"])
if not anchor_message: if not anchor_message:
logger.info(f"{self.log_prefix} 未找到锚点消息,创建占位符") logger.info(f"{log_prefix} 未找到锚点消息,创建占位符")
anchor_message = await create_empty_anchor_message( anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream chat_stream.platform, chat_stream.group_info, chat_stream
) )
@@ -267,12 +276,16 @@ class MessageAPI:
anchor_message.update_chat_stream(chat_stream) anchor_message.update_chat_stream(chat_stream)
# 调用内部方法发送消息 # 调用内部方法发送消息
cycle_timers = getattr(self, 'cycle_timers', {})
reasoning = getattr(self, 'reasoning', '插件生成')
thinking_id = getattr(self, 'thinking_id', 'plugin_thinking')
success, _ = await expressor.deal_reply( success, _ = await expressor.deal_reply(
cycle_timers=self.cycle_timers, cycle_timers=cycle_timers,
action_data=reply_data, action_data=reply_data,
anchor_message=anchor_message, anchor_message=anchor_message,
reasoning=self.reasoning, reasoning=reasoning,
thinking_id=self.thinking_id, thinking_id=thinking_id,
) )
return success return success
@@ -289,18 +302,22 @@ class MessageAPI:
Returns: Returns:
bool: 是否发送成功 bool: 是否发送成功
""" """
replyer: DefaultReplyer = self._services.get("replyer") # 安全获取服务和日志前缀
chat_stream: ChatStream = self._services.get("chat_stream") services = getattr(self, '_services', {})
log_prefix = getattr(self, 'log_prefix', '[MessageAPI]')
replyer: DefaultReplyer = services.get("replyer")
chat_stream: ChatStream = services.get("chat_stream")
if not replyer or not chat_stream: if not replyer or not chat_stream:
logger.error(f"{self.log_prefix} 无法发送消息:缺少必要的内部服务") logger.error(f"{log_prefix} 无法发送消息:缺少必要的内部服务")
return False return False
# 构造简化的动作数据 # 构造简化的动作数据
reply_data = {"target": target or "", "extra_info_block": extra_info_block} reply_data = {"target": target or "", "extra_info_block": extra_info_block}
# 获取锚定消息(如果有) # 获取锚定消息(如果有)
observations = self._services.get("observations", []) observations = services.get("observations", [])
# 查找 ChattingObservation 实例 # 查找 ChattingObservation 实例
chatting_observation = None chatting_observation = None
@@ -310,14 +327,14 @@ class MessageAPI:
break break
if not chatting_observation: if not chatting_observation:
logger.warning(f"{self.log_prefix} 未找到 ChattingObservation 实例,创建占位符") logger.warning(f"{log_prefix} 未找到 ChattingObservation 实例,创建占位符")
anchor_message = await create_empty_anchor_message( anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream chat_stream.platform, chat_stream.group_info, chat_stream
) )
else: else:
anchor_message = chatting_observation.search_message_by_text(reply_data["target"]) anchor_message = chatting_observation.search_message_by_text(reply_data["target"])
if not anchor_message: if not anchor_message:
logger.info(f"{self.log_prefix} 未找到锚点消息,创建占位符") logger.info(f"{log_prefix} 未找到锚点消息,创建占位符")
anchor_message = await create_empty_anchor_message( anchor_message = await create_empty_anchor_message(
chat_stream.platform, chat_stream.group_info, chat_stream chat_stream.platform, chat_stream.group_info, chat_stream
) )
@@ -325,12 +342,16 @@ class MessageAPI:
anchor_message.update_chat_stream(chat_stream) anchor_message.update_chat_stream(chat_stream)
# 调用内部方法发送消息 # 调用内部方法发送消息
cycle_timers = getattr(self, 'cycle_timers', {})
reasoning = getattr(self, 'reasoning', '插件生成')
thinking_id = getattr(self, 'thinking_id', 'plugin_thinking')
success, _ = await replyer.deal_reply( success, _ = await replyer.deal_reply(
cycle_timers=self.cycle_timers, cycle_timers=cycle_timers,
action_data=reply_data, action_data=reply_data,
anchor_message=anchor_message, anchor_message=anchor_message,
reasoning=self.reasoning, reasoning=reasoning,
thinking_id=self.thinking_id, thinking_id=thinking_id,
) )
return success return success
@@ -341,7 +362,8 @@ class MessageAPI:
Returns: Returns:
str: 聊天类型 ("group" "private") str: 聊天类型 ("group" "private")
""" """
chat_stream: ChatStream = self._services.get("chat_stream") services = getattr(self, '_services', {})
chat_stream: ChatStream = services.get("chat_stream")
if chat_stream and hasattr(chat_stream, "group_info"): if chat_stream and hasattr(chat_stream, "group_info"):
return "group" if chat_stream.group_info else "private" return "group" if chat_stream.group_info else "private"
return "unknown" return "unknown"
@@ -356,7 +378,8 @@ class MessageAPI:
List[Dict]: 消息列表每个消息包含发送者内容等信息 List[Dict]: 消息列表每个消息包含发送者内容等信息
""" """
messages = [] messages = []
observations = self._services.get("observations", []) services = getattr(self, '_services', {})
observations = services.get("observations", [])
if observations and len(observations) > 0: if observations and len(observations) > 0:
obs = observations[0] obs = observations[0]

View File

@@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
"""
统一的插件API聚合模块
提供所有插件API功能的统一访问入口
"""
from typing import Dict, Any, Optional
from src.common.logger_manager import get_logger
# 导入所有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
logger = get_logger("plugin_api")
class PluginAPI(MessageAPI, LLMAPI, DatabaseAPI, ConfigAPI, UtilsAPI, StreamAPI, HearflowAPI):
"""
插件API聚合类
集成了所有可供插件使用的API功能提供统一的访问接口。
插件组件可以直接使用此API实例来访问各种功能。
特性:
- 聚合所有API模块的功能
- 支持依赖注入和配置
- 提供统一的错误处理和日志记录
"""
def __init__(self,
chat_stream=None,
expressor=None,
replyer=None,
observations=None,
log_prefix: str = "[PluginAPI]"):
"""
初始化插件API
Args:
chat_stream: 聊天流对象
expressor: 表达器对象
replyer: 回复器对象
observations: 观察列表
log_prefix: 日志前缀
"""
# 存储依赖对象
self._services = {
"chat_stream": chat_stream,
"expressor": expressor,
"replyer": replyer,
"observations": observations or []
}
self.log_prefix = log_prefix
# 调用所有父类的初始化
super().__init__()
logger.debug(f"{self.log_prefix} PluginAPI 初始化完成")
def set_chat_stream(self, chat_stream):
"""设置聊天流对象"""
self._services["chat_stream"] = chat_stream
logger.debug(f"{self.log_prefix} 设置聊天流: {getattr(chat_stream, 'stream_id', 'Unknown')}")
def set_expressor(self, expressor):
"""设置表达器对象"""
self._services["expressor"] = expressor
logger.debug(f"{self.log_prefix} 设置表达器")
def set_replyer(self, replyer):
"""设置回复器对象"""
self._services["replyer"] = replyer
logger.debug(f"{self.log_prefix} 设置回复器")
def set_observations(self, observations):
"""设置观察列表"""
self._services["observations"] = observations or []
logger.debug(f"{self.log_prefix} 设置观察列表,数量: {len(observations or [])}")
def get_service(self, service_name: str):
"""获取指定的服务对象"""
return self._services.get(service_name)
def has_service(self, service_name: str) -> bool:
"""检查是否有指定的服务对象"""
return service_name in self._services and self._services[service_name] is not None
# 便捷的工厂函数
def create_plugin_api(chat_stream=None,
expressor=None,
replyer=None,
observations=None,
log_prefix: str = "[Plugin]") -> PluginAPI:
"""
创建插件API实例的便捷函数
Args:
chat_stream: 聊天流对象
expressor: 表达器对象
replyer: 回复器对象
observations: 观察列表
log_prefix: 日志前缀
Returns:
PluginAPI: 配置好的插件API实例
"""
return PluginAPI(
chat_stream=chat_stream,
expressor=expressor,
replyer=replyer,
observations=observations,
log_prefix=log_prefix
)
def create_command_api(message, log_prefix: str = "[Command]") -> PluginAPI:
"""
为命令创建插件API实例的便捷函数
Args:
message: 消息对象,应该包含 chat_stream 等信息
log_prefix: 日志前缀
Returns:
PluginAPI: 配置好的插件API实例
"""
chat_stream = getattr(message, 'chat_stream', None)
api = PluginAPI(
chat_stream=chat_stream,
log_prefix=log_prefix
)
return api
# 导出主要接口
__all__ = [
'PluginAPI',
'create_plugin_api',
'create_command_api',
# 也可以导出各个API类供单独使用
'MessageAPI',
'LLMAPI',
'DatabaseAPI',
'ConfigAPI',
'UtilsAPI',
'StreamAPI',
'HearflowAPI'
]

View File

@@ -0,0 +1,27 @@
"""
插件基础类模块
提供插件开发的基础类和类型定义
"""
from src.plugin_system.base.base_plugin import BasePlugin, register_plugin
from src.plugin_system.base.base_action import BaseAction
from src.plugin_system.base.base_command import BaseCommand
from src.plugin_system.base.component_types import (
ComponentType, ActionActivationType, ChatMode,
ComponentInfo, ActionInfo, CommandInfo, PluginInfo
)
__all__ = [
'BasePlugin',
'BaseAction',
'BaseCommand',
'register_plugin',
'ComponentType',
'ActionActivationType',
'ChatMode',
'ComponentInfo',
'ActionInfo',
'CommandInfo',
'PluginInfo',
]

View File

@@ -0,0 +1,120 @@
from abc import ABC, abstractmethod
from typing import Tuple, Dict, Any, Optional
from src.common.logger_manager import get_logger
from src.plugin_system.apis.plugin_api import PluginAPI
from src.plugin_system.base.component_types import ActionActivationType, ChatMode, ActionInfo, ComponentType
logger = get_logger("base_action")
class BaseAction(ABC):
"""Action组件基类
Action是插件的一种组件类型用于处理聊天中的动作逻辑
子类可以通过类属性定义激活条件:
- focus_activation_type: 专注模式激活类型
- normal_activation_type: 普通模式激活类型
- activation_keywords: 激活关键词列表
- keyword_case_sensitive: 关键词是否区分大小写
- mode_enable: 启用的聊天模式
- parallel_action: 是否允许并行执行
- random_activation_probability: 随机激活概率
- llm_judge_prompt: LLM判断提示词
"""
# 默认激活设置(子类可以覆盖)
focus_activation_type: ActionActivationType = ActionActivationType.NEVER
normal_activation_type: ActionActivationType = ActionActivationType.NEVER
activation_keywords: list = []
keyword_case_sensitive: bool = False
mode_enable: ChatMode = ChatMode.ALL
parallel_action: bool = True
random_activation_probability: float = 0.0
llm_judge_prompt: str = ""
def __init__(self,
action_data: dict,
reasoning: str,
cycle_timers: dict,
thinking_id: str,
**kwargs):
"""初始化Action组件
Args:
action_data: 动作数据
reasoning: 执行该动作的理由
cycle_timers: 计时器字典
thinking_id: 思考ID
**kwargs: 其他参数(包含服务对象)
"""
self.action_data = action_data
self.reasoning = reasoning
self.cycle_timers = cycle_timers
self.thinking_id = thinking_id
# 创建API实例
self.api = PluginAPI(
chat_stream=kwargs.get("chat_stream"),
expressor=kwargs.get("expressor"),
replyer=kwargs.get("replyer"),
observations=kwargs.get("observations"),
log_prefix=kwargs.get("log_prefix", "")
)
self.log_prefix = kwargs.get("log_prefix", "")
logger.debug(f"{self.log_prefix} Action组件初始化完成")
async def send_reply(self, content: str) -> bool:
"""发送回复消息
Args:
content: 回复内容
Returns:
bool: 是否发送成功
"""
return await self.api.send_message("text", content)
@classmethod
def get_action_info(cls, name: str = None, description: str = None) -> 'ActionInfo':
"""从类属性生成ActionInfo
Args:
name: Action名称如果不提供则使用类名
description: Action描述如果不提供则使用类文档字符串
Returns:
ActionInfo: 生成的Action信息对象
"""
# 自动生成名称和描述
if name is None:
name = cls.__name__.lower().replace('action', '')
if description is None:
description = cls.__doc__ or f"{cls.__name__} Action组件"
description = description.strip().split('\n')[0] # 取第一行作为描述
return ActionInfo(
name=name,
component_type=ComponentType.ACTION,
description=description,
focus_activation_type=cls.focus_activation_type,
normal_activation_type=cls.normal_activation_type,
activation_keywords=cls.activation_keywords.copy() if cls.activation_keywords else [],
keyword_case_sensitive=cls.keyword_case_sensitive,
mode_enable=cls.mode_enable,
parallel_action=cls.parallel_action,
random_activation_probability=cls.random_activation_probability,
llm_judge_prompt=cls.llm_judge_prompt
)
@abstractmethod
async def execute(self) -> Tuple[bool, str]:
"""执行Action的抽象方法子类必须实现
Returns:
Tuple[bool, str]: (是否执行成功, 回复文本)
"""
pass

View File

@@ -0,0 +1,113 @@
from abc import ABC, abstractmethod
from typing import Dict, Tuple, Optional, List
from src.common.logger_manager import get_logger
from src.plugin_system.apis.plugin_api import PluginAPI
from src.plugin_system.base.component_types import CommandInfo, ComponentType
from src.chat.message_receive.message import MessageRecv
logger = get_logger("base_command")
class BaseCommand(ABC):
"""Command组件基类
Command是插件的一种组件类型用于处理命令请求
子类可以通过类属性定义命令模式:
- command_pattern: 命令匹配的正则表达式
- command_help: 命令帮助信息
- command_examples: 命令使用示例列表
"""
# 默认命令设置(子类可以覆盖)
command_pattern: str = ""
command_help: str = ""
command_examples: List[str] = []
def __init__(self, message: MessageRecv):
"""初始化Command组件
Args:
message: 接收到的消息对象
"""
self.message = message
self.matched_groups: Dict[str, str] = {} # 存储正则表达式匹配的命名组
# 创建API实例
self.api = PluginAPI(
chat_stream=message.chat_stream,
log_prefix=f"[Command]"
)
self.log_prefix = f"[Command]"
logger.debug(f"{self.log_prefix} Command组件初始化完成")
def set_matched_groups(self, groups: Dict[str, str]) -> None:
"""设置正则表达式匹配的命名组
Args:
groups: 正则表达式匹配的命名组
"""
self.matched_groups = groups
@abstractmethod
async def execute(self) -> Tuple[bool, Optional[str]]:
"""执行Command的抽象方法子类必须实现
Returns:
Tuple[bool, Optional[str]]: (是否执行成功, 可选的回复消息)
"""
pass
async def send_reply(self, content: str) -> None:
"""发送回复消息
Args:
content: 回复内容
"""
# 获取聊天流信息
chat_stream = self.message.chat_stream
if chat_stream.group_info:
# 群聊
await self.api.send_text_to_group(
text=content,
group_id=str(chat_stream.group_info.group_id),
platform=chat_stream.platform
)
else:
# 私聊
await self.api.send_text_to_user(
text=content,
user_id=str(chat_stream.user_info.user_id),
platform=chat_stream.platform
)
@classmethod
def get_command_info(cls, name: str = None, description: str = None) -> 'CommandInfo':
"""从类属性生成CommandInfo
Args:
name: Command名称如果不提供则使用类名
description: Command描述如果不提供则使用类文档字符串
Returns:
CommandInfo: 生成的Command信息对象
"""
# 自动生成名称和描述
if name is None:
name = cls.__name__.lower().replace('command', '')
if description is None:
description = cls.__doc__ or f"{cls.__name__} Command组件"
description = description.strip().split('\n')[0] # 取第一行作为描述
return CommandInfo(
name=name,
component_type=ComponentType.COMMAND,
description=description,
command_pattern=cls.command_pattern,
command_help=cls.command_help,
command_examples=cls.command_examples.copy() if cls.command_examples else []
)

View File

@@ -0,0 +1,226 @@
from abc import ABC, abstractmethod
from typing import Dict, List, Type, Optional, Any
import os
import inspect
import toml
from src.common.logger_manager import get_logger
from src.plugin_system.base.component_types import (
PluginInfo, ComponentInfo, ActionInfo, CommandInfo,
ComponentType, ActionActivationType, ChatMode
)
from src.plugin_system.core.component_registry import component_registry
logger = get_logger("base_plugin")
# 全局插件类注册表
_plugin_classes: Dict[str, Type['BasePlugin']] = {}
class BasePlugin(ABC):
"""插件基类
所有插件都应该继承这个基类,一个插件可以包含多种组件:
- Action组件处理聊天中的动作
- Command组件处理命令请求
- 未来可扩展Scheduler、Listener等
"""
# 插件基本信息(子类必须定义)
plugin_name: str = "" # 插件名称
plugin_description: str = "" # 插件描述
plugin_version: str = "1.0.0" # 插件版本
plugin_author: str = "" # 插件作者
enable_plugin: bool = True # 是否启用插件
dependencies: List[str] = [] # 依赖的其他插件
config_file_name: Optional[str] = None # 配置文件名
def __init__(self, plugin_dir: str = None):
"""初始化插件
Args:
plugin_dir: 插件目录路径,由插件管理器传递
"""
self.config: Dict[str, Any] = {} # 插件配置
self.plugin_dir = plugin_dir # 插件目录路径
self.log_prefix = f"[Plugin:{self.plugin_name}]"
# 验证插件信息
self._validate_plugin_info()
# 加载插件配置
self._load_plugin_config()
# 创建插件信息对象
self.plugin_info = PluginInfo(
name=self.plugin_name,
description=self.plugin_description,
version=self.plugin_version,
author=self.plugin_author,
enabled=self.enable_plugin,
is_built_in=False,
config_file=self.config_file_name or "",
dependencies=self.dependencies.copy()
)
logger.debug(f"{self.log_prefix} 插件基类初始化完成")
def _validate_plugin_info(self):
"""验证插件基本信息"""
if not self.plugin_name:
raise ValueError(f"插件类 {self.__class__.__name__} 必须定义 plugin_name")
if not self.plugin_description:
raise ValueError(f"插件 {self.plugin_name} 必须定义 plugin_description")
def _load_plugin_config(self):
"""加载插件配置文件"""
if not self.config_file_name:
logger.debug(f"{self.log_prefix} 未指定配置文件,跳过加载")
return
# 优先使用传入的插件目录路径
if self.plugin_dir:
plugin_dir = self.plugin_dir
else:
# fallback尝试从类的模块信息获取路径
try:
plugin_module_path = inspect.getfile(self.__class__)
plugin_dir = os.path.dirname(plugin_module_path)
except (TypeError, OSError):
# 最后的fallback从模块的__file__属性获取
module = inspect.getmodule(self.__class__)
if module and hasattr(module, '__file__') and module.__file__:
plugin_dir = os.path.dirname(module.__file__)
else:
logger.warning(f"{self.log_prefix} 无法获取插件目录路径,跳过配置加载")
return
config_file_path = os.path.join(plugin_dir, self.config_file_name)
if not os.path.exists(config_file_path):
logger.warning(f"{self.log_prefix} 配置文件 {config_file_path} 不存在")
return
file_ext = os.path.splitext(self.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} 配置已从 {config_file_path} 加载")
else:
logger.warning(f"{self.log_prefix} 不支持的配置文件格式: {file_ext},仅支持 .toml")
self.config = {}
@abstractmethod
def get_plugin_components(self) -> List[tuple[ComponentInfo, Type]]:
"""获取插件包含的组件列表
子类必须实现此方法,返回组件信息和组件类的列表
Returns:
List[tuple[ComponentInfo, Type]]: [(组件信息, 组件类), ...]
"""
pass
def register_plugin(self) -> bool:
"""注册插件及其所有组件"""
if not self.enable_plugin:
logger.info(f"{self.log_prefix} 插件已禁用,跳过注册")
return False
components = self.get_plugin_components()
# 检查依赖
if not self._check_dependencies():
logger.error(f"{self.log_prefix} 依赖检查失败,跳过注册")
return False
# 注册所有组件
registered_components = []
for component_info, component_class in components:
component_info.plugin_name = self.plugin_name
if component_registry.register_component(component_info, component_class):
registered_components.append(component_info)
else:
logger.warning(f"{self.log_prefix} 组件 {component_info.name} 注册失败")
# 更新插件信息中的组件列表
self.plugin_info.components = registered_components
# 注册插件
if component_registry.register_plugin(self.plugin_info):
logger.info(f"{self.log_prefix} 插件注册成功,包含 {len(registered_components)} 个组件")
return True
else:
logger.error(f"{self.log_prefix} 插件注册失败")
return False
def _check_dependencies(self) -> bool:
"""检查插件依赖"""
if not self.dependencies:
return True
for dep in self.dependencies:
if not component_registry.get_plugin_info(dep):
logger.error(f"{self.log_prefix} 缺少依赖插件: {dep}")
return False
return True
def get_config(self, key: str, default: Any = None) -> Any:
"""获取插件配置值
Args:
key: 配置键名
default: 默认值
Returns:
Any: 配置值或默认值
"""
return self.config.get(key, default)
def register_plugin(cls):
"""插件注册装饰器
用法:
@register_plugin
class MyPlugin(BasePlugin):
plugin_name = "my_plugin"
plugin_description = "我的插件"
...
"""
if not issubclass(cls, BasePlugin):
logger.error(f"{cls.__name__} 不是 BasePlugin 的子类")
return cls
# 只是注册插件类,不立即实例化
# 插件管理器会负责实例化和注册
plugin_name = cls.plugin_name or cls.__name__
_plugin_classes[plugin_name] = cls
logger.debug(f"插件类已注册: {plugin_name}")
return cls
def get_registered_plugin_classes() -> Dict[str, Type['BasePlugin']]:
"""获取所有已注册的插件类"""
return _plugin_classes.copy()
def instantiate_and_register_plugin(plugin_class: Type['BasePlugin'], plugin_dir: str = None) -> bool:
"""实例化并注册插件
Args:
plugin_class: 插件类
plugin_dir: 插件目录路径
Returns:
bool: 是否成功
"""
try:
plugin_instance = plugin_class(plugin_dir=plugin_dir)
return plugin_instance.register_plugin()
except Exception as e:
logger.error(f"注册插件 {plugin_class.__name__} 时出错: {e}")
import traceback
logger.error(traceback.format_exc())
return False

View File

@@ -0,0 +1,104 @@
from enum import Enum
from typing import Dict, Any, List
from dataclasses import dataclass
# 组件类型枚举
class ComponentType(Enum):
"""组件类型枚举"""
ACTION = "action" # 动作组件
COMMAND = "command" # 命令组件
SCHEDULER = "scheduler" # 定时任务组件(预留)
LISTENER = "listener" # 事件监听组件(预留)
# 动作激活类型枚举
class ActionActivationType(Enum):
"""动作激活类型枚举"""
NEVER = "never" # 从不激活(默认关闭)
ALWAYS = "always" # 默认参与到planner
LLM_JUDGE = "llm_judge" # LLM判定是否启动该action到planner
RANDOM = "random" # 随机启用action到planner
KEYWORD = "keyword" # 关键词触发启用action到planner
# 聊天模式枚举
class ChatMode(Enum):
"""聊天模式枚举"""
FOCUS = "focus" # Focus聊天模式
NORMAL = "normal" # Normal聊天模式
ALL = "all" # 所有聊天模式
@dataclass
class ComponentInfo:
"""组件信息"""
name: str # 组件名称
component_type: ComponentType # 组件类型
description: str # 组件描述
enabled: bool = True # 是否启用
plugin_name: str = "" # 所属插件名称
is_built_in: bool = False # 是否为内置组件
metadata: Dict[str, Any] = None # 额外元数据
def __post_init__(self):
if self.metadata is None:
self.metadata = {}
@dataclass
class ActionInfo(ComponentInfo):
"""动作组件信息"""
focus_activation_type: ActionActivationType = ActionActivationType.ALWAYS
normal_activation_type: ActionActivationType = ActionActivationType.ALWAYS
random_activation_probability: float = 0.3
llm_judge_prompt: str = ""
activation_keywords: List[str] = None
keyword_case_sensitive: bool = False
mode_enable: ChatMode = ChatMode.ALL
parallel_action: bool = False
action_parameters: Dict[str, Any] = None
action_require: List[str] = None
associated_types: List[str] = None
def __post_init__(self):
super().__post_init__()
if self.activation_keywords is None:
self.activation_keywords = []
if self.action_parameters is None:
self.action_parameters = {}
if self.action_require is None:
self.action_require = []
if self.associated_types is None:
self.associated_types = []
self.component_type = ComponentType.ACTION
@dataclass
class CommandInfo(ComponentInfo):
"""命令组件信息"""
command_pattern: str = "" # 命令匹配模式(正则表达式)
command_help: str = "" # 命令帮助信息
command_examples: List[str] = None # 命令使用示例
def __post_init__(self):
super().__post_init__()
if self.command_examples is None:
self.command_examples = []
self.component_type = ComponentType.COMMAND
@dataclass
class PluginInfo:
"""插件信息"""
name: str # 插件名称
description: str # 插件描述
version: str = "1.0.0" # 插件版本
author: str = "" # 插件作者
enabled: bool = True # 是否启用
is_built_in: bool = False # 是否为内置插件
components: List[ComponentInfo] = None # 包含的组件列表
dependencies: List[str] = None # 依赖的其他插件
config_file: str = "" # 配置文件路径
metadata: Dict[str, Any] = None # 额外元数据
def __post_init__(self):
if self.components is None:
self.components = []
if self.dependencies is None:
self.dependencies = []
if self.metadata is None:
self.metadata = {}

View File

@@ -0,0 +1,13 @@
"""
插件核心管理模块
提供插件的加载、注册和管理功能
"""
from src.plugin_system.core.plugin_manager import plugin_manager
from src.plugin_system.core.component_registry import component_registry
__all__ = [
'plugin_manager',
'component_registry',
]

View File

@@ -0,0 +1,245 @@
from typing import Dict, List, Type, Optional, Any, Pattern
from abc import ABC
import re
from src.common.logger_manager import get_logger
from src.plugin_system.base.component_types import (
ComponentInfo, ActionInfo, CommandInfo, PluginInfo,
ComponentType, ActionActivationType, ChatMode
)
logger = get_logger("component_registry")
class ComponentRegistry:
"""统一的组件注册中心
负责管理所有插件组件的注册、查询和生命周期管理
"""
def __init__(self):
# 组件注册表
self._components: Dict[str, ComponentInfo] = {} # 组件名 -> 组件信息
self._components_by_type: Dict[ComponentType, Dict[str, ComponentInfo]] = {
ComponentType.ACTION: {},
ComponentType.COMMAND: {},
}
self._component_classes: Dict[str, Type] = {} # 组件名 -> 组件类
# 插件注册表
self._plugins: Dict[str, PluginInfo] = {} # 插件名 -> 插件信息
# Action特定注册表
self._action_registry: Dict[str, Type] = {} # action名 -> action类
self._default_actions: Dict[str, str] = {} # 启用的action名 -> 描述
# Command特定注册表
self._command_registry: Dict[str, Type] = {} # command名 -> command类
self._command_patterns: Dict[Pattern, Type] = {} # 编译后的正则 -> command类
logger.info("组件注册中心初始化完成")
# === 通用组件注册方法 ===
def register_component(self, component_info: ComponentInfo, component_class: Type) -> bool:
"""注册组件
Args:
component_info: 组件信息
component_class: 组件类
Returns:
bool: 是否注册成功
"""
component_name = component_info.name
component_type = component_info.component_type
if component_name in self._components:
logger.warning(f"组件 {component_name} 已存在,跳过注册")
return False
# 注册到通用注册表
self._components[component_name] = component_info
self._components_by_type[component_type][component_name] = component_info
self._component_classes[component_name] = component_class
# 根据组件类型进行特定注册
if component_type == ComponentType.ACTION:
self._register_action_component(component_info, component_class)
elif component_type == ComponentType.COMMAND:
self._register_command_component(component_info, component_class)
logger.info(f"已注册{component_type.value}组件: {component_name} ({component_class.__name__})")
return True
def _register_action_component(self, action_info: ActionInfo, action_class: Type):
"""注册Action组件到Action特定注册表"""
action_name = action_info.name
self._action_registry[action_name] = action_class
# 如果启用,添加到默认动作集
if action_info.enabled:
self._default_actions[action_name] = action_info.description
def _register_command_component(self, command_info: CommandInfo, command_class: Type):
"""注册Command组件到Command特定注册表"""
command_name = command_info.name
self._command_registry[command_name] = command_class
# 编译正则表达式并注册
if command_info.command_pattern:
pattern = re.compile(command_info.command_pattern, re.IGNORECASE | re.DOTALL)
self._command_patterns[pattern] = command_class
# === 组件查询方法 ===
def get_component_info(self, component_name: str) -> Optional[ComponentInfo]:
"""获取组件信息"""
return self._components.get(component_name)
def get_component_class(self, component_name: str) -> Optional[Type]:
"""获取组件类"""
return self._component_classes.get(component_name)
def get_components_by_type(self, component_type: ComponentType) -> Dict[str, ComponentInfo]:
"""获取指定类型的所有组件"""
return self._components_by_type.get(component_type, {}).copy()
def get_enabled_components_by_type(self, component_type: ComponentType) -> Dict[str, ComponentInfo]:
"""获取指定类型的所有启用组件"""
components = self.get_components_by_type(component_type)
return {name: info for name, info in components.items() if info.enabled}
# === Action特定查询方法 ===
def get_action_registry(self) -> Dict[str, Type]:
"""获取Action注册表用于兼容现有系统"""
return self._action_registry.copy()
def get_default_actions(self) -> Dict[str, str]:
"""获取默认启用的Action列表用于兼容现有系统"""
return self._default_actions.copy()
def get_action_info(self, action_name: str) -> Optional[ActionInfo]:
"""获取Action信息"""
info = self.get_component_info(action_name)
return info if isinstance(info, ActionInfo) else None
# === Command特定查询方法 ===
def get_command_registry(self) -> Dict[str, Type]:
"""获取Command注册表用于兼容现有系统"""
return self._command_registry.copy()
def get_command_patterns(self) -> Dict[Pattern, Type]:
"""获取Command模式注册表用于兼容现有系统"""
return self._command_patterns.copy()
def get_command_info(self, command_name: str) -> Optional[CommandInfo]:
"""获取Command信息"""
info = self.get_component_info(command_name)
return info if isinstance(info, CommandInfo) else None
def find_command_by_text(self, text: str) -> Optional[tuple[Type, dict]]:
"""根据文本查找匹配的命令
Args:
text: 输入文本
Returns:
Optional[tuple[Type, dict]]: (命令类, 匹配的命名组) 或 None
"""
for pattern, command_class in self._command_patterns.items():
match = pattern.match(text)
if match:
command_name = None
# 查找对应的组件信息
for name, cls in self._command_registry.items():
if cls == command_class:
command_name = name
break
# 检查命令是否启用
if command_name:
command_info = self.get_command_info(command_name)
if command_info and command_info.enabled:
return command_class, match.groupdict()
return None
# === 插件管理方法 ===
def register_plugin(self, plugin_info: PluginInfo) -> bool:
"""注册插件
Args:
plugin_info: 插件信息
Returns:
bool: 是否注册成功
"""
plugin_name = plugin_info.name
if plugin_name in self._plugins:
logger.warning(f"插件 {plugin_name} 已存在,跳过注册")
return False
self._plugins[plugin_name] = plugin_info
logger.info(f"已注册插件: {plugin_name} (组件数量: {len(plugin_info.components)})")
return True
def get_plugin_info(self, plugin_name: str) -> Optional[PluginInfo]:
"""获取插件信息"""
return self._plugins.get(plugin_name)
def get_all_plugins(self) -> Dict[str, PluginInfo]:
"""获取所有插件"""
return self._plugins.copy()
def get_enabled_plugins(self) -> Dict[str, PluginInfo]:
"""获取所有启用的插件"""
return {name: info for name, info in self._plugins.items() if info.enabled}
def get_plugin_components(self, plugin_name: str) -> List[ComponentInfo]:
"""获取插件的所有组件"""
plugin_info = self.get_plugin_info(plugin_name)
return plugin_info.components if plugin_info else []
# === 状态管理方法 ===
def enable_component(self, component_name: str) -> bool:
"""启用组件"""
if component_name in self._components:
self._components[component_name].enabled = True
# 如果是Action更新默认动作集
component_info = self._components[component_name]
if isinstance(component_info, ActionInfo):
self._default_actions[component_name] = component_info.description
logger.info(f"已启用组件: {component_name}")
return True
return False
def disable_component(self, component_name: str) -> bool:
"""禁用组件"""
if component_name in self._components:
self._components[component_name].enabled = False
# 如果是Action从默认动作集中移除
if component_name in self._default_actions:
del self._default_actions[component_name]
logger.info(f"已禁用组件: {component_name}")
return True
return False
def get_registry_stats(self) -> Dict[str, Any]:
"""获取注册中心统计信息"""
return {
"total_components": len(self._components),
"total_plugins": len(self._plugins),
"components_by_type": {
component_type.value: len(components)
for component_type, components in self._components_by_type.items()
},
"enabled_components": len([c for c in self._components.values() if c.enabled]),
"enabled_plugins": len([p for p in self._plugins.values() if p.enabled]),
}
# 全局组件注册中心实例
component_registry = ComponentRegistry()

View File

@@ -0,0 +1,223 @@
from typing import Dict, List, Optional, Any
import os
import importlib
import importlib.util
from pathlib import Path
from src.common.logger_manager import get_logger
from src.plugin_system.core.component_registry import component_registry
from src.plugin_system.base.component_types import PluginInfo, ComponentType
logger = get_logger("plugin_manager")
class PluginManager:
"""插件管理器
负责加载、初始化和管理所有插件及其组件
"""
def __init__(self):
self.plugin_directories: List[str] = []
self.loaded_plugins: Dict[str, Any] = {}
self.failed_plugins: Dict[str, str] = {}
logger.info("插件管理器初始化完成")
def add_plugin_directory(self, directory: str):
"""添加插件目录"""
if os.path.exists(directory):
self.plugin_directories.append(directory)
logger.info(f"已添加插件目录: {directory}")
else:
logger.warning(f"插件目录不存在: {directory}")
def load_all_plugins(self) -> tuple[int, int]:
"""加载所有插件目录中的插件
Returns:
tuple[int, int]: (插件数量, 组件数量)
"""
logger.info("开始加载所有插件...")
# 第一阶段:加载所有插件模块(注册插件类)
total_loaded_modules = 0
total_failed_modules = 0
for directory in self.plugin_directories:
loaded, failed = self._load_plugin_modules_from_directory(directory)
total_loaded_modules += loaded
total_failed_modules += failed
logger.info(f"插件模块加载完成 - 成功: {total_loaded_modules}, 失败: {total_failed_modules}")
# 第二阶段:实例化所有已注册的插件类
from src.plugin_system.base.base_plugin import get_registered_plugin_classes, instantiate_and_register_plugin
plugin_classes = get_registered_plugin_classes()
total_registered = 0
total_failed_registration = 0
for plugin_name, plugin_class in plugin_classes.items():
# 尝试找到插件对应的目录
plugin_dir = self._find_plugin_directory(plugin_class)
if instantiate_and_register_plugin(plugin_class, plugin_dir):
total_registered += 1
self.loaded_plugins[plugin_name] = plugin_class
else:
total_failed_registration += 1
self.failed_plugins[plugin_name] = "插件注册失败"
logger.info(f"插件注册完成 - 成功: {total_registered}, 失败: {total_failed_registration}")
# 获取组件统计信息
stats = component_registry.get_registry_stats()
logger.info(f"组件注册统计: {stats}")
# 返回插件数量和组件数量
return total_registered, stats.get('total_components', 0)
def _find_plugin_directory(self, plugin_class) -> Optional[str]:
"""查找插件类对应的目录路径"""
try:
import inspect
module = inspect.getmodule(plugin_class)
if module and hasattr(module, '__file__') and module.__file__:
return os.path.dirname(module.__file__)
except Exception:
pass
return None
def _load_plugin_modules_from_directory(self, directory: str) -> tuple[int, int]:
"""从指定目录加载插件模块"""
loaded_count = 0
failed_count = 0
if not os.path.exists(directory):
logger.warning(f"插件目录不存在: {directory}")
return loaded_count, failed_count
logger.info(f"正在扫描插件目录: {directory}")
# 遍历目录中的所有Python文件和包
for item in os.listdir(directory):
item_path = os.path.join(directory, item)
if os.path.isfile(item_path) and item.endswith('.py') and item != '__init__.py':
# 单文件插件
if self._load_plugin_module_file(item_path):
loaded_count += 1
else:
failed_count += 1
elif os.path.isdir(item_path) and not item.startswith('.') and not item.startswith('__'):
# 插件包
plugin_file = os.path.join(item_path, 'plugin.py')
if os.path.exists(plugin_file):
if self._load_plugin_module_file(plugin_file):
loaded_count += 1
else:
failed_count += 1
return loaded_count, failed_count
def _load_plugin_module_file(self, plugin_file: str) -> bool:
"""加载单个插件模块文件"""
plugin_name = None
# 生成模块名
plugin_path = Path(plugin_file)
if plugin_path.parent.name != 'plugins':
# 插件包格式parent_dir.plugin
module_name = f"plugins.{plugin_path.parent.name}.plugin"
else:
# 单文件格式plugins.filename
module_name = f"plugins.{plugin_path.stem}"
try:
# 动态导入插件模块
spec = importlib.util.spec_from_file_location(module_name, plugin_file)
if spec is None or spec.loader is None:
logger.error(f"无法创建模块规范: {plugin_file}")
return False
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# 模块加载成功,插件类会自动通过装饰器注册
plugin_name = plugin_path.parent.name if plugin_path.parent.name != 'plugins' else plugin_path.stem
logger.debug(f"插件模块加载成功: {plugin_file}")
return True
except Exception as e:
error_msg = f"加载插件模块 {plugin_file} 失败: {e}"
logger.error(error_msg)
if plugin_name:
self.failed_plugins[plugin_name] = error_msg
return False
def get_loaded_plugins(self) -> List[PluginInfo]:
"""获取所有已加载的插件信息"""
return list(component_registry.get_all_plugins().values())
def get_enabled_plugins(self) -> List[PluginInfo]:
"""获取所有启用的插件信息"""
return list(component_registry.get_enabled_plugins().values())
def enable_plugin(self, plugin_name: str) -> bool:
"""启用插件"""
plugin_info = component_registry.get_plugin_info(plugin_name)
if plugin_info:
plugin_info.enabled = True
# 启用插件的所有组件
for component in plugin_info.components:
component_registry.enable_component(component.name)
logger.info(f"已启用插件: {plugin_name}")
return True
return False
def disable_plugin(self, plugin_name: str) -> bool:
"""禁用插件"""
plugin_info = component_registry.get_plugin_info(plugin_name)
if plugin_info:
plugin_info.enabled = False
# 禁用插件的所有组件
for component in plugin_info.components:
component_registry.disable_component(component.name)
logger.info(f"已禁用插件: {plugin_name}")
return True
return False
def get_plugin_stats(self) -> Dict[str, Any]:
"""获取插件统计信息"""
all_plugins = component_registry.get_all_plugins()
enabled_plugins = component_registry.get_enabled_plugins()
action_components = component_registry.get_components_by_type(ComponentType.ACTION)
command_components = component_registry.get_components_by_type(ComponentType.COMMAND)
return {
"total_plugins": len(all_plugins),
"enabled_plugins": len(enabled_plugins),
"failed_plugins": len(self.failed_plugins),
"total_components": len(action_components) + len(command_components),
"action_components": len(action_components),
"command_components": len(command_components),
"loaded_plugin_files": len(self.loaded_plugins),
"failed_plugin_details": self.failed_plugins.copy()
}
def reload_plugin(self, plugin_name: str) -> bool:
"""重新加载插件(高级功能,需要谨慎使用)"""
# TODO: 实现插件热重载功能
logger.warning("插件热重载功能尚未实现")
return False
# 全局插件管理器实例
plugin_manager = PluginManager()
# 默认插件目录
plugin_manager.add_plugin_directory("src/plugins/built_in")
plugin_manager.add_plugin_directory("src/plugins/examples")
plugin_manager.add_plugin_directory("plugins") # 用户插件目录

206
src/plugins/README.md Normal file
View File

@@ -0,0 +1,206 @@
# MaiBot 插件系统架构
## 概述
MaiBot 插件系统采用组件化设计,支持插件包含多种组件类型:
- **Action组件**:处理聊天中的动作逻辑
- **Command组件**:处理命令请求
- **未来扩展**Scheduler定时任务、Listener事件监听
## 目录结构
```
src/plugins/
├── core/ # 插件核心管理
│ ├── plugin_manager.py # 插件管理器
│ ├── plugin_loader.py # 插件加载器(预留)
│ └── component_registry.py # 组件注册中心
├── apis/ # API模块
│ ├── plugin_api.py # 统一API聚合
│ ├── message_api.py # 消息API
│ ├── llm_api.py # LLM API
│ ├── database_api.py # 数据库API
│ ├── config_api.py # 配置API
│ ├── utils_api.py # 工具API
│ ├── stream_api.py # 流API
│ └── hearflow_api.py # 心流API
├── base/ # 基础类
│ ├── base_plugin.py # 插件基类
│ ├── base_action.py # Action组件基类
│ ├── base_command.py # Command组件基类
│ └── component_types.py # 组件类型定义
├── built_in/ # 内置组件
│ ├── actions/ # 内置Action
│ └── commands/ # 内置Command
└── examples/ # 示例插件
└── simple_plugin/ # 简单插件示例
├── plugin.py
└── config.toml
```
## 核心特性
### 1. 组件化设计
- 插件可以包含多种组件类型
- 每种组件有明确的职责和接口
- 支持组件的独立启用/禁用
### 2. 统一的API访问
- 所有插件组件通过 `PluginAPI` 访问系统功能
- 包含消息发送、数据库操作、LLM调用等
- 提供统一的错误处理和日志记录
### 3. 灵活的配置系统
- 支持 TOML 格式的配置文件
- 插件可以读取自定义配置
- 支持全局配置和插件特定配置
### 4. 统一的注册管理
- 组件注册中心管理所有组件
- 支持组件的动态启用/禁用
- 提供丰富的查询和统计接口
## 插件开发指南
### 创建基本插件
```python
from src.plugins.base.base_plugin import BasePlugin, register_plugin
from src.plugins.base.base_action import BaseAction
from src.plugins.base.component_types import ActionInfo, ActionActivationType
class MyAction(BaseAction):
async def execute(self) -> tuple[bool, str]:
# 使用API发送消息
response = "Hello from my plugin!"
return True, response
@register_plugin
class MyPlugin(BasePlugin):
plugin_name = "my_plugin"
plugin_description = "我的第一个插件"
def get_plugin_components(self):
action_info = ActionInfo(
name="my_action",
description="我的动作",
activation_keywords=["hello"]
)
return [(action_info, MyAction)]
```
### 创建命令组件
```python
from src.plugins.base.base_command import BaseCommand
from src.plugins.base.component_types import CommandInfo
class MyCommand(BaseCommand):
async def execute(self) -> tuple[bool, str]:
# 获取命令参数
param = self.matched_groups.get("param", "")
# 发送回复
await self.send_reply(f"收到参数: {param}")
return True, f"处理完成: {param}"
# 在插件中注册
def get_plugin_components(self):
command_info = CommandInfo(
name="my_command",
description="我的命令",
command_pattern=r"^/mycmd\s+(?P<param>\w+)$",
command_help="用法:/mycmd <参数>"
)
return [(command_info, MyCommand)]
```
### 使用配置文件
```toml
# config.toml
[plugin]
name = "my_plugin"
enabled = true
[my_settings]
max_items = 10
default_message = "Hello World"
```
```python
class MyPlugin(BasePlugin):
config_file_name = "config.toml"
def get_plugin_components(self):
# 读取配置
max_items = self.get_config("my_settings.max_items", 5)
message = self.get_config("my_settings.default_message", "Hi")
# 使用配置创建组件...
```
## API使用示例
### 消息操作
```python
# 发送文本消息
await self.api.send_text_to_group(chat_stream, "Hello!")
# 发送图片
await self.api.send_image_to_group(chat_stream, image_path)
```
### 数据库操作
```python
# 查询数据
data = await self.api.db_get("table_name", "key")
# 保存数据
await self.api.db_set("table_name", "key", "value")
```
### LLM调用
```python
# 生成文本
response = await self.api.llm_text_request("你好,请介绍一下自己")
# 生成图片
image_url = await self.api.llm_image_request("一只可爱的猫咪")
```
## 内置组件迁移
现有的内置Action和Command将迁移到新架构
### Action迁移
- `reply_action.py``src/plugins/built_in/actions/reply_action.py`
- `emoji_action.py``src/plugins/built_in/actions/emoji_action.py`
- `no_reply_action.py``src/plugins/built_in/actions/no_reply_action.py`
### Command迁移
- 现有命令系统将封装为内置Command组件
- 保持现有的命令模式和功能
## 兼容性
新插件系统保持与现有系统的兼容性:
- 现有的Action和Command继续工作
- 提供兼容层和适配器
- 逐步迁移到新架构
## 扩展性
系统设计支持未来扩展:
- 新的组件类型Scheduler、Listener等
- 插件间依赖和通信
- 插件热重载
- 插件市场和分发
## 最佳实践
1. **单一职责**:每个组件专注于特定功能
2. **配置驱动**:通过配置文件控制行为
3. **错误处理**:妥善处理异常情况
4. **日志记录**:记录关键操作和错误
5. **测试覆盖**:为插件编写单元测试

View File

@@ -1,105 +0,0 @@
# 发送消息命令插件
这个插件提供了多个便捷的消息发送命令,允许管理员向指定群聊或用户发送消息。
## 命令列表
### 1. `/send` - 基础发送命令
向指定群聊或用户发送文本消息。
**语法:**
```
/send <group|user> <ID> <消息内容>
```
**示例:**
```
/send group 123456789 大家好!
/send user 987654321 私聊消息
```
### 2. `/sendfull` - 增强发送命令
支持多种消息类型和平台的发送命令。
**语法:**
```
/sendfull <消息类型> <目标类型> <ID> [平台] <内容>
```
**消息类型:**
- `text` - 文本消息
- `image` - 图片消息提供图片URL
- `emoji` - 表情消息
**示例:**
```
/sendfull text group 123456789 qq 大家好!这是文本消息
/sendfull image user 987654321 https://example.com/image.jpg
/sendfull emoji group 123456789 😄
```
### 3. `/msg` - 快速群聊发送
快速向群聊发送文本消息的简化命令。
**语法:**
```
/msg <群ID> <消息内容>
```
**示例:**
```
/msg 123456789 大家好!
/msg 987654321 这是一条快速消息
```
### 4. `/pm` - 私聊发送
快速向用户发送私聊消息的命令。
**语法:**
```
/pm <用户ID> <消息内容>
```
**示例:**
```
/pm 123456789 你好!
/pm 987654321 这是私聊消息
```
## 使用前提
1. **目标存在**: 目标群聊或用户必须已经在机器人的数据库中存在对应的chat_stream记录
2. **权限要求**: 机器人必须在目标群聊中有发言权限
3. **管理员权限**: 这些命令通常需要管理员权限才能使用
## 错误处理
如果消息发送失败,可能的原因:
1. **目标不存在**: 指定的群ID或用户ID在数据库中找不到对应记录
2. **权限不足**: 机器人在目标群聊中没有发言权限
3. **网络问题**: 网络连接异常
4. **平台限制**: 目标平台的API限制
## 注意事项
1. **ID格式**: 群ID和用户ID必须是纯数字
2. **消息长度**: 注意平台对消息长度的限制
3. **图片格式**: 发送图片时需要提供有效的图片URL
4. **平台支持**: 目前主要支持QQ平台其他平台可能需要额外配置
## 安全建议
1. 限制这些命令的使用权限,避免滥用
2. 监控发送频率,防止刷屏
3. 定期检查发送日志,确保合规使用
## 故障排除
查看日志文件中的详细错误信息:
```
[INFO] [Command:send] 执行发送消息命令: group:123456789 -> 大家好!...
[ERROR] [Command:send] 发送群聊消息时出错: 未找到群ID为 123456789 的聊天流
```
根据错误信息进行相应的处理。

View File

@@ -0,0 +1,30 @@
# 完整示例插件配置文件
[plugin]
name = "simple_plugin"
version = "1.1.0"
enabled = true
description = "展示新插件系统完整功能的示例插件"
[hello_action]
greeting_message = "你好,{username}欢迎使用MaiBot新插件系统"
enable_emoji = true
enable_llm_greeting = false # 是否使用LLM生成个性化问候
default_username = "朋友"
[status_command]
show_detailed_info = true
allowed_types = ["系统", "插件", "数据库", "内存", "网络"]
default_type = "系统"
[echo_command]
max_message_length = 500
enable_formatting = true
[help_command]
show_extended_help = true
include_config_info = true
[logging]
level = "INFO"
prefix = "[SimplePlugin]"

View File

@@ -0,0 +1,195 @@
"""
完整示例插件
演示新插件系统的完整功能:
- 使用简化的导入接口
- 展示Action和Command组件的定义
- 展示插件配置的使用
- 提供实用的示例功能
- 演示API的多种使用方式
"""
from typing import List, Tuple, Type, Optional
# 使用简化的导入接口
from src.plugin_system import (
BasePlugin, register_plugin, BaseAction, BaseCommand,
ComponentInfo, ActionInfo, CommandInfo, ActionActivationType, ChatMode
)
from src.common.logger_manager import get_logger
logger = get_logger("simple_plugin")
class HelloAction(BaseAction):
"""智能问候Action组件"""
# ✅ 现在可以直接在类中定义激活条件!
focus_activation_type = ActionActivationType.KEYWORD
normal_activation_type = ActionActivationType.KEYWORD
activation_keywords = ["你好", "hello", "问候", "hi", ""]
keyword_case_sensitive = False
mode_enable = ChatMode.ALL
parallel_action = False
async def execute(self) -> Tuple[bool, str]:
"""执行问候动作"""
username = self.action_data.get("username", "朋友")
# 使用配置文件中的问候消息
plugin_instance = SimplePlugin()
greeting_template = plugin_instance.get_config("hello_action.greeting_message", "你好,{username}")
enable_emoji = plugin_instance.get_config("hello_action.enable_emoji", True)
enable_llm = plugin_instance.get_config("hello_action.enable_llm_greeting", False)
# 如果启用LLM生成个性化问候
if enable_llm:
try:
# 演示使用LLM API生成个性化问候
models = self.api.get_available_models()
if models:
first_model = list(models.values())[0]
prompt = f"为用户名叫{username}的朋友生成一句温暖的个性化问候语不超过30字"
success, response, _, _ = await self.api.generate_with_model(
prompt=prompt,
model_config=first_model
)
if success:
logger.info(f"{self.log_prefix} 使用LLM生成问候: {response}")
return True, response
except Exception as e:
logger.warning(f"{self.log_prefix} LLM生成问候失败使用默认模板: {e}")
# 构建基础问候消息
response = greeting_template.format(username=username)
if enable_emoji:
response += " 😊"
# 演示存储Action执行记录到数据库
await self.api.store_action_info(
action_build_into_prompt=False,
action_prompt_display=f"问候了用户: {username}",
action_done=True
)
logger.info(f"{self.log_prefix} 执行问候动作: {username}")
return True, response
class EchoCommand(BaseCommand):
"""回声命令 - 重复用户输入"""
# ✅ 现在可以直接在类中定义命令模式!
command_pattern = r"^/echo\s+(?P<message>.+)$"
command_help = "重复消息,用法:/echo <消息内容>"
command_examples = ["/echo Hello World", "/echo 你好世界"]
async def execute(self) -> Tuple[bool, Optional[str]]:
"""执行回声命令"""
# 获取匹配的参数
message = self.matched_groups.get("message", "")
if not message:
response = "请提供要重复的消息!用法:/echo <消息内容>"
else:
response = f"🔊 {message}"
# 发送回复
await self.send_reply(response)
logger.info(f"{self.log_prefix} 执行回声命令: {message}")
return True, response
class StatusCommand(BaseCommand):
"""状态查询Command组件"""
# ✅ 直接定义命令模式
command_pattern = r"^/status\s*(?P<type>\w+)?$"
command_help = "查询系统状态,用法:/status [类型]"
command_examples = ["/status", "/status 系统", "/status 插件"]
async def execute(self) -> Tuple[bool, Optional[str]]:
"""执行状态查询命令"""
# 获取匹配的参数
query_type = self.matched_groups.get("type", "系统")
# 从配置文件获取设置
plugin_instance = SimplePlugin()
show_detailed = plugin_instance.get_config("status_command.show_detailed_info", True)
allowed_types = plugin_instance.get_config("status_command.allowed_types", ["系统", "插件"])
if query_type not in allowed_types:
response = f"不支持的查询类型: {query_type}\n支持的类型: {', '.join(allowed_types)}"
elif show_detailed:
response = f"📊 {query_type}状态详情:\n✅ 运行正常\n🔧 版本: 1.0.0\n⚡ 性能: 良好"
else:
response = f"{query_type}状态:正常"
# 发送回复
await self.send_reply(response)
logger.info(f"{self.log_prefix} 执行状态查询: {query_type}")
return True, response
class HelpCommand(BaseCommand):
"""帮助命令 - 显示插件功能"""
# ✅ 直接定义命令模式
command_pattern = r"^/help$"
command_help = "显示插件帮助信息"
command_examples = ["/help"]
async def execute(self) -> Tuple[bool, Optional[str]]:
"""执行帮助命令"""
help_text = """
🤖 简单示例插件帮助
📝 可用命令:
• /echo <消息> - 重复你的消息
• /status [类型] - 查询系统状态
• /help - 显示此帮助信息
🎯 智能功能:
• 自动问候 - 当消息包含"你好""hello"等关键词时触发
⚙️ 配置:
本插件支持通过config.toml文件进行个性化配置
💡 这是新插件系统的完整示例展示了Action和Command的结合使用。
""".strip()
await self.send_reply(help_text)
logger.info(f"{self.log_prefix} 显示帮助信息")
return True, "已显示帮助信息"
@register_plugin
class SimplePlugin(BasePlugin):
"""完整示例插件
包含多个Action和Command组件展示插件系统的完整功能
"""
# 插件基本信息
plugin_name = "simple_plugin"
plugin_description = "完整的示例插件,展示新插件系统的各种功能"
plugin_version = "1.1.0"
plugin_author = "MaiBot开发团队"
enable_plugin = True
config_file_name = "config.toml" # 配置文件
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
"""返回插件包含的组件列表"""
# ✅ 现在可以直接从类属性生成组件信息!
return [
(HelloAction.get_action_info("hello_action", "智能问候动作,支持自定义消息和表情"), HelloAction),
(EchoCommand.get_command_info("echo_command", "回声命令,重复用户输入的消息"), EchoCommand),
(StatusCommand.get_command_info("status_command", "状态查询命令,支持多种查询类型"), StatusCommand),
(HelpCommand.get_command_info("help_command", "帮助命令,显示插件功能说明"), HelpCommand)
]

View File

@@ -1,304 +0,0 @@
import importlib
import pkgutil
import os
from typing import Dict, Tuple
from src.common.logger_manager import get_logger
logger = get_logger("plugin_loader")
class PluginLoader:
"""统一的插件加载器负责加载插件的所有组件actions、commands等"""
def __init__(self):
self.loaded_actions = 0
self.loaded_commands = 0
self.plugin_stats: Dict[str, Dict[str, int]] = {} # 统计每个插件加载的组件数量
self.plugin_sources: Dict[str, str] = {} # 记录每个插件来自哪个路径
def load_all_plugins(self) -> Tuple[int, int]:
"""加载所有插件的所有组件
Returns:
Tuple[int, int]: (加载的动作数量, 加载的命令数量)
"""
# 定义插件搜索路径(优先级从高到低)
plugin_paths = [
("plugins", "plugins"), # 项目根目录的plugins文件夹
("src.plugins", os.path.join("src", "plugins")), # src下的plugins文件夹
]
total_plugins_found = 0
for plugin_import_path, plugin_dir_path in plugin_paths:
try:
plugins_loaded = self._load_plugins_from_path(plugin_import_path, plugin_dir_path)
total_plugins_found += plugins_loaded
except Exception as e:
logger.error(f"从路径 {plugin_dir_path} 加载插件失败: {e}")
import traceback
logger.error(traceback.format_exc())
if total_plugins_found == 0:
logger.info("未找到任何插件目录或插件")
# 输出加载统计
self._log_loading_stats()
return self.loaded_actions, self.loaded_commands
def _load_plugins_from_path(self, plugin_import_path: str, plugin_dir_path: str) -> int:
"""从指定路径加载插件
Args:
plugin_import_path: 插件的导入路径 (如 "plugins""src.plugins")
plugin_dir_path: 插件目录的文件系统路径
Returns:
int: 找到的插件包数量
"""
# 检查插件目录是否存在
if not os.path.exists(plugin_dir_path):
logger.debug(f"插件目录 {plugin_dir_path} 不存在,跳过")
return 0
logger.info(f"正在从 {plugin_dir_path} 加载插件...")
# 导入插件包
try:
plugins_package = importlib.import_module(plugin_import_path)
logger.info(f"成功导入插件包: {plugin_import_path}")
except ImportError as e:
logger.warning(f"导入插件包 {plugin_import_path} 失败: {e}")
return 0
# 遍历插件包中的所有子包
plugins_found = 0
for _, plugin_name, is_pkg in pkgutil.iter_modules(plugins_package.__path__, plugins_package.__name__ + "."):
if not is_pkg:
continue
logger.debug(f"检测到插件: {plugin_name}")
# 记录插件来源
self.plugin_sources[plugin_name] = plugin_dir_path
self._load_single_plugin(plugin_name)
plugins_found += 1
if plugins_found > 0:
logger.info(f"{plugin_dir_path} 找到 {plugins_found} 个插件包")
else:
logger.debug(f"{plugin_dir_path} 未找到任何插件包")
return plugins_found
def _load_single_plugin(self, plugin_name: str) -> None:
"""加载单个插件的所有组件
Args:
plugin_name: 插件名称
"""
plugin_stats = {"actions": 0, "commands": 0}
# 加载动作组件
actions_count = self._load_plugin_actions(plugin_name)
plugin_stats["actions"] = actions_count
self.loaded_actions += actions_count
# 加载命令组件
commands_count = self._load_plugin_commands(plugin_name)
plugin_stats["commands"] = commands_count
self.loaded_commands += commands_count
# 记录插件统计信息
if actions_count > 0 or commands_count > 0:
self.plugin_stats[plugin_name] = plugin_stats
logger.info(f"插件 {plugin_name} 加载完成: {actions_count} 个动作, {commands_count} 个命令")
def _load_plugin_actions(self, plugin_name: str) -> int:
"""加载插件的动作组件
Args:
plugin_name: 插件名称
Returns:
int: 加载的动作数量
"""
loaded_count = 0
# 优先检查插件是否有actions子包
plugin_actions_path = f"{plugin_name}.actions"
plugin_actions_dir = plugin_name.replace(".", os.path.sep) + os.path.sep + "actions"
actions_loaded_from_subdir = False
# 首先尝试从actions子目录加载
if os.path.exists(plugin_actions_dir):
loaded_count += self._load_from_actions_subdir(plugin_name, plugin_actions_path, plugin_actions_dir)
if loaded_count > 0:
actions_loaded_from_subdir = True
# 如果actions子目录不存在或加载失败尝试从插件根目录加载
if not actions_loaded_from_subdir:
loaded_count += self._load_actions_from_root_dir(plugin_name)
return loaded_count
def _load_plugin_commands(self, plugin_name: str) -> int:
"""加载插件的命令组件
Args:
plugin_name: 插件名称
Returns:
int: 加载的命令数量
"""
loaded_count = 0
# 优先检查插件是否有commands子包
plugin_commands_path = f"{plugin_name}.commands"
plugin_commands_dir = plugin_name.replace(".", os.path.sep) + os.path.sep + "commands"
commands_loaded_from_subdir = False
# 首先尝试从commands子目录加载
if os.path.exists(plugin_commands_dir):
loaded_count += self._load_from_commands_subdir(plugin_name, plugin_commands_path, plugin_commands_dir)
if loaded_count > 0:
commands_loaded_from_subdir = True
# 如果commands子目录不存在或加载失败尝试从插件根目录加载
if not commands_loaded_from_subdir:
loaded_count += self._load_commands_from_root_dir(plugin_name)
return loaded_count
def _load_from_actions_subdir(self, plugin_name: str, plugin_actions_path: str, plugin_actions_dir: str) -> int:
"""从actions子目录加载动作"""
loaded_count = 0
try:
# 尝试导入插件的actions包
actions_module = importlib.import_module(plugin_actions_path)
logger.debug(f"成功加载插件动作模块: {plugin_actions_path}")
# 遍历actions目录中的所有Python文件
actions_dir = os.path.dirname(actions_module.__file__)
for file in os.listdir(actions_dir):
if file.endswith(".py") and file != "__init__.py":
action_module_name = f"{plugin_actions_path}.{file[:-3]}"
try:
importlib.import_module(action_module_name)
logger.info(f"成功加载动作: {action_module_name}")
loaded_count += 1
except Exception as e:
logger.error(f"加载动作失败: {action_module_name}, 错误: {e}")
except ImportError as e:
logger.debug(f"插件 {plugin_name} 的actions子包导入失败: {e}")
return loaded_count
def _load_from_commands_subdir(self, plugin_name: str, plugin_commands_path: str, plugin_commands_dir: str) -> int:
"""从commands子目录加载命令"""
loaded_count = 0
try:
# 尝试导入插件的commands包
commands_module = importlib.import_module(plugin_commands_path)
logger.debug(f"成功加载插件命令模块: {plugin_commands_path}")
# 遍历commands目录中的所有Python文件
commands_dir = os.path.dirname(commands_module.__file__)
for file in os.listdir(commands_dir):
if file.endswith(".py") and file != "__init__.py":
command_module_name = f"{plugin_commands_path}.{file[:-3]}"
try:
importlib.import_module(command_module_name)
logger.info(f"成功加载命令: {command_module_name}")
loaded_count += 1
except Exception as e:
logger.error(f"加载命令失败: {command_module_name}, 错误: {e}")
except ImportError as e:
logger.debug(f"插件 {plugin_name} 的commands子包导入失败: {e}")
return loaded_count
def _load_actions_from_root_dir(self, plugin_name: str) -> int:
"""从插件根目录加载动作文件"""
loaded_count = 0
try:
# 导入插件包本身
plugin_module = importlib.import_module(plugin_name)
logger.debug(f"尝试从插件根目录加载动作: {plugin_name}")
# 遍历插件根目录中的所有Python文件
plugin_dir = os.path.dirname(plugin_module.__file__)
for file in os.listdir(plugin_dir):
if file.endswith(".py") and file != "__init__.py":
# 跳过非动作文件(根据命名约定)
if not (file.endswith("_action.py") or file.endswith("_actions.py") or "action" in file):
continue
action_module_name = f"{plugin_name}.{file[:-3]}"
try:
importlib.import_module(action_module_name)
logger.info(f"成功加载动作: {action_module_name}")
loaded_count += 1
except Exception as e:
logger.error(f"加载动作失败: {action_module_name}, 错误: {e}")
except ImportError as e:
logger.debug(f"插件 {plugin_name} 导入失败: {e}")
return loaded_count
def _load_commands_from_root_dir(self, plugin_name: str) -> int:
"""从插件根目录加载命令文件"""
loaded_count = 0
try:
# 导入插件包本身
plugin_module = importlib.import_module(plugin_name)
logger.debug(f"尝试从插件根目录加载命令: {plugin_name}")
# 遍历插件根目录中的所有Python文件
plugin_dir = os.path.dirname(plugin_module.__file__)
for file in os.listdir(plugin_dir):
if file.endswith(".py") and file != "__init__.py":
# 跳过非命令文件(根据命名约定)
if not (file.endswith("_command.py") or file.endswith("_commands.py") or "command" in file):
continue
command_module_name = f"{plugin_name}.{file[:-3]}"
try:
importlib.import_module(command_module_name)
logger.info(f"成功加载命令: {command_module_name}")
loaded_count += 1
except Exception as e:
logger.error(f"加载命令失败: {command_module_name}, 错误: {e}")
except ImportError as e:
logger.debug(f"插件 {plugin_name} 导入失败: {e}")
return loaded_count
def _log_loading_stats(self) -> None:
"""输出加载统计信息"""
logger.success(f"插件加载完成: 总计 {self.loaded_actions} 个动作, {self.loaded_commands} 个命令")
if self.plugin_stats:
logger.info("插件加载详情:")
for plugin_name, stats in self.plugin_stats.items():
plugin_display_name = plugin_name.split(".")[-1] # 只显示插件名称,不显示完整路径
source_path = self.plugin_sources.get(plugin_name, "未知路径")
logger.info(
f" {plugin_display_name} (来源: {source_path}): {stats['actions']} 动作, {stats['commands']} 命令"
)
# 创建全局插件加载器实例
plugin_loader = PluginLoader()