feat:插件依赖管理(如管
This commit is contained in:
@@ -10,7 +10,7 @@ MaiBot 是一个基于大语言模型的智能聊天机器人,采用现代化
|
|||||||
- **统一API接口**:提供丰富的API功能,包括消息发送、数据库操作、LLM调用等
|
- **统一API接口**:提供丰富的API功能,包括消息发送、数据库操作、LLM调用等
|
||||||
- **配置驱动**:支持TOML配置文件,实现灵活的参数配置
|
- **配置驱动**:支持TOML配置文件,实现灵活的参数配置
|
||||||
- **热加载机制**:支持动态加载和卸载插件
|
- **热加载机制**:支持动态加载和卸载插件
|
||||||
- **依赖管理**:内置依赖检查和解析机制
|
- **智能依赖管理**:自动检查和安装Python第三方包依赖
|
||||||
- **拦截控制**:Command组件支持消息拦截控制
|
- **拦截控制**:Command组件支持消息拦截控制
|
||||||
- **双目录支持**:区分用户插件和系统内置插件
|
- **双目录支持**:区分用户插件和系统内置插件
|
||||||
|
|
||||||
@@ -35,6 +35,7 @@ MaiBot支持两个插件目录:
|
|||||||
- [⚡ Action组件详解](docs/plugins/action-components.md) - 智能动作组件开发指南
|
- [⚡ Action组件详解](docs/plugins/action-components.md) - 智能动作组件开发指南
|
||||||
- [💻 Command组件详解](docs/plugins/command-components.md) - 命令组件开发指南
|
- [💻 Command组件详解](docs/plugins/command-components.md) - 命令组件开发指南
|
||||||
- [🔧 工具系统详解](docs/plugins/tool-system.md) - 扩展麦麦信息获取能力的工具组件
|
- [🔧 工具系统详解](docs/plugins/tool-system.md) - 扩展麦麦信息获取能力的工具组件
|
||||||
|
- [📦 依赖管理系统](docs/plugins/dependency-management.md) - Python包依赖管理详解
|
||||||
|
|
||||||
### 🔌 API参考
|
### 🔌 API参考
|
||||||
- [📡 消息API](docs/plugins/api/message-api.md) - 消息发送和处理接口
|
- [📡 消息API](docs/plugins/api/message-api.md) - 消息发送和处理接口
|
||||||
|
|||||||
337
docs/plugins/dependency-management.md
Normal file
337
docs/plugins/dependency-management.md
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
# 📦 插件依赖管理系统
|
||||||
|
|
||||||
|
> 🎯 **简介**:MaiBot插件系统提供了强大的Python包依赖管理功能,让插件开发更加便捷和可靠。
|
||||||
|
|
||||||
|
## ✨ 功能概述
|
||||||
|
|
||||||
|
### 🎯 核心能力
|
||||||
|
- **声明式依赖**:插件可以明确声明需要的Python包
|
||||||
|
- **智能检查**:自动检查依赖包的安装状态
|
||||||
|
- **版本控制**:精确的版本要求管理
|
||||||
|
- **可选依赖**:区分必需依赖和可选依赖
|
||||||
|
- **自动安装**:可选的自动安装功能
|
||||||
|
- **批量管理**:生成统一的requirements文件
|
||||||
|
- **安全控制**:防止意外安装和版本冲突
|
||||||
|
|
||||||
|
### 🔄 工作流程
|
||||||
|
1. **声明依赖** → 在插件中声明所需的Python包
|
||||||
|
2. **加载检查** → 插件加载时自动检查依赖状态
|
||||||
|
3. **状态报告** → 详细报告缺失或版本不匹配的依赖
|
||||||
|
4. **智能安装** → 可选择自动安装或手动安装
|
||||||
|
5. **运行时处理** → 插件运行时优雅处理依赖缺失
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 步骤1:声明依赖
|
||||||
|
|
||||||
|
在你的插件类中添加`python_dependencies`字段:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from src.plugin_system import BasePlugin, PythonDependency, register_plugin
|
||||||
|
|
||||||
|
@register_plugin
|
||||||
|
class MyPlugin(BasePlugin):
|
||||||
|
plugin_name = "my_plugin"
|
||||||
|
plugin_description = "我的示例插件"
|
||||||
|
|
||||||
|
# 声明Python包依赖
|
||||||
|
python_dependencies = [
|
||||||
|
PythonDependency(
|
||||||
|
package_name="requests",
|
||||||
|
version=">=2.25.0",
|
||||||
|
description="HTTP请求库,用于网络通信"
|
||||||
|
),
|
||||||
|
PythonDependency(
|
||||||
|
package_name="numpy",
|
||||||
|
version=">=1.20.0",
|
||||||
|
optional=True,
|
||||||
|
description="数值计算库(可选功能)"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_plugin_components(self):
|
||||||
|
# 返回插件组件
|
||||||
|
return []
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤2:处理依赖
|
||||||
|
|
||||||
|
在组件代码中优雅处理依赖缺失:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MyAction(BaseAction):
|
||||||
|
async def execute(self, action_input, context=None):
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
# 使用requests进行网络请求
|
||||||
|
response = requests.get("https://api.example.com")
|
||||||
|
return {"status": "success", "data": response.json()}
|
||||||
|
except ImportError:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"message": "功能不可用:缺少requests库",
|
||||||
|
"hint": "请运行: pip install requests>=2.25.0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤3:检查和管理
|
||||||
|
|
||||||
|
使用依赖管理API:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from src.plugin_system import plugin_manager
|
||||||
|
|
||||||
|
# 检查所有插件的依赖状态
|
||||||
|
result = plugin_manager.check_all_dependencies()
|
||||||
|
print(f"检查了 {result['total_plugins_checked']} 个插件")
|
||||||
|
print(f"缺少必需依赖的插件: {result['plugins_with_missing_required']} 个")
|
||||||
|
|
||||||
|
# 生成requirements文件
|
||||||
|
plugin_manager.generate_plugin_requirements("plugin_requirements.txt")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 详细教程
|
||||||
|
|
||||||
|
### PythonDependency 类详解
|
||||||
|
|
||||||
|
`PythonDependency`是依赖声明的核心类:
|
||||||
|
|
||||||
|
```python
|
||||||
|
PythonDependency(
|
||||||
|
package_name="requests", # 导入时的包名
|
||||||
|
version=">=2.25.0", # 版本要求
|
||||||
|
optional=False, # 是否为可选依赖
|
||||||
|
description="HTTP请求库", # 依赖描述
|
||||||
|
install_name="" # pip安装时的包名(可选)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 参数说明
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必需 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `package_name` | str | ✅ | Python导入时使用的包名(如`requests`) |
|
||||||
|
| `version` | str | ❌ | 版本要求,支持pip格式(如`>=1.0.0`, `==2.1.3`) |
|
||||||
|
| `optional` | bool | ❌ | 是否为可选依赖,默认`False` |
|
||||||
|
| `description` | str | ❌ | 依赖的用途描述 |
|
||||||
|
| `install_name` | str | ❌ | pip安装时的包名,默认与`package_name`相同 |
|
||||||
|
|
||||||
|
#### 版本格式示例
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 常用版本格式
|
||||||
|
PythonDependency("requests", ">=2.25.0") # 最小版本
|
||||||
|
PythonDependency("numpy", ">=1.20.0,<2.0.0") # 版本范围
|
||||||
|
PythonDependency("pillow", "==8.3.2") # 精确版本
|
||||||
|
PythonDependency("scipy", ">=1.7.0,!=1.8.0") # 排除特定版本
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 特殊情况处理
|
||||||
|
|
||||||
|
**导入名与安装名不同的包:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
PythonDependency(
|
||||||
|
package_name="PIL", # import PIL
|
||||||
|
install_name="Pillow", # pip install Pillow
|
||||||
|
version=">=8.0.0"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**可选依赖示例:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
python_dependencies = [
|
||||||
|
# 必需依赖 - 核心功能
|
||||||
|
PythonDependency(
|
||||||
|
package_name="requests",
|
||||||
|
version=">=2.25.0",
|
||||||
|
description="HTTP库,插件核心功能必需"
|
||||||
|
),
|
||||||
|
|
||||||
|
# 可选依赖 - 增强功能
|
||||||
|
PythonDependency(
|
||||||
|
package_name="numpy",
|
||||||
|
version=">=1.20.0",
|
||||||
|
optional=True,
|
||||||
|
description="数值计算库,用于高级数学运算"
|
||||||
|
),
|
||||||
|
PythonDependency(
|
||||||
|
package_name="matplotlib",
|
||||||
|
version=">=3.0.0",
|
||||||
|
optional=True,
|
||||||
|
description="绘图库,用于数据可视化功能"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 依赖检查机制
|
||||||
|
|
||||||
|
系统在以下时机会自动检查依赖:
|
||||||
|
|
||||||
|
1. **插件加载时**:检查插件声明的所有依赖
|
||||||
|
2. **手动调用时**:通过API主动检查
|
||||||
|
3. **运行时检查**:在组件执行时动态检查
|
||||||
|
|
||||||
|
#### 检查结果状态
|
||||||
|
|
||||||
|
| 状态 | 描述 | 处理建议 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `no_dependencies` | 插件未声明任何依赖 | 无需处理 |
|
||||||
|
| `ok` | 所有依赖都已满足 | 正常使用 |
|
||||||
|
| `missing_optional` | 缺少可选依赖 | 部分功能不可用,考虑安装 |
|
||||||
|
| `missing_required` | 缺少必需依赖 | 插件功能受限,需要安装 |
|
||||||
|
|
||||||
|
## 🎯 最佳实践
|
||||||
|
|
||||||
|
### 1. 依赖声明原则
|
||||||
|
|
||||||
|
#### ✅ 推荐做法
|
||||||
|
|
||||||
|
```python
|
||||||
|
python_dependencies = [
|
||||||
|
# 明确的版本要求
|
||||||
|
PythonDependency(
|
||||||
|
package_name="requests",
|
||||||
|
version=">=2.25.0,<3.0.0", # 主版本兼容
|
||||||
|
description="HTTP请求库,用于API调用"
|
||||||
|
),
|
||||||
|
|
||||||
|
# 合理的可选依赖
|
||||||
|
PythonDependency(
|
||||||
|
package_name="numpy",
|
||||||
|
version=">=1.20.0",
|
||||||
|
optional=True,
|
||||||
|
description="数值计算库,用于数据处理功能"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### ❌ 避免的做法
|
||||||
|
|
||||||
|
```python
|
||||||
|
python_dependencies = [
|
||||||
|
# 过于宽泛的版本要求
|
||||||
|
PythonDependency("requests"), # 没有版本限制
|
||||||
|
|
||||||
|
# 过于严格的版本要求
|
||||||
|
PythonDependency("numpy", "==1.21.0"), # 精确版本过于严格
|
||||||
|
|
||||||
|
# 缺少描述
|
||||||
|
PythonDependency("matplotlib", ">=3.0.0"), # 没有说明用途
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 错误处理模式
|
||||||
|
|
||||||
|
#### 优雅降级模式
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SmartAction(BaseAction):
|
||||||
|
async def execute(self, action_input, context=None):
|
||||||
|
# 检查可选依赖
|
||||||
|
try:
|
||||||
|
import numpy as np
|
||||||
|
# 使用numpy的高级功能
|
||||||
|
return await self._advanced_processing(action_input, np)
|
||||||
|
except ImportError:
|
||||||
|
# 降级到基础功能
|
||||||
|
return await self._basic_processing(action_input)
|
||||||
|
|
||||||
|
async def _advanced_processing(self, input_data, np):
|
||||||
|
"""使用numpy的高级处理"""
|
||||||
|
result = np.array(input_data).mean()
|
||||||
|
return {"result": result, "method": "advanced"}
|
||||||
|
|
||||||
|
async def _basic_processing(self, input_data):
|
||||||
|
"""基础处理(不依赖外部库)"""
|
||||||
|
result = sum(input_data) / len(input_data)
|
||||||
|
return {"result": result, "method": "basic"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 使用API
|
||||||
|
|
||||||
|
### 检查依赖状态
|
||||||
|
|
||||||
|
```python
|
||||||
|
from src.plugin_system import plugin_manager
|
||||||
|
|
||||||
|
# 检查所有插件依赖(仅检查,不安装)
|
||||||
|
result = plugin_manager.check_all_dependencies(auto_install=False)
|
||||||
|
|
||||||
|
# 检查并自动安装缺失的必需依赖
|
||||||
|
result = plugin_manager.check_all_dependencies(auto_install=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生成requirements文件
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 生成包含所有插件依赖的requirements文件
|
||||||
|
plugin_manager.generate_plugin_requirements("plugin_requirements.txt")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取依赖状态报告
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 获取详细的依赖检查报告
|
||||||
|
result = plugin_manager.check_all_dependencies()
|
||||||
|
for plugin_name, status in result['plugin_status'].items():
|
||||||
|
print(f"插件 {plugin_name}: {status['status']}")
|
||||||
|
if status['missing']:
|
||||||
|
print(f" 缺失必需依赖: {status['missing']}")
|
||||||
|
if status['optional_missing']:
|
||||||
|
print(f" 缺失可选依赖: {status['optional_missing']}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛡️ 安全考虑
|
||||||
|
|
||||||
|
### 1. 自动安装控制
|
||||||
|
- 🛡️ **默认手动**: 自动安装默认关闭,需要明确启用
|
||||||
|
- 🔍 **依赖审查**: 安装前会显示将要安装的包列表
|
||||||
|
- ⏱️ **超时控制**: 安装操作有超时限制(5分钟)
|
||||||
|
|
||||||
|
### 2. 权限管理
|
||||||
|
- 📁 **环境隔离**: 推荐在虚拟环境中使用
|
||||||
|
- 🔒 **版本锁定**: 支持精确的版本控制
|
||||||
|
- 📝 **安装日志**: 记录所有安装操作
|
||||||
|
|
||||||
|
## 📊 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
1. **依赖检查失败**
|
||||||
|
```python
|
||||||
|
# 手动检查包是否可导入
|
||||||
|
try:
|
||||||
|
import package_name
|
||||||
|
print("包可用")
|
||||||
|
except ImportError:
|
||||||
|
print("包不可用,需要安装")
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **版本冲突**
|
||||||
|
```python
|
||||||
|
# 检查已安装的包版本
|
||||||
|
import package_name
|
||||||
|
print(f"当前版本: {package_name.__version__}")
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **安装失败**
|
||||||
|
```python
|
||||||
|
# 查看安装日志
|
||||||
|
from src.plugin_system import dependency_manager
|
||||||
|
result = dependency_manager.get_install_summary()
|
||||||
|
print("安装日志:", result['install_log'])
|
||||||
|
print("失败详情:", result['failed_installs'])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔗 相关文档
|
||||||
|
|
||||||
|
- [🚀 快速开始指南](quick-start.md) - 创建你的第一个插件
|
||||||
|
- [⚡ Action组件详解](action-components.md) - Action开发指南
|
||||||
|
- [💻 Command组件详解](command-components.md) - Command开发指南
|
||||||
|
- [📋 开发规范](development-standards.md) - 代码规范和最佳实践
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
通过依赖管理系统,你的插件将更加健壮和易于维护。开始使用这些功能让你的插件开发更加高效吧! 🚀
|
||||||
477
docs/plugins/examples/dependency-example.md
Normal file
477
docs/plugins/examples/dependency-example.md
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
# 📦 依赖管理完整示例
|
||||||
|
|
||||||
|
> 这个示例展示了如何在插件中正确使用Python依赖管理功能。
|
||||||
|
|
||||||
|
## 🎯 示例插件:智能数据分析插件
|
||||||
|
|
||||||
|
这个插件展示了如何处理必需依赖、可选依赖,以及优雅降级处理。
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""
|
||||||
|
智能数据分析插件
|
||||||
|
展示依赖管理的完整用法
|
||||||
|
"""
|
||||||
|
|
||||||
|
from src.plugin_system import (
|
||||||
|
BasePlugin,
|
||||||
|
BaseAction,
|
||||||
|
register_plugin,
|
||||||
|
ActionInfo,
|
||||||
|
PythonDependency,
|
||||||
|
ActionActivationType
|
||||||
|
)
|
||||||
|
from src.common.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("data_analysis_plugin")
|
||||||
|
|
||||||
|
|
||||||
|
@register_plugin
|
||||||
|
class DataAnalysisPlugin(BasePlugin):
|
||||||
|
"""智能数据分析插件"""
|
||||||
|
|
||||||
|
plugin_name = "data_analysis_plugin"
|
||||||
|
plugin_description = "提供数据分析和可视化功能的示例插件"
|
||||||
|
plugin_version = "1.0.0"
|
||||||
|
plugin_author = "MaiBot Team"
|
||||||
|
|
||||||
|
# 声明Python包依赖
|
||||||
|
python_dependencies = [
|
||||||
|
# 必需依赖 - 核心功能
|
||||||
|
PythonDependency(
|
||||||
|
package_name="requests",
|
||||||
|
version=">=2.25.0",
|
||||||
|
description="HTTP库,用于获取外部数据"
|
||||||
|
),
|
||||||
|
|
||||||
|
# 可选依赖 - 数据处理
|
||||||
|
PythonDependency(
|
||||||
|
package_name="pandas",
|
||||||
|
version=">=1.3.0",
|
||||||
|
optional=True,
|
||||||
|
description="数据处理库,提供高级数据操作功能"
|
||||||
|
),
|
||||||
|
|
||||||
|
# 可选依赖 - 数值计算
|
||||||
|
PythonDependency(
|
||||||
|
package_name="numpy",
|
||||||
|
version=">=1.20.0",
|
||||||
|
optional=True,
|
||||||
|
description="数值计算库,用于数学运算"
|
||||||
|
),
|
||||||
|
|
||||||
|
# 可选依赖 - 数据可视化
|
||||||
|
PythonDependency(
|
||||||
|
package_name="matplotlib",
|
||||||
|
version=">=3.3.0",
|
||||||
|
optional=True,
|
||||||
|
description="绘图库,用于生成数据图表"
|
||||||
|
),
|
||||||
|
|
||||||
|
# 特殊情况:导入名与安装名不同
|
||||||
|
PythonDependency(
|
||||||
|
package_name="PIL",
|
||||||
|
install_name="Pillow",
|
||||||
|
version=">=8.0.0",
|
||||||
|
optional=True,
|
||||||
|
description="图像处理库,用于图表保存和处理"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_plugin_components(self):
|
||||||
|
"""返回插件组件"""
|
||||||
|
return [
|
||||||
|
# 基础数据获取(只依赖requests)
|
||||||
|
(ActionInfo(
|
||||||
|
name="fetch_data_action",
|
||||||
|
description="获取外部数据",
|
||||||
|
focus_activation_type=ActionActivationType.KEYWORD,
|
||||||
|
normal_activation_type=ActionActivationType.KEYWORD,
|
||||||
|
activation_keywords=["获取数据", "下载数据"],
|
||||||
|
), FetchDataAction),
|
||||||
|
|
||||||
|
# 数据分析(依赖pandas和numpy)
|
||||||
|
(ActionInfo(
|
||||||
|
name="analyze_data_action",
|
||||||
|
description="数据分析和统计",
|
||||||
|
focus_activation_type=ActionActivationType.KEYWORD,
|
||||||
|
normal_activation_type=ActionActivationType.KEYWORD,
|
||||||
|
activation_keywords=["分析数据", "数据统计"],
|
||||||
|
), AnalyzeDataAction),
|
||||||
|
|
||||||
|
# 数据可视化(依赖matplotlib)
|
||||||
|
(ActionInfo(
|
||||||
|
name="visualize_data_action",
|
||||||
|
description="数据可视化",
|
||||||
|
focus_activation_type=ActionActivationType.KEYWORD,
|
||||||
|
normal_activation_type=ActionActivationType.KEYWORD,
|
||||||
|
activation_keywords=["数据图表", "可视化"],
|
||||||
|
), VisualizeDataAction),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class FetchDataAction(BaseAction):
|
||||||
|
"""数据获取Action - 仅依赖必需的requests库"""
|
||||||
|
|
||||||
|
async def execute(self, action_input, context=None):
|
||||||
|
"""获取外部数据"""
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# 模拟数据获取
|
||||||
|
url = action_input.get("url", "https://api.github.com/users/octocat")
|
||||||
|
|
||||||
|
response = requests.get(url, timeout=10)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": f"成功获取数据,响应大小: {len(str(data))} 字符",
|
||||||
|
"data": data,
|
||||||
|
"capabilities": ["basic_fetch"]
|
||||||
|
}
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"message": "缺少必需依赖:requests库",
|
||||||
|
"hint": "请运行: pip install requests>=2.25.0",
|
||||||
|
"error_code": "MISSING_DEPENDENCY"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"message": f"数据获取失败: {str(e)}",
|
||||||
|
"error_code": "FETCH_ERROR"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AnalyzeDataAction(BaseAction):
|
||||||
|
"""数据分析Action - 支持多级功能降级"""
|
||||||
|
|
||||||
|
async def execute(self, action_input, context=None):
|
||||||
|
"""分析数据,支持功能降级"""
|
||||||
|
|
||||||
|
# 检查可用的依赖
|
||||||
|
has_pandas = self._check_dependency("pandas")
|
||||||
|
has_numpy = self._check_dependency("numpy")
|
||||||
|
|
||||||
|
# 获取输入数据
|
||||||
|
data = action_input.get("data", [1, 2, 3, 4, 5])
|
||||||
|
|
||||||
|
if has_pandas and has_numpy:
|
||||||
|
return await self._advanced_analysis(data)
|
||||||
|
elif has_numpy:
|
||||||
|
return await self._numpy_analysis(data)
|
||||||
|
else:
|
||||||
|
return await self._basic_analysis(data)
|
||||||
|
|
||||||
|
def _check_dependency(self, package_name):
|
||||||
|
"""检查依赖是否可用"""
|
||||||
|
try:
|
||||||
|
__import__(package_name)
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _advanced_analysis(self, data):
|
||||||
|
"""高级分析(使用pandas + numpy)"""
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# 转换为DataFrame
|
||||||
|
df = pd.DataFrame({"values": data})
|
||||||
|
|
||||||
|
# 高级统计分析
|
||||||
|
stats = {
|
||||||
|
"count": len(df),
|
||||||
|
"mean": df["values"].mean(),
|
||||||
|
"median": df["values"].median(),
|
||||||
|
"std": df["values"].std(),
|
||||||
|
"min": df["values"].min(),
|
||||||
|
"max": df["values"].max(),
|
||||||
|
"quartiles": df["values"].quantile([0.25, 0.5, 0.75]).to_dict(),
|
||||||
|
"skewness": df["values"].skew(),
|
||||||
|
"kurtosis": df["values"].kurtosis()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "高级数据分析完成",
|
||||||
|
"data": stats,
|
||||||
|
"method": "advanced",
|
||||||
|
"capabilities": ["pandas", "numpy", "advanced_stats"]
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _numpy_analysis(self, data):
|
||||||
|
"""中级分析(仅使用numpy)"""
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
arr = np.array(data)
|
||||||
|
|
||||||
|
stats = {
|
||||||
|
"count": len(arr),
|
||||||
|
"mean": np.mean(arr),
|
||||||
|
"median": np.median(arr),
|
||||||
|
"std": np.std(arr),
|
||||||
|
"min": np.min(arr),
|
||||||
|
"max": np.max(arr),
|
||||||
|
"sum": np.sum(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "数值计算分析完成",
|
||||||
|
"data": stats,
|
||||||
|
"method": "numpy",
|
||||||
|
"capabilities": ["numpy", "basic_stats"]
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _basic_analysis(self, data):
|
||||||
|
"""基础分析(纯Python)"""
|
||||||
|
|
||||||
|
stats = {
|
||||||
|
"count": len(data),
|
||||||
|
"mean": sum(data) / len(data) if data else 0,
|
||||||
|
"min": min(data) if data else None,
|
||||||
|
"max": max(data) if data else None,
|
||||||
|
"sum": sum(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "基础数据分析完成",
|
||||||
|
"data": stats,
|
||||||
|
"method": "basic",
|
||||||
|
"capabilities": ["pure_python"],
|
||||||
|
"note": "安装numpy和pandas可获得更多分析功能"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class VisualizeDataAction(BaseAction):
|
||||||
|
"""数据可视化Action - 展示条件功能启用"""
|
||||||
|
|
||||||
|
async def execute(self, action_input, context=None):
|
||||||
|
"""数据可视化"""
|
||||||
|
|
||||||
|
# 检查可视化依赖
|
||||||
|
visualization_available = self._check_visualization_deps()
|
||||||
|
|
||||||
|
if not visualization_available:
|
||||||
|
return {
|
||||||
|
"status": "unavailable",
|
||||||
|
"message": "数据可视化功能不可用",
|
||||||
|
"reason": "缺少matplotlib和PIL依赖",
|
||||||
|
"install_hint": "pip install matplotlib>=3.3.0 Pillow>=8.0.0",
|
||||||
|
"alternative": "可以使用基础数据分析功能"
|
||||||
|
}
|
||||||
|
|
||||||
|
return await self._create_visualization(action_input)
|
||||||
|
|
||||||
|
def _check_visualization_deps(self):
|
||||||
|
"""检查可视化所需的依赖"""
|
||||||
|
try:
|
||||||
|
import matplotlib
|
||||||
|
import PIL
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _create_visualization(self, action_input):
|
||||||
|
"""创建数据可视化"""
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import io
|
||||||
|
import base64
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# 获取数据
|
||||||
|
data = action_input.get("data", [1, 2, 3, 4, 5])
|
||||||
|
chart_type = action_input.get("type", "line")
|
||||||
|
|
||||||
|
# 创建图表
|
||||||
|
plt.figure(figsize=(10, 6))
|
||||||
|
|
||||||
|
if chart_type == "line":
|
||||||
|
plt.plot(data)
|
||||||
|
plt.title("线性图")
|
||||||
|
elif chart_type == "bar":
|
||||||
|
plt.bar(range(len(data)), data)
|
||||||
|
plt.title("柱状图")
|
||||||
|
elif chart_type == "hist":
|
||||||
|
plt.hist(data, bins=10)
|
||||||
|
plt.title("直方图")
|
||||||
|
else:
|
||||||
|
plt.plot(data)
|
||||||
|
plt.title("默认线性图")
|
||||||
|
|
||||||
|
plt.xlabel("索引")
|
||||||
|
plt.ylabel("数值")
|
||||||
|
plt.grid(True)
|
||||||
|
|
||||||
|
# 保存为字节流
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
plt.savefig(buffer, format='png', dpi=150, bbox_inches='tight')
|
||||||
|
buffer.seek(0)
|
||||||
|
|
||||||
|
# 转换为base64
|
||||||
|
image_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||||||
|
|
||||||
|
plt.close() # 释放内存
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": f"生成{chart_type}图表成功",
|
||||||
|
"data": {
|
||||||
|
"chart_type": chart_type,
|
||||||
|
"data_points": len(data),
|
||||||
|
"image_base64": image_base64
|
||||||
|
},
|
||||||
|
"capabilities": ["matplotlib", "pillow", "visualization"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 测试和演示代码
|
||||||
|
async def demo_dependency_management():
|
||||||
|
"""演示依赖管理功能"""
|
||||||
|
|
||||||
|
print("🔍 插件依赖管理演示")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# 创建插件实例
|
||||||
|
plugin = DataAnalysisPlugin()
|
||||||
|
|
||||||
|
print("\n📦 插件依赖信息:")
|
||||||
|
for dep in plugin.python_dependencies:
|
||||||
|
status = "✅" if plugin._check_dependency_available(dep.package_name) else "❌"
|
||||||
|
optional_str = " (可选)" if dep.optional else " (必需)"
|
||||||
|
print(f" {status} {dep.package_name} {dep.version}{optional_str}")
|
||||||
|
print(f" {dep.description}")
|
||||||
|
|
||||||
|
print("\n🧪 功能测试:")
|
||||||
|
|
||||||
|
# 测试数据获取
|
||||||
|
fetch_action = FetchDataAction()
|
||||||
|
result = await fetch_action.execute({"url": "https://httpbin.org/json"})
|
||||||
|
print(f" 数据获取: {result['status']}")
|
||||||
|
|
||||||
|
# 测试数据分析
|
||||||
|
analyze_action = AnalyzeDataAction()
|
||||||
|
test_data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
result = await analyze_action.execute({"data": test_data})
|
||||||
|
print(f" 数据分析: {result['status']} (方法: {result.get('method', 'unknown')})")
|
||||||
|
print(f" 可用功能: {result.get('capabilities', [])}")
|
||||||
|
|
||||||
|
# 测试数据可视化
|
||||||
|
viz_action = VisualizeDataAction()
|
||||||
|
result = await viz_action.execute({"data": test_data, "type": "line"})
|
||||||
|
print(f" 数据可视化: {result['status']}")
|
||||||
|
|
||||||
|
print("\n💡 依赖管理建议:")
|
||||||
|
missing_deps = plugin.plugin_info.get_missing_packages()
|
||||||
|
if missing_deps:
|
||||||
|
print(" 缺失的必需依赖:")
|
||||||
|
for dep in missing_deps:
|
||||||
|
print(f" - {dep.get_pip_requirement()}")
|
||||||
|
print(f"\n 安装命令:")
|
||||||
|
print(f" pip install {' '.join([dep.get_pip_requirement() for dep in missing_deps])}")
|
||||||
|
else:
|
||||||
|
print(" ✅ 所有必需依赖都已安装")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
# 为演示添加依赖检查方法
|
||||||
|
def _check_dependency_available(package_name):
|
||||||
|
try:
|
||||||
|
__import__(package_name)
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
DataAnalysisPlugin._check_dependency_available = _check_dependency_available
|
||||||
|
|
||||||
|
# 运行演示
|
||||||
|
asyncio.run(demo_dependency_management())
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 示例说明
|
||||||
|
|
||||||
|
### 1. 依赖分层设计
|
||||||
|
|
||||||
|
这个示例展示了三层依赖设计:
|
||||||
|
|
||||||
|
- **必需依赖**: `requests` - 核心功能必需
|
||||||
|
- **增强依赖**: `pandas`, `numpy` - 提供更强大的分析能力
|
||||||
|
- **可选依赖**: `matplotlib`, `PIL` - 提供可视化功能
|
||||||
|
|
||||||
|
### 2. 优雅降级策略
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 三级功能降级
|
||||||
|
if has_pandas and has_numpy:
|
||||||
|
return await self._advanced_analysis(data) # 最佳体验
|
||||||
|
elif has_numpy:
|
||||||
|
return await self._numpy_analysis(data) # 中等体验
|
||||||
|
else:
|
||||||
|
return await self._basic_analysis(data) # 基础体验
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 条件功能启用
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 只有依赖可用时才提供功能
|
||||||
|
visualization_available = self._check_visualization_deps()
|
||||||
|
if not visualization_available:
|
||||||
|
return {"status": "unavailable", "install_hint": "..."}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 使用这个示例
|
||||||
|
|
||||||
|
### 1. 复制代码
|
||||||
|
|
||||||
|
将示例代码保存为 `plugins/data_analysis_plugin/plugin.py`
|
||||||
|
|
||||||
|
### 2. 测试依赖检查
|
||||||
|
|
||||||
|
```python
|
||||||
|
from src.plugin_system import plugin_manager
|
||||||
|
|
||||||
|
# 检查这个插件的依赖
|
||||||
|
result = plugin_manager.check_all_dependencies()
|
||||||
|
print(result['plugin_status']['data_analysis_plugin'])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 安装缺失依赖
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 生成requirements文件
|
||||||
|
plugin_manager.generate_plugin_requirements("data_plugin_deps.txt")
|
||||||
|
|
||||||
|
# 手动安装
|
||||||
|
# pip install -r data_plugin_deps.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 测试功能降级
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 测试基础功能(只安装requests)
|
||||||
|
pip install requests>=2.25.0
|
||||||
|
|
||||||
|
# 测试增强功能(添加数据处理)
|
||||||
|
pip install numpy>=1.20.0 pandas>=1.3.0
|
||||||
|
|
||||||
|
# 测试完整功能(添加可视化)
|
||||||
|
pip install matplotlib>=3.3.0 Pillow>=8.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## 💡 最佳实践总结
|
||||||
|
|
||||||
|
1. **分层依赖设计**: 区分核心、增强、可选依赖
|
||||||
|
2. **优雅降级处理**: 提供多级功能体验
|
||||||
|
3. **明确错误信息**: 告诉用户如何解决依赖问题
|
||||||
|
4. **条件功能启用**: 根据依赖可用性动态调整功能
|
||||||
|
5. **详细依赖描述**: 说明每个依赖的用途
|
||||||
|
|
||||||
|
这个示例展示了如何构建一个既强大又灵活的插件,即使在依赖不完整的情况下也能提供有用的功能。
|
||||||
@@ -124,6 +124,16 @@ class HelloWorldPlugin(BasePlugin):
|
|||||||
plugin_author = "你的名字"
|
plugin_author = "你的名字"
|
||||||
enable_plugin = True
|
enable_plugin = True
|
||||||
config_file_name = "config.toml"
|
config_file_name = "config.toml"
|
||||||
|
|
||||||
|
# Python依赖声明(可选)
|
||||||
|
python_dependencies = [
|
||||||
|
# 如果你的插件需要额外的Python包,在这里声明
|
||||||
|
# PythonDependency(
|
||||||
|
# package_name="requests",
|
||||||
|
# version=">=2.25.0",
|
||||||
|
# description="HTTP请求库"
|
||||||
|
# ),
|
||||||
|
]
|
||||||
|
|
||||||
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
|
||||||
"""返回插件包含的组件列表"""
|
"""返回插件包含的组件列表"""
|
||||||
@@ -245,14 +255,59 @@ python main.py
|
|||||||
2. **组件列表**: `get_plugin_components()` 返回所有组件
|
2. **组件列表**: `get_plugin_components()` 返回所有组件
|
||||||
3. **配置加载**: 自动加载 `config.toml` 文件
|
3. **配置加载**: 自动加载 `config.toml` 文件
|
||||||
|
|
||||||
|
## 📦 添加依赖包(可选)
|
||||||
|
|
||||||
|
如果你的插件需要额外的Python包,可以声明依赖:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from src.plugin_system import PythonDependency
|
||||||
|
|
||||||
|
@register_plugin
|
||||||
|
class HelloWorldPlugin(BasePlugin):
|
||||||
|
# ... 其他配置 ...
|
||||||
|
|
||||||
|
# 声明Python依赖
|
||||||
|
python_dependencies = [
|
||||||
|
PythonDependency(
|
||||||
|
package_name="requests",
|
||||||
|
version=">=2.25.0",
|
||||||
|
description="HTTP请求库,用于网络功能"
|
||||||
|
),
|
||||||
|
PythonDependency(
|
||||||
|
package_name="numpy",
|
||||||
|
version=">=1.20.0",
|
||||||
|
optional=True,
|
||||||
|
description="数值计算库(可选功能)"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 依赖检查
|
||||||
|
|
||||||
|
系统会自动检查依赖,你也可以手动检查:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from src.plugin_system import plugin_manager
|
||||||
|
|
||||||
|
# 检查所有插件依赖
|
||||||
|
result = plugin_manager.check_all_dependencies()
|
||||||
|
print(f"缺少依赖的插件: {result['plugins_with_missing_required']}个")
|
||||||
|
|
||||||
|
# 生成requirements文件
|
||||||
|
plugin_manager.generate_plugin_requirements("plugin_deps.txt")
|
||||||
|
```
|
||||||
|
|
||||||
|
📚 **详细了解**: [依赖管理系统](dependency-management.md)
|
||||||
|
|
||||||
## 🎯 下一步
|
## 🎯 下一步
|
||||||
|
|
||||||
恭喜!你已经创建了第一个MaiBot插件。接下来可以:
|
恭喜!你已经创建了第一个MaiBot插件。接下来可以:
|
||||||
|
|
||||||
1. 学习 [Action组件详解](action-components.md) 掌握更复杂的Action开发
|
1. 学习 [Action组件详解](action-components.md) 掌握更复杂的Action开发
|
||||||
2. 学习 [Command组件详解](command-components.md) 创建更强大的命令
|
2. 学习 [Command组件详解](command-components.md) 创建更强大的命令
|
||||||
3. 查看 [API参考](api/) 了解所有可用的接口
|
3. 了解 [依赖管理系统](dependency-management.md) 管理Python包依赖
|
||||||
4. 参考 [完整示例](examples/complete-examples.md) 学习最佳实践
|
4. 查看 [API参考](api/) 了解所有可用的接口
|
||||||
|
5. 参考 [完整示例](examples/complete-examples.md) 学习最佳实践
|
||||||
|
|
||||||
## 🐛 常见问题
|
## 🐛 常见问题
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ from src.plugin_system.base.component_types import (
|
|||||||
ActionInfo,
|
ActionInfo,
|
||||||
CommandInfo,
|
CommandInfo,
|
||||||
PluginInfo,
|
PluginInfo,
|
||||||
|
PythonDependency,
|
||||||
)
|
)
|
||||||
from src.plugin_system.apis.plugin_api import PluginAPI, create_plugin_api, create_command_api
|
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.plugin_manager import plugin_manager
|
||||||
from src.plugin_system.core.component_registry import component_registry
|
from src.plugin_system.core.component_registry import component_registry
|
||||||
|
from src.plugin_system.core.dependency_manager import dependency_manager
|
||||||
|
|
||||||
__version__ = "1.0.0"
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
@@ -36,6 +38,7 @@ __all__ = [
|
|||||||
"ActionInfo",
|
"ActionInfo",
|
||||||
"CommandInfo",
|
"CommandInfo",
|
||||||
"PluginInfo",
|
"PluginInfo",
|
||||||
|
"PythonDependency",
|
||||||
# API接口
|
# API接口
|
||||||
"PluginAPI",
|
"PluginAPI",
|
||||||
"create_plugin_api",
|
"create_plugin_api",
|
||||||
@@ -43,6 +46,7 @@ __all__ = [
|
|||||||
# 管理器
|
# 管理器
|
||||||
"plugin_manager",
|
"plugin_manager",
|
||||||
"component_registry",
|
"component_registry",
|
||||||
|
"dependency_manager",
|
||||||
# 装饰器
|
# 装饰器
|
||||||
"register_plugin",
|
"register_plugin",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from src.common.logger import get_logger
|
|||||||
from src.plugin_system.base.component_types import (
|
from src.plugin_system.base.component_types import (
|
||||||
PluginInfo,
|
PluginInfo,
|
||||||
ComponentInfo,
|
ComponentInfo,
|
||||||
|
PythonDependency,
|
||||||
)
|
)
|
||||||
from src.plugin_system.core.component_registry import component_registry
|
from src.plugin_system.core.component_registry import component_registry
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ class BasePlugin(ABC):
|
|||||||
plugin_author: str = "" # 插件作者
|
plugin_author: str = "" # 插件作者
|
||||||
enable_plugin: bool = True # 是否启用插件
|
enable_plugin: bool = True # 是否启用插件
|
||||||
dependencies: List[str] = [] # 依赖的其他插件
|
dependencies: List[str] = [] # 依赖的其他插件
|
||||||
|
python_dependencies: List[PythonDependency] = [] # Python包依赖
|
||||||
config_file_name: Optional[str] = None # 配置文件名
|
config_file_name: Optional[str] = None # 配置文件名
|
||||||
|
|
||||||
def __init__(self, plugin_dir: str = None):
|
def __init__(self, plugin_dir: str = None):
|
||||||
@@ -60,6 +62,7 @@ class BasePlugin(ABC):
|
|||||||
is_built_in=False,
|
is_built_in=False,
|
||||||
config_file=self.config_file_name or "",
|
config_file=self.config_file_name or "",
|
||||||
dependencies=self.dependencies.copy(),
|
dependencies=self.dependencies.copy(),
|
||||||
|
python_dependencies=self.python_dependencies.copy(),
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(f"{self.log_prefix} 插件基类初始化完成")
|
logger.debug(f"{self.log_prefix} 插件基类初始化完成")
|
||||||
|
|||||||
@@ -33,6 +33,27 @@ class ChatMode(Enum):
|
|||||||
ALL = "all" # 所有聊天模式
|
ALL = "all" # 所有聊天模式
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PythonDependency:
|
||||||
|
"""Python包依赖信息"""
|
||||||
|
|
||||||
|
package_name: str # 包名称
|
||||||
|
version: str = "" # 版本要求,例如: ">=1.0.0", "==2.1.3", ""表示任意版本
|
||||||
|
optional: bool = False # 是否为可选依赖
|
||||||
|
description: str = "" # 依赖描述
|
||||||
|
install_name: str = "" # 安装时的包名(如果与import名不同)
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if not self.install_name:
|
||||||
|
self.install_name = self.package_name
|
||||||
|
|
||||||
|
def get_pip_requirement(self) -> str:
|
||||||
|
"""获取pip安装格式的依赖字符串"""
|
||||||
|
if self.version:
|
||||||
|
return f"{self.install_name}{self.version}"
|
||||||
|
return self.install_name
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ComponentInfo:
|
class ComponentInfo:
|
||||||
"""组件信息"""
|
"""组件信息"""
|
||||||
@@ -107,6 +128,7 @@ class PluginInfo:
|
|||||||
is_built_in: bool = False # 是否为内置插件
|
is_built_in: bool = False # 是否为内置插件
|
||||||
components: List[ComponentInfo] = None # 包含的组件列表
|
components: List[ComponentInfo] = None # 包含的组件列表
|
||||||
dependencies: List[str] = None # 依赖的其他插件
|
dependencies: List[str] = None # 依赖的其他插件
|
||||||
|
python_dependencies: List[PythonDependency] = None # Python包依赖
|
||||||
config_file: str = "" # 配置文件路径
|
config_file: str = "" # 配置文件路径
|
||||||
metadata: Dict[str, Any] = None # 额外元数据
|
metadata: Dict[str, Any] = None # 额外元数据
|
||||||
|
|
||||||
@@ -115,5 +137,22 @@ class PluginInfo:
|
|||||||
self.components = []
|
self.components = []
|
||||||
if self.dependencies is None:
|
if self.dependencies is None:
|
||||||
self.dependencies = []
|
self.dependencies = []
|
||||||
|
if self.python_dependencies is None:
|
||||||
|
self.python_dependencies = []
|
||||||
if self.metadata is None:
|
if self.metadata is None:
|
||||||
self.metadata = {}
|
self.metadata = {}
|
||||||
|
|
||||||
|
def get_missing_packages(self) -> List[PythonDependency]:
|
||||||
|
"""检查缺失的Python包"""
|
||||||
|
missing = []
|
||||||
|
for dep in self.python_dependencies:
|
||||||
|
try:
|
||||||
|
__import__(dep.package_name)
|
||||||
|
except ImportError:
|
||||||
|
if not dep.optional:
|
||||||
|
missing.append(dep)
|
||||||
|
return missing
|
||||||
|
|
||||||
|
def get_pip_requirements(self) -> List[str]:
|
||||||
|
"""获取所有pip安装格式的依赖"""
|
||||||
|
return [dep.get_pip_requirement() for dep in self.python_dependencies]
|
||||||
|
|||||||
190
src/plugin_system/core/dependency_manager.py
Normal file
190
src/plugin_system/core/dependency_manager.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
"""
|
||||||
|
插件依赖管理器
|
||||||
|
|
||||||
|
负责检查和安装插件的Python包依赖
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import importlib
|
||||||
|
from typing import List, Dict, Tuple, Optional
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from src.common.logger import get_logger
|
||||||
|
from src.plugin_system.base.component_types import PythonDependency
|
||||||
|
|
||||||
|
logger = get_logger("dependency_manager")
|
||||||
|
|
||||||
|
|
||||||
|
class DependencyManager:
|
||||||
|
"""依赖管理器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.install_log: List[str] = []
|
||||||
|
self.failed_installs: Dict[str, str] = {}
|
||||||
|
|
||||||
|
def check_dependencies(self, dependencies: List[PythonDependency]) -> Tuple[List[PythonDependency], List[PythonDependency]]:
|
||||||
|
"""检查依赖包状态
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dependencies: 依赖包列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[List[PythonDependency], List[PythonDependency]]: (缺失的依赖, 可选缺失的依赖)
|
||||||
|
"""
|
||||||
|
missing_required = []
|
||||||
|
missing_optional = []
|
||||||
|
|
||||||
|
for dep in dependencies:
|
||||||
|
if not self._is_package_available(dep.package_name):
|
||||||
|
if dep.optional:
|
||||||
|
missing_optional.append(dep)
|
||||||
|
logger.warning(f"可选依赖包缺失: {dep.package_name} - {dep.description}")
|
||||||
|
else:
|
||||||
|
missing_required.append(dep)
|
||||||
|
logger.error(f"必需依赖包缺失: {dep.package_name} - {dep.description}")
|
||||||
|
else:
|
||||||
|
logger.debug(f"依赖包已存在: {dep.package_name}")
|
||||||
|
|
||||||
|
return missing_required, missing_optional
|
||||||
|
|
||||||
|
def _is_package_available(self, package_name: str) -> bool:
|
||||||
|
"""检查包是否可用"""
|
||||||
|
try:
|
||||||
|
importlib.import_module(package_name)
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def install_dependencies(self, dependencies: List[PythonDependency], auto_install: bool = False) -> bool:
|
||||||
|
"""安装依赖包
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dependencies: 需要安装的依赖包列表
|
||||||
|
auto_install: 是否自动安装(True时不询问用户)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 安装是否成功
|
||||||
|
"""
|
||||||
|
if not dependencies:
|
||||||
|
return True
|
||||||
|
|
||||||
|
logger.info(f"需要安装 {len(dependencies)} 个依赖包")
|
||||||
|
|
||||||
|
# 显示将要安装的包
|
||||||
|
for dep in dependencies:
|
||||||
|
install_cmd = dep.get_pip_requirement()
|
||||||
|
logger.info(f" - {install_cmd} {'(可选)' if dep.optional else '(必需)'}")
|
||||||
|
if dep.description:
|
||||||
|
logger.info(f" 说明: {dep.description}")
|
||||||
|
|
||||||
|
if not auto_install:
|
||||||
|
# 这里可以添加用户确认逻辑
|
||||||
|
logger.warning("手动安装模式:请手动运行 pip install 命令安装依赖包")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 执行安装
|
||||||
|
success_count = 0
|
||||||
|
for dep in dependencies:
|
||||||
|
if self._install_single_package(dep):
|
||||||
|
success_count += 1
|
||||||
|
else:
|
||||||
|
self.failed_installs[dep.package_name] = f"安装失败: {dep.get_pip_requirement()}"
|
||||||
|
|
||||||
|
logger.info(f"依赖安装完成: {success_count}/{len(dependencies)} 个成功")
|
||||||
|
return success_count == len(dependencies)
|
||||||
|
|
||||||
|
def _install_single_package(self, dependency: PythonDependency) -> bool:
|
||||||
|
"""安装单个包"""
|
||||||
|
pip_requirement = dependency.get_pip_requirement()
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"正在安装: {pip_requirement}")
|
||||||
|
|
||||||
|
# 使用subprocess安装包
|
||||||
|
cmd = [sys.executable, "-m", "pip", "install", pip_requirement]
|
||||||
|
result = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=300 # 5分钟超时
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
logger.info(f"✅ 成功安装: {pip_requirement}")
|
||||||
|
self.install_log.append(f"成功安装: {pip_requirement}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error(f"❌ 安装失败: {pip_requirement}")
|
||||||
|
logger.error(f"错误输出: {result.stderr}")
|
||||||
|
self.install_log.append(f"安装失败: {pip_requirement} - {result.stderr}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
logger.error(f"❌ 安装超时: {pip_requirement}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ 安装异常: {pip_requirement} - {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def generate_requirements_file(self, plugins_dependencies: List[List[PythonDependency]],
|
||||||
|
output_path: str = "plugin_requirements.txt") -> bool:
|
||||||
|
"""生成插件依赖的requirements文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plugins_dependencies: 所有插件的依赖列表
|
||||||
|
output_path: 输出文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 生成是否成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
all_deps = {}
|
||||||
|
|
||||||
|
# 合并所有插件的依赖
|
||||||
|
for plugin_deps in plugins_dependencies:
|
||||||
|
for dep in plugin_deps:
|
||||||
|
key = dep.install_name
|
||||||
|
if key in all_deps:
|
||||||
|
# 如果已存在,可以添加版本兼容性检查逻辑
|
||||||
|
existing = all_deps[key]
|
||||||
|
if dep.version and existing.version != dep.version:
|
||||||
|
logger.warning(f"依赖版本冲突: {key} ({existing.version} vs {dep.version})")
|
||||||
|
else:
|
||||||
|
all_deps[key] = dep
|
||||||
|
|
||||||
|
# 写入requirements文件
|
||||||
|
with open(output_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write("# 插件依赖包自动生成\n")
|
||||||
|
f.write("# Auto-generated plugin dependencies\n\n")
|
||||||
|
|
||||||
|
# 按包名排序
|
||||||
|
sorted_deps = sorted(all_deps.values(), key=lambda x: x.install_name)
|
||||||
|
|
||||||
|
for dep in sorted_deps:
|
||||||
|
requirement = dep.get_pip_requirement()
|
||||||
|
if dep.description:
|
||||||
|
f.write(f"# {dep.description}\n")
|
||||||
|
if dep.optional:
|
||||||
|
f.write(f"# Optional dependency\n")
|
||||||
|
f.write(f"{requirement}\n\n")
|
||||||
|
|
||||||
|
logger.info(f"已生成插件依赖文件: {output_path} ({len(all_deps)} 个包)")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"生成requirements文件失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_install_summary(self) -> Dict[str, any]:
|
||||||
|
"""获取安装摘要"""
|
||||||
|
return {
|
||||||
|
"install_log": self.install_log.copy(),
|
||||||
|
"failed_installs": self.failed_installs.copy(),
|
||||||
|
"total_attempts": len(self.install_log),
|
||||||
|
"failed_count": len(self.failed_installs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 全局依赖管理器实例
|
||||||
|
dependency_manager = DependencyManager()
|
||||||
@@ -9,7 +9,8 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.plugin_system.core.component_registry import component_registry
|
from src.plugin_system.core.component_registry import component_registry
|
||||||
from src.plugin_system.base.component_types import ComponentType, PluginInfo
|
from src.plugin_system.core.dependency_manager import dependency_manager
|
||||||
|
from src.plugin_system.base.component_types import ComponentType, PluginInfo, PythonDependency
|
||||||
|
|
||||||
logger = get_logger("plugin_manager")
|
logger = get_logger("plugin_manager")
|
||||||
|
|
||||||
@@ -344,6 +345,109 @@ class PluginManager:
|
|||||||
logger.warning("插件热重载功能尚未实现")
|
logger.warning("插件热重载功能尚未实现")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def check_all_dependencies(self, auto_install: bool = False) -> Dict[str, any]:
|
||||||
|
"""检查所有插件的Python依赖包
|
||||||
|
|
||||||
|
Args:
|
||||||
|
auto_install: 是否自动安装缺失的依赖包
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, any]: 检查结果摘要
|
||||||
|
"""
|
||||||
|
logger.info("开始检查所有插件的Python依赖包...")
|
||||||
|
|
||||||
|
all_required_missing = []
|
||||||
|
all_optional_missing = []
|
||||||
|
plugin_status = {}
|
||||||
|
|
||||||
|
for plugin_name, plugin_instance in self.loaded_plugins.items():
|
||||||
|
plugin_info = component_registry.get_plugin_info(plugin_name)
|
||||||
|
if not plugin_info or not plugin_info.python_dependencies:
|
||||||
|
plugin_status[plugin_name] = {"status": "no_dependencies", "missing": []}
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f"检查插件 {plugin_name} 的依赖...")
|
||||||
|
|
||||||
|
missing_required, missing_optional = dependency_manager.check_dependencies(
|
||||||
|
plugin_info.python_dependencies
|
||||||
|
)
|
||||||
|
|
||||||
|
if missing_required:
|
||||||
|
all_required_missing.extend(missing_required)
|
||||||
|
plugin_status[plugin_name] = {
|
||||||
|
"status": "missing_required",
|
||||||
|
"missing": [dep.package_name for dep in missing_required],
|
||||||
|
"optional_missing": [dep.package_name for dep in missing_optional]
|
||||||
|
}
|
||||||
|
logger.error(f"插件 {plugin_name} 缺少必需依赖: {[dep.package_name for dep in missing_required]}")
|
||||||
|
elif missing_optional:
|
||||||
|
all_optional_missing.extend(missing_optional)
|
||||||
|
plugin_status[plugin_name] = {
|
||||||
|
"status": "missing_optional",
|
||||||
|
"missing": [],
|
||||||
|
"optional_missing": [dep.package_name for dep in missing_optional]
|
||||||
|
}
|
||||||
|
logger.warning(f"插件 {plugin_name} 缺少可选依赖: {[dep.package_name for dep in missing_optional]}")
|
||||||
|
else:
|
||||||
|
plugin_status[plugin_name] = {"status": "ok", "missing": []}
|
||||||
|
logger.info(f"插件 {plugin_name} 依赖检查通过")
|
||||||
|
|
||||||
|
# 汇总结果
|
||||||
|
total_missing = len(set(dep.package_name for dep in all_required_missing))
|
||||||
|
total_optional_missing = len(set(dep.package_name for dep in all_optional_missing))
|
||||||
|
|
||||||
|
logger.info(f"依赖检查完成 - 缺少必需包: {total_missing}个, 缺少可选包: {total_optional_missing}个")
|
||||||
|
|
||||||
|
# 如果需要自动安装
|
||||||
|
install_success = True
|
||||||
|
if auto_install and all_required_missing:
|
||||||
|
# 去重
|
||||||
|
unique_required = {}
|
||||||
|
for dep in all_required_missing:
|
||||||
|
unique_required[dep.package_name] = dep
|
||||||
|
|
||||||
|
logger.info(f"开始自动安装 {len(unique_required)} 个必需依赖包...")
|
||||||
|
install_success = dependency_manager.install_dependencies(
|
||||||
|
list(unique_required.values()),
|
||||||
|
auto_install=True
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total_plugins_checked": len(plugin_status),
|
||||||
|
"plugins_with_missing_required": len([p for p in plugin_status.values() if p["status"] == "missing_required"]),
|
||||||
|
"plugins_with_missing_optional": len([p for p in plugin_status.values() if p["status"] == "missing_optional"]),
|
||||||
|
"total_missing_required": total_missing,
|
||||||
|
"total_missing_optional": total_optional_missing,
|
||||||
|
"plugin_status": plugin_status,
|
||||||
|
"auto_install_attempted": auto_install and bool(all_required_missing),
|
||||||
|
"auto_install_success": install_success,
|
||||||
|
"install_summary": dependency_manager.get_install_summary()
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_plugin_requirements(self, output_path: str = "plugin_requirements.txt") -> bool:
|
||||||
|
"""生成所有插件依赖的requirements文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
output_path: 输出文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 生成是否成功
|
||||||
|
"""
|
||||||
|
logger.info("开始生成插件依赖requirements文件...")
|
||||||
|
|
||||||
|
all_dependencies = []
|
||||||
|
|
||||||
|
for plugin_name, plugin_instance in self.loaded_plugins.items():
|
||||||
|
plugin_info = component_registry.get_plugin_info(plugin_name)
|
||||||
|
if plugin_info and plugin_info.python_dependencies:
|
||||||
|
all_dependencies.append(plugin_info.python_dependencies)
|
||||||
|
|
||||||
|
if not all_dependencies:
|
||||||
|
logger.info("没有找到任何插件依赖")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return dependency_manager.generate_requirements_file(all_dependencies, output_path)
|
||||||
|
|
||||||
|
|
||||||
# 全局插件管理器实例
|
# 全局插件管理器实例
|
||||||
plugin_manager = PluginManager()
|
plugin_manager = PluginManager()
|
||||||
|
|||||||
Reference in New Issue
Block a user