Merge branch 'dev' of https://github.com/Windpicker-owo/MaiBot into dev
This commit is contained in:
@@ -23,6 +23,8 @@
|
|||||||
6. 增加了插件和组件管理的API。
|
6. 增加了插件和组件管理的API。
|
||||||
7. `BaseCommand`的`execute`方法现在返回一个三元组,包含是否执行成功、可选的回复消息和是否拦截消息。
|
7. `BaseCommand`的`execute`方法现在返回一个三元组,包含是否执行成功、可选的回复消息和是否拦截消息。
|
||||||
- 这意味着你终于可以动态控制是否继续后续消息的处理了。
|
- 这意味着你终于可以动态控制是否继续后续消息的处理了。
|
||||||
|
8. 移除了dependency_manager,但是依然保留了`python_dependencies`属性,等待后续重构。
|
||||||
|
- 一并移除了文档有关manager的内容。
|
||||||
|
|
||||||
# 插件系统修改
|
# 插件系统修改
|
||||||
1. 现在所有的匹配模式不再是关键字了,而是枚举类。**(可能有遗漏)**
|
1. 现在所有的匹配模式不再是关键字了,而是枚举类。**(可能有遗漏)**
|
||||||
|
|||||||
@@ -1,93 +1,6 @@
|
|||||||
# 📦 插件依赖管理系统
|
# 📦 插件依赖管理系统
|
||||||
|
|
||||||
> 🎯 **简介**:MaiBot插件系统提供了强大的Python包依赖管理功能,让插件开发更加便捷和可靠。
|
现在的Python依赖包管理依然存在问题,请保留你的`python_dependencies`属性,等待后续重构。
|
||||||
|
|
||||||
## ✨ 功能概述
|
|
||||||
|
|
||||||
### 🎯 核心能力
|
|
||||||
- **声明式依赖**:插件可以明确声明需要的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):
|
|
||||||
name = "my_plugin"
|
|
||||||
|
|
||||||
# 声明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")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📚 详细教程
|
## 📚 详细教程
|
||||||
|
|
||||||
@@ -97,11 +10,11 @@ plugin_manager.generate_plugin_requirements("plugin_requirements.txt")
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
PythonDependency(
|
PythonDependency(
|
||||||
package_name="requests", # 导入时的包名
|
package_name="PIL", # 导入时的包名
|
||||||
version=">=2.25.0", # 版本要求
|
version=">=11.2.0", # 版本要求
|
||||||
optional=False, # 是否为可选依赖
|
optional=False, # 是否为可选依赖
|
||||||
description="HTTP请求库", # 依赖描述
|
description="图像处理库", # 依赖描述
|
||||||
install_name="" # pip安装时的包名(可选)
|
install_name="pillow" # pip安装时的包名(可选)
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -110,10 +23,10 @@ PythonDependency(
|
|||||||
| 参数 | 类型 | 必需 | 说明 |
|
| 参数 | 类型 | 必需 | 说明 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| `package_name` | str | ✅ | Python导入时使用的包名(如`requests`) |
|
| `package_name` | str | ✅ | Python导入时使用的包名(如`requests`) |
|
||||||
| `version` | str | ❌ | 版本要求,支持pip格式(如`>=1.0.0`, `==2.1.3`) |
|
| `version` | str | ❌ | 版本要求,使用pip格式(如`>=1.0.0`, `==2.1.3`) |
|
||||||
| `optional` | bool | ❌ | 是否为可选依赖,默认`False` |
|
| `optional` | bool | ❌ | 是否为可选依赖,默认`False` |
|
||||||
| `description` | str | ❌ | 依赖的用途描述 |
|
| `description` | str | ❌ | 依赖的用途描述 |
|
||||||
| `install_name` | str | ❌ | pip安装时的包名,默认与`package_name`相同 |
|
| `install_name` | str | ❌ | pip安装时的包名,默认与`package_name`相同,用于处理安装名称和导入名称不一致的情况 |
|
||||||
|
|
||||||
#### 版本格式示例
|
#### 版本格式示例
|
||||||
|
|
||||||
@@ -125,201 +38,3 @@ PythonDependency("pillow", "==8.3.2") # 精确版本
|
|||||||
PythonDependency("scipy", ">=1.7.0,!=1.8.0") # 排除特定版本
|
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'])
|
|
||||||
```
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
@@ -72,3 +72,9 @@ Command vs Action 选择指南
|
|||||||
2. 查看相关示例代码
|
2. 查看相关示例代码
|
||||||
3. 参考其他类似插件
|
3. 参考其他类似插件
|
||||||
4. 提交文档仓库issue
|
4. 提交文档仓库issue
|
||||||
|
|
||||||
|
## 一个方便的小设计
|
||||||
|
|
||||||
|
我们在`__init__.py`中定义了一个`__all__`变量,包含了所有需要导出的类和函数。
|
||||||
|
这样在其他地方导入时,可以直接使用 `from src.plugin_system import *` 来导入所有插件相关的类和函数。
|
||||||
|
或者你可以直接使用 `from src.plugin_system import BasePlugin, register_plugin, ComponentInfo` 之类的方式来导入你需要的部分。
|
||||||
@@ -8,6 +8,12 @@
|
|||||||
|
|
||||||
以下代码都在我们的`plugins/hello_world_plugin/`目录下。
|
以下代码都在我们的`plugins/hello_world_plugin/`目录下。
|
||||||
|
|
||||||
|
### 一个方便的小设计
|
||||||
|
|
||||||
|
在开发中,我们在`__init__.py`中定义了一个`__all__`变量,包含了所有需要导出的类和函数。
|
||||||
|
这样在其他地方导入时,可以直接使用 `from src.plugin_system import *` 来导入所有插件相关的类和函数。
|
||||||
|
或者你可以直接使用 `from src.plugin_system import BasePlugin, register_plugin, ComponentInfo` 之类的方式来导入你需要的部分。
|
||||||
|
|
||||||
### 📂 准备工作
|
### 📂 准备工作
|
||||||
|
|
||||||
确保你已经:
|
确保你已经:
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ from src.chat.chat_loop.hfc_utils import CycleDetail
|
|||||||
from src.person_info.relationship_builder_manager import relationship_builder_manager
|
from src.person_info.relationship_builder_manager import relationship_builder_manager
|
||||||
from src.person_info.person_info import get_person_info_manager
|
from src.person_info.person_info import get_person_info_manager
|
||||||
from src.plugin_system.base.component_types import ActionInfo, ChatMode
|
from src.plugin_system.base.component_types import ActionInfo, ChatMode
|
||||||
from src.plugin_system.apis import generator_api, send_api, message_api
|
from src.plugin_system.apis import generator_api, send_api, message_api, database_api
|
||||||
from src.chat.willing.willing_manager import get_willing_manager
|
from src.chat.willing.willing_manager import get_willing_manager
|
||||||
from src.mais4u.mai_think import mai_thinking_manager
|
from src.mais4u.mai_think import mai_thinking_manager
|
||||||
from maim_message.message_base import GroupInfo
|
|
||||||
from src.mais4u.constant_s4u import ENABLE_S4U
|
from src.mais4u.constant_s4u import ENABLE_S4U
|
||||||
|
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
||||||
|
from src.chat.chat_loop.hfc_utils import send_typing, stop_typing
|
||||||
|
|
||||||
ERROR_LOOP_INFO = {
|
ERROR_LOOP_INFO = {
|
||||||
"loop_plan_info": {
|
"loop_plan_info": {
|
||||||
@@ -254,44 +255,19 @@ class HeartFChatting:
|
|||||||
person_name = await person_info_manager.get_value(person_id, "person_name")
|
person_name = await person_info_manager.get_value(person_id, "person_name")
|
||||||
return f"{person_name}:{message_data.get('processed_plain_text')}"
|
return f"{person_name}:{message_data.get('processed_plain_text')}"
|
||||||
|
|
||||||
async def send_typing(self):
|
|
||||||
group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心")
|
|
||||||
|
|
||||||
chat = await get_chat_manager().get_or_create_stream(
|
|
||||||
platform="amaidesu_default",
|
|
||||||
user_info=None,
|
|
||||||
group_info=group_info,
|
|
||||||
)
|
|
||||||
|
|
||||||
await send_api.custom_to_stream(
|
|
||||||
message_type="state", content="typing", stream_id=chat.stream_id, storage_message=False
|
|
||||||
)
|
|
||||||
|
|
||||||
async def stop_typing(self):
|
|
||||||
group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心")
|
|
||||||
|
|
||||||
chat = await get_chat_manager().get_or_create_stream(
|
|
||||||
platform="amaidesu_default",
|
|
||||||
user_info=None,
|
|
||||||
group_info=group_info,
|
|
||||||
)
|
|
||||||
|
|
||||||
await send_api.custom_to_stream(
|
|
||||||
message_type="state", content="stop_typing", stream_id=chat.stream_id, storage_message=False
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _observe(self, message_data: Optional[Dict[str, Any]] = None):
|
async def _observe(self, message_data: Optional[Dict[str, Any]] = None):
|
||||||
# sourcery skip: hoist-statement-from-if, merge-comparisons, reintroduce-else
|
# sourcery skip: hoist-statement-from-if, merge-comparisons, reintroduce-else
|
||||||
if not message_data:
|
if not message_data:
|
||||||
message_data = {}
|
message_data = {}
|
||||||
action_type = "no_action"
|
action_type = "no_action"
|
||||||
|
reply_text = "" # 初始化reply_text变量,避免UnboundLocalError
|
||||||
# 创建新的循环信息
|
# 创建新的循环信息
|
||||||
cycle_timers, thinking_id = self.start_cycle()
|
cycle_timers, thinking_id = self.start_cycle()
|
||||||
|
|
||||||
logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]")
|
logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]")
|
||||||
|
|
||||||
if ENABLE_S4U:
|
if ENABLE_S4U:
|
||||||
await self.send_typing()
|
await send_typing()
|
||||||
|
|
||||||
async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()):
|
async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()):
|
||||||
loop_start_time = time.time()
|
loop_start_time = time.time()
|
||||||
@@ -308,9 +284,11 @@ class HeartFChatting:
|
|||||||
logger.error(f"{self.log_prefix} 动作修改失败: {e}")
|
logger.error(f"{self.log_prefix} 动作修改失败: {e}")
|
||||||
|
|
||||||
# 如果normal,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的)
|
# 如果normal,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的)
|
||||||
|
gen_task = None
|
||||||
|
reply_to_str = ""
|
||||||
if self.loop_mode == ChatMode.NORMAL:
|
if self.loop_mode == ChatMode.NORMAL:
|
||||||
reply_to_str = await self.build_reply_to_str(message_data)
|
reply_to_str = await self.build_reply_to_str(message_data)
|
||||||
gen_task = asyncio.create_task(self._generate_response(message_data, available_actions, reply_to_str))
|
gen_task = asyncio.create_task(self._generate_response(message_data, available_actions, reply_to_str, "chat.replyer.normal"))
|
||||||
|
|
||||||
with Timer("规划器", cycle_timers):
|
with Timer("规划器", cycle_timers):
|
||||||
plan_result, target_message = await self.action_planner.plan(mode=self.loop_mode)
|
plan_result, target_message = await self.action_planner.plan(mode=self.loop_mode)
|
||||||
@@ -325,54 +303,117 @@ class HeartFChatting:
|
|||||||
|
|
||||||
action_data["loop_start_time"] = loop_start_time
|
action_data["loop_start_time"] = loop_start_time
|
||||||
|
|
||||||
if self.loop_mode == ChatMode.NORMAL:
|
|
||||||
if action_type == "no_action":
|
if action_type == "reply":
|
||||||
logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复")
|
logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复")
|
||||||
elif is_parallel:
|
elif is_parallel:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作"
|
f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定执行{action_type}动作")
|
if not gen_task.done():
|
||||||
|
gen_task.cancel()
|
||||||
|
logger.debug(f"{self.log_prefix} 已取消预生成的回复任务")
|
||||||
|
logger.info(
|
||||||
|
f"{self.log_prefix}{global_config.bot.nickname} 原本想要回复,但选择执行{action_type},不发表回复"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
content = " ".join([item[1] for item in gen_task.result() if item[0] == "text"])
|
||||||
|
logger.debug(f"{self.log_prefix} 预生成的回复任务已完成")
|
||||||
|
logger.info(
|
||||||
|
f"{self.log_prefix}{global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复"
|
||||||
|
)
|
||||||
|
|
||||||
if action_type == "no_action":
|
|
||||||
|
action_message: Dict[str, Any] = message_data or target_message # type: ignore
|
||||||
|
if action_type == "reply" or is_parallel:
|
||||||
# 等待回复生成完毕
|
# 等待回复生成完毕
|
||||||
|
if self.loop_mode == ChatMode.NORMAL:
|
||||||
gather_timeout = global_config.chat.thinking_timeout
|
gather_timeout = global_config.chat.thinking_timeout
|
||||||
try:
|
try:
|
||||||
response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout)
|
response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
|
logger.warning(f"{self.log_prefix} 回复生成超时>{global_config.chat.thinking_timeout}s,已跳过")
|
||||||
response_set = None
|
response_set = None
|
||||||
|
|
||||||
if response_set:
|
# 模型炸了或超时,没有回复内容生成
|
||||||
content = " ".join([item[1] for item in response_set if item[0] == "text"])
|
|
||||||
|
|
||||||
# 模型炸了,没有回复内容生成
|
|
||||||
if not response_set:
|
if not response_set:
|
||||||
logger.warning(f"{self.log_prefix}模型未生成回复内容")
|
logger.warning(f"{self.log_prefix}模型未生成回复内容")
|
||||||
return False
|
return False
|
||||||
elif action_type not in ["no_action"] and not is_parallel:
|
else:
|
||||||
logger.info(
|
logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复 (focus模式)")
|
||||||
f"{self.log_prefix}{global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复"
|
|
||||||
)
|
# 构建reply_to字符串
|
||||||
|
reply_to_str = await self.build_reply_to_str(action_message)
|
||||||
|
|
||||||
|
# 生成回复
|
||||||
|
with Timer("回复生成", cycle_timers):
|
||||||
|
response_set = await self._generate_response(action_message, available_actions, reply_to_str, request_type="chat.replyer.focus")
|
||||||
|
|
||||||
|
if not response_set:
|
||||||
|
logger.warning(f"{self.log_prefix}模型未生成回复内容")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定的回复内容: {content}")
|
# 发送回复
|
||||||
|
with Timer("回复发送", cycle_timers):
|
||||||
|
reply_text = await self._send_response(response_set, reply_to_str, loop_start_time, action_message)
|
||||||
|
|
||||||
# 发送回复 (不再需要传入 chat)
|
# 存储reply action信息 (focus模式)
|
||||||
reply_text = await self._send_response(response_set, reply_to_str, loop_start_time,message_data)
|
person_info_manager = get_person_info_manager()
|
||||||
|
person_id = person_info_manager.get_person_id(
|
||||||
|
action_message.get("chat_info_platform", ""),
|
||||||
|
action_message.get("user_id", ""),
|
||||||
|
)
|
||||||
|
person_name = await person_info_manager.get_value(person_id, "person_name")
|
||||||
|
action_prompt_display = f"你对{person_name}进行了回复:{reply_text}"
|
||||||
|
|
||||||
|
await database_api.store_action_info(
|
||||||
|
chat_stream=self.chat_stream,
|
||||||
|
action_build_into_prompt=False,
|
||||||
|
action_prompt_display=action_prompt_display,
|
||||||
|
action_done=True,
|
||||||
|
thinking_id=thinking_id,
|
||||||
|
action_data={"reply_text": reply_text, "reply_to": reply_to_str},
|
||||||
|
action_name="reply",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 构建循环信息
|
||||||
|
loop_info = {
|
||||||
|
"loop_plan_info": {
|
||||||
|
"action_result": plan_result.get("action_result", {}),
|
||||||
|
},
|
||||||
|
"loop_action_info": {
|
||||||
|
"action_taken": True,
|
||||||
|
"reply_text": reply_text,
|
||||||
|
"command": "",
|
||||||
|
"taken_time": time.time(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
success = True
|
||||||
|
command = ""
|
||||||
if ENABLE_S4U:
|
|
||||||
await self.stop_typing()
|
|
||||||
await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
action_message: Dict[str, Any] = message_data or target_message # type: ignore
|
# 如果是并行执行且在normal模式下,需要等待预生成的回复任务完成
|
||||||
|
# if self.loop_mode == ChatMode.NORMAL and is_parallel and gen_task:
|
||||||
|
# # 等待预生成的回复任务完成
|
||||||
|
# gather_timeout = global_config.chat.thinking_timeout
|
||||||
|
# try:
|
||||||
|
# response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout)
|
||||||
|
# if response_set:
|
||||||
|
# # 发送回复
|
||||||
|
# with Timer("回复发送", cycle_timers):
|
||||||
|
# reply_text_parallel = await self._send_response(response_set, reply_to_str, loop_start_time, action_message)
|
||||||
|
# logger.info(f"{self.log_prefix} 并行执行:已发送回复内容")
|
||||||
|
# else:
|
||||||
|
# logger.warning(f"{self.log_prefix} 并行执行:预生成回复内容为空")
|
||||||
|
# except asyncio.TimeoutError:
|
||||||
|
# logger.warning(f"{self.log_prefix} 并行执行:回复生成超时>{global_config.chat.thinking_timeout}s,已跳过")
|
||||||
|
# except asyncio.CancelledError:
|
||||||
|
# logger.debug(f"{self.log_prefix} 并行执行:回复生成任务已被取消")
|
||||||
|
|
||||||
# 动作执行计时
|
# 动作执行计时
|
||||||
with Timer("动作执行", cycle_timers):
|
with Timer("动作执行", cycle_timers):
|
||||||
@@ -392,10 +433,10 @@ class HeartFChatting:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if loop_info["loop_action_info"]["command"] == "stop_focus_chat":
|
|
||||||
logger.info(f"{self.log_prefix} 麦麦决定停止专注聊天")
|
if ENABLE_S4U:
|
||||||
return False
|
await stop_typing()
|
||||||
# 停止该聊天模式的循环
|
await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text)
|
||||||
|
|
||||||
self.end_cycle(loop_info, cycle_timers)
|
self.end_cycle(loop_info, cycle_timers)
|
||||||
self.print_cycle_info(cycle_timers)
|
self.print_cycle_info(cycle_timers)
|
||||||
@@ -406,13 +447,11 @@ class HeartFChatting:
|
|||||||
# 管理no_reply计数器:当执行了非no_reply动作时,重置计数器
|
# 管理no_reply计数器:当执行了非no_reply动作时,重置计数器
|
||||||
if action_type != "no_reply" and action_type != "no_action":
|
if action_type != "no_reply" and action_type != "no_action":
|
||||||
# 导入NoReplyAction并重置计数器
|
# 导入NoReplyAction并重置计数器
|
||||||
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
|
||||||
NoReplyAction.reset_consecutive_count()
|
NoReplyAction.reset_consecutive_count()
|
||||||
logger.info(f"{self.log_prefix} 执行了{action_type}动作,重置no_reply计数器")
|
logger.info(f"{self.log_prefix} 执行了{action_type}动作,重置no_reply计数器")
|
||||||
return True
|
return True
|
||||||
elif action_type == "no_action":
|
elif action_type == "no_action":
|
||||||
# 当执行回复动作时,也重置no_reply计数器
|
# 当执行回复动作时,也重置no_reply计数器s
|
||||||
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
|
||||||
NoReplyAction.reset_consecutive_count()
|
NoReplyAction.reset_consecutive_count()
|
||||||
logger.info(f"{self.log_prefix} 执行了回复动作,重置no_reply计数器")
|
logger.info(f"{self.log_prefix} 执行了回复动作,重置no_reply计数器")
|
||||||
|
|
||||||
@@ -551,7 +590,7 @@ class HeartFChatting:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def _generate_response(
|
async def _generate_response(
|
||||||
self, message_data: dict, available_actions: Optional[Dict[str, ActionInfo]], reply_to: str
|
self, message_data: dict, available_actions: Optional[Dict[str, ActionInfo]], reply_to: str, request_type: str = "chat.replyer.normal"
|
||||||
) -> Optional[list]:
|
) -> Optional[list]:
|
||||||
"""生成普通回复"""
|
"""生成普通回复"""
|
||||||
try:
|
try:
|
||||||
@@ -559,8 +598,8 @@ class HeartFChatting:
|
|||||||
chat_stream=self.chat_stream,
|
chat_stream=self.chat_stream,
|
||||||
reply_to=reply_to,
|
reply_to=reply_to,
|
||||||
available_actions=available_actions,
|
available_actions=available_actions,
|
||||||
enable_tool=global_config.tool.enable_in_normal_chat,
|
enable_tool=global_config.tool.enable_tool,
|
||||||
request_type="chat.replyer.normal",
|
request_type=request_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not success or not reply_set:
|
if not success or not reply_set:
|
||||||
@@ -589,7 +628,7 @@ class HeartFChatting:
|
|||||||
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复"
|
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
logger.info(
|
||||||
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复"
|
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
from src.common.message_repository import count_messages
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
|
from src.chat.message_receive.chat_stream import get_chat_manager
|
||||||
|
from src.plugin_system.apis import send_api
|
||||||
|
from maim_message.message_base import GroupInfo
|
||||||
|
|
||||||
|
from src.common.message_repository import count_messages
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -106,3 +109,30 @@ def get_recent_message_stats(minutes: float = 30, chat_id: Optional[str] = None)
|
|||||||
bot_reply_count = count_messages(bot_filter)
|
bot_reply_count = count_messages(bot_filter)
|
||||||
|
|
||||||
return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count}
|
return {"bot_reply_count": bot_reply_count, "total_message_count": total_message_count}
|
||||||
|
|
||||||
|
|
||||||
|
async def send_typing():
|
||||||
|
group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心")
|
||||||
|
|
||||||
|
chat = await get_chat_manager().get_or_create_stream(
|
||||||
|
platform="amaidesu_default",
|
||||||
|
user_info=None,
|
||||||
|
group_info=group_info,
|
||||||
|
)
|
||||||
|
|
||||||
|
await send_api.custom_to_stream(
|
||||||
|
message_type="state", content="typing", stream_id=chat.stream_id, storage_message=False
|
||||||
|
)
|
||||||
|
|
||||||
|
async def stop_typing():
|
||||||
|
group_info = GroupInfo(platform="amaidesu_default", group_id="114514", group_name="内心")
|
||||||
|
|
||||||
|
chat = await get_chat_manager().get_or_create_stream(
|
||||||
|
platform="amaidesu_default",
|
||||||
|
user_info=None,
|
||||||
|
group_info=group_info,
|
||||||
|
)
|
||||||
|
|
||||||
|
await send_api.custom_to_stream(
|
||||||
|
message_type="state", content="stop_typing", stream_id=chat.stream_id, storage_message=False
|
||||||
|
)
|
||||||
@@ -33,10 +33,11 @@ def init_prompt():
|
|||||||
{time_block}
|
{time_block}
|
||||||
{identity_block}
|
{identity_block}
|
||||||
你现在需要根据聊天内容,选择的合适的action来参与聊天。
|
你现在需要根据聊天内容,选择的合适的action来参与聊天。
|
||||||
{chat_context_description},以下是具体的聊天内容:
|
{chat_context_description},以下是具体的聊天内容
|
||||||
{chat_content_block}
|
{chat_content_block}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{moderation_prompt}
|
{moderation_prompt}
|
||||||
|
|
||||||
现在请你根据{by_what}选择合适的action和触发action的消息:
|
现在请你根据{by_what}选择合适的action和触发action的消息:
|
||||||
@@ -224,6 +225,7 @@ class ActionPlanner:
|
|||||||
reasoning = f"Planner 内部处理错误: {outer_e}"
|
reasoning = f"Planner 内部处理错误: {outer_e}"
|
||||||
|
|
||||||
is_parallel = False
|
is_parallel = False
|
||||||
|
if mode == ChatMode.NORMAL:
|
||||||
if action in current_available_actions:
|
if action in current_available_actions:
|
||||||
is_parallel = current_available_actions[action].parallel_action
|
is_parallel = current_available_actions[action].parallel_action
|
||||||
|
|
||||||
@@ -311,7 +313,7 @@ class ActionPlanner:
|
|||||||
by_what = "聊天内容和用户的最新消息"
|
by_what = "聊天内容和用户的最新消息"
|
||||||
target_prompt = ""
|
target_prompt = ""
|
||||||
no_action_block = """重要说明:
|
no_action_block = """重要说明:
|
||||||
- 'no_action' 表示只进行普通聊天回复,不执行任何额外动作
|
- 'reply' 表示只进行普通聊天回复,不执行任何额外动作
|
||||||
- 其他action表示在普通回复的基础上,执行相应的额外动作"""
|
- 其他action表示在普通回复的基础上,执行相应的额外动作"""
|
||||||
|
|
||||||
chat_context_description = "你现在正在一个群聊中"
|
chat_context_description = "你现在正在一个群聊中"
|
||||||
|
|||||||
@@ -39,32 +39,6 @@ def init_prompt():
|
|||||||
Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1")
|
Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1")
|
||||||
Prompt("在群里聊天", "chat_target_group2")
|
Prompt("在群里聊天", "chat_target_group2")
|
||||||
Prompt("和{sender_name}聊天", "chat_target_private2")
|
Prompt("和{sender_name}聊天", "chat_target_private2")
|
||||||
Prompt("\n你有以下这些**知识**:\n{prompt_info}\n请你**记住上面的知识**,之后可能会用到。\n", "knowledge_prompt")
|
|
||||||
|
|
||||||
Prompt(
|
|
||||||
"""
|
|
||||||
{expression_habits_block}
|
|
||||||
{tool_info_block}
|
|
||||||
{knowledge_prompt}
|
|
||||||
{memory_block}
|
|
||||||
{relation_info_block}
|
|
||||||
{extra_info_block}
|
|
||||||
|
|
||||||
{chat_target}
|
|
||||||
{time_block}
|
|
||||||
{chat_info}
|
|
||||||
{reply_target_block}
|
|
||||||
{identity}
|
|
||||||
|
|
||||||
{action_descriptions}
|
|
||||||
你正在{chat_target_2},你现在的心情是:{mood_state}
|
|
||||||
现在请你读读之前的聊天记录,并给出回复
|
|
||||||
{config_expression_style}。注意不要复读你说过的话
|
|
||||||
{keywords_reaction_prompt}
|
|
||||||
{moderation_prompt}
|
|
||||||
不要浮夸,不要夸张修辞,不要输出多余内容(包括前后缀,冒号和引号,括号(),表情包,at或 @等 )。只输出回复内容""",
|
|
||||||
"default_generator_prompt",
|
|
||||||
)
|
|
||||||
|
|
||||||
Prompt(
|
Prompt(
|
||||||
"""
|
"""
|
||||||
@@ -113,7 +87,8 @@ def init_prompt():
|
|||||||
{core_dialogue_prompt}
|
{core_dialogue_prompt}
|
||||||
|
|
||||||
{reply_target_block}
|
{reply_target_block}
|
||||||
对方最新发送的内容:{message_txt}
|
|
||||||
|
|
||||||
你现在的心情是:{mood_state}
|
你现在的心情是:{mood_state}
|
||||||
{config_expression_style}
|
{config_expression_style}
|
||||||
注意不要复读你说过的话
|
注意不要复读你说过的话
|
||||||
@@ -177,7 +152,6 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
async def generate_reply_with_context(
|
async def generate_reply_with_context(
|
||||||
self,
|
self,
|
||||||
reply_data: Optional[Dict[str, Any]] = None,
|
|
||||||
reply_to: str = "",
|
reply_to: str = "",
|
||||||
extra_info: str = "",
|
extra_info: str = "",
|
||||||
available_actions: Optional[Dict[str, ActionInfo]] = None,
|
available_actions: Optional[Dict[str, ActionInfo]] = None,
|
||||||
@@ -186,30 +160,25 @@ class DefaultReplyer:
|
|||||||
) -> Tuple[bool, Optional[str], Optional[str]]:
|
) -> Tuple[bool, Optional[str], Optional[str]]:
|
||||||
"""
|
"""
|
||||||
回复器 (Replier): 核心逻辑,负责生成回复文本。
|
回复器 (Replier): 核心逻辑,负责生成回复文本。
|
||||||
(已整合原 HeartFCGenerator 的功能)
|
|
||||||
"""
|
"""
|
||||||
prompt = None
|
prompt = None
|
||||||
if available_actions is None:
|
if available_actions is None:
|
||||||
available_actions = {}
|
available_actions = {}
|
||||||
try:
|
try:
|
||||||
if not reply_data:
|
|
||||||
reply_data = {
|
|
||||||
"reply_to": reply_to,
|
|
||||||
"extra_info": extra_info,
|
|
||||||
}
|
|
||||||
for key, value in reply_data.items():
|
|
||||||
if not value:
|
|
||||||
logger.debug(f"回复数据跳过{key},生成回复时将忽略。")
|
|
||||||
|
|
||||||
# 3. 构建 Prompt
|
# 3. 构建 Prompt
|
||||||
with Timer("构建Prompt", {}): # 内部计时器,可选保留
|
with Timer("构建Prompt", {}): # 内部计时器,可选保留
|
||||||
prompt = await self.build_prompt_reply_context(
|
prompt = await self.build_prompt_reply_context(
|
||||||
reply_data=reply_data, # 传递action_data
|
reply_to = reply_to,
|
||||||
|
extra_info=extra_info,
|
||||||
available_actions=available_actions,
|
available_actions=available_actions,
|
||||||
enable_timeout=enable_timeout,
|
enable_timeout=enable_timeout,
|
||||||
enable_tool=enable_tool,
|
enable_tool=enable_tool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not prompt:
|
||||||
|
logger.warning("构建prompt失败,跳过回复生成")
|
||||||
|
return False, None, None
|
||||||
|
|
||||||
# 4. 调用 LLM 生成回复
|
# 4. 调用 LLM 生成回复
|
||||||
content = None
|
content = None
|
||||||
reasoning_content = None
|
reasoning_content = None
|
||||||
@@ -308,14 +277,13 @@ class DefaultReplyer:
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
async def build_relation_info(self, reply_data=None):
|
async def build_relation_info(self, reply_to: str = ""):
|
||||||
if not global_config.relationship.enable_relationship:
|
if not global_config.relationship.enable_relationship:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
relationship_fetcher = relationship_fetcher_manager.get_fetcher(self.chat_stream.stream_id)
|
relationship_fetcher = relationship_fetcher_manager.get_fetcher(self.chat_stream.stream_id)
|
||||||
if not reply_data:
|
if not reply_to:
|
||||||
return ""
|
return ""
|
||||||
reply_to = reply_data.get("reply_to", "")
|
|
||||||
sender, text = self._parse_reply_target(reply_to)
|
sender, text = self._parse_reply_target(reply_to)
|
||||||
if not sender or not text:
|
if not sender or not text:
|
||||||
return ""
|
return ""
|
||||||
@@ -407,7 +375,7 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
return memory_str
|
return memory_str
|
||||||
|
|
||||||
async def build_tool_info(self, chat_history, reply_data: Optional[Dict], enable_tool: bool = True):
|
async def build_tool_info(self, chat_history, reply_to: str = "", enable_tool: bool = True):
|
||||||
"""构建工具信息块
|
"""构建工具信息块
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -421,10 +389,9 @@ class DefaultReplyer:
|
|||||||
if not enable_tool:
|
if not enable_tool:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
if not reply_data:
|
if not reply_to:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
reply_to = reply_data.get("reply_to", "")
|
|
||||||
sender, text = self._parse_reply_target(reply_to)
|
sender, text = self._parse_reply_target(reply_to)
|
||||||
|
|
||||||
if not text:
|
if not text:
|
||||||
@@ -603,7 +570,8 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
async def build_prompt_reply_context(
|
async def build_prompt_reply_context(
|
||||||
self,
|
self,
|
||||||
reply_data: Dict[str, Any],
|
reply_to: str,
|
||||||
|
extra_info: str = "",
|
||||||
available_actions: Optional[Dict[str, ActionInfo]] = None,
|
available_actions: Optional[Dict[str, ActionInfo]] = None,
|
||||||
enable_timeout: bool = False,
|
enable_timeout: bool = False,
|
||||||
enable_tool: bool = True,
|
enable_tool: bool = True,
|
||||||
@@ -628,8 +596,6 @@ class DefaultReplyer:
|
|||||||
chat_id = chat_stream.stream_id
|
chat_id = chat_stream.stream_id
|
||||||
person_info_manager = get_person_info_manager()
|
person_info_manager = get_person_info_manager()
|
||||||
is_group_chat = bool(chat_stream.group_info)
|
is_group_chat = bool(chat_stream.group_info)
|
||||||
reply_to = reply_data.get("reply_to", "none")
|
|
||||||
extra_info_block = reply_data.get("extra_info", "") or reply_data.get("extra_info_block", "")
|
|
||||||
|
|
||||||
if global_config.mood.enable_mood:
|
if global_config.mood.enable_mood:
|
||||||
chat_mood = mood_manager.get_mood_by_chat_id(chat_id)
|
chat_mood = mood_manager.get_mood_by_chat_id(chat_id)
|
||||||
@@ -638,6 +604,13 @@ class DefaultReplyer:
|
|||||||
mood_prompt = ""
|
mood_prompt = ""
|
||||||
|
|
||||||
sender, target = self._parse_reply_target(reply_to)
|
sender, target = self._parse_reply_target(reply_to)
|
||||||
|
person_info_manager = get_person_info_manager()
|
||||||
|
person_id = person_info_manager.get_person_id_by_person_name(sender)
|
||||||
|
user_id = person_info_manager.get_value_sync(person_id, "user_id")
|
||||||
|
platform = chat_stream.platform
|
||||||
|
if user_id == global_config.bot.qq_account and platform == global_config.bot.platform:
|
||||||
|
logger.warning("选取了自身作为回复对象,跳过构建prompt")
|
||||||
|
return ""
|
||||||
|
|
||||||
target = replace_user_references_sync(target, chat_stream.platform, replace_bot_name=True)
|
target = replace_user_references_sync(target, chat_stream.platform, replace_bot_name=True)
|
||||||
|
|
||||||
@@ -656,21 +629,6 @@ class DefaultReplyer:
|
|||||||
limit=global_config.chat.max_context_size * 2,
|
limit=global_config.chat.max_context_size * 2,
|
||||||
)
|
)
|
||||||
|
|
||||||
message_list_before_now = get_raw_msg_before_timestamp_with_chat(
|
|
||||||
chat_id=chat_id,
|
|
||||||
timestamp=time.time(),
|
|
||||||
limit=global_config.chat.max_context_size,
|
|
||||||
)
|
|
||||||
chat_talking_prompt = build_readable_messages(
|
|
||||||
message_list_before_now,
|
|
||||||
replace_bot_name=True,
|
|
||||||
merge_messages=False,
|
|
||||||
timestamp_mode="normal_no_YMD",
|
|
||||||
read_mark=0.0,
|
|
||||||
truncate=True,
|
|
||||||
show_actions=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
message_list_before_short = get_raw_msg_before_timestamp_with_chat(
|
message_list_before_short = get_raw_msg_before_timestamp_with_chat(
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
timestamp=time.time(),
|
timestamp=time.time(),
|
||||||
@@ -690,10 +648,10 @@ class DefaultReplyer:
|
|||||||
self._time_and_run_task(
|
self._time_and_run_task(
|
||||||
self.build_expression_habits(chat_talking_prompt_short, target), "expression_habits"
|
self.build_expression_habits(chat_talking_prompt_short, target), "expression_habits"
|
||||||
),
|
),
|
||||||
self._time_and_run_task(self.build_relation_info(reply_data), "relation_info"),
|
self._time_and_run_task(self.build_relation_info(reply_to), "relation_info"),
|
||||||
self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "memory_block"),
|
self._time_and_run_task(self.build_memory_block(chat_talking_prompt_short, target), "memory_block"),
|
||||||
self._time_and_run_task(
|
self._time_and_run_task(
|
||||||
self.build_tool_info(chat_talking_prompt_short, reply_data, enable_tool=enable_tool), "tool_info"
|
self.build_tool_info(chat_talking_prompt_short, reply_to, enable_tool=enable_tool), "tool_info"
|
||||||
),
|
),
|
||||||
self._time_and_run_task(get_prompt_info(target, threshold=0.38), "prompt_info"),
|
self._time_and_run_task(get_prompt_info(target, threshold=0.38), "prompt_info"),
|
||||||
)
|
)
|
||||||
@@ -726,8 +684,8 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target)
|
keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target)
|
||||||
|
|
||||||
if extra_info_block:
|
if extra_info:
|
||||||
extra_info_block = f"以下是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策\n{extra_info_block}\n以上是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策"
|
extra_info_block = f"以下是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策\n{extra_info}\n以上是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策"
|
||||||
else:
|
else:
|
||||||
extra_info_block = ""
|
extra_info_block = ""
|
||||||
|
|
||||||
@@ -782,8 +740,6 @@ class DefaultReplyer:
|
|||||||
# 根据sender通过person_info_manager反向查找person_id,再获取user_id
|
# 根据sender通过person_info_manager反向查找person_id,再获取user_id
|
||||||
person_id = person_info_manager.get_person_id_by_person_name(sender)
|
person_id = person_info_manager.get_person_id_by_person_name(sender)
|
||||||
|
|
||||||
# 根据配置选择使用哪种 prompt 构建模式
|
|
||||||
if global_config.chat.use_s4u_prompt_mode and person_id:
|
|
||||||
# 使用 s4u 对话构建模式:分离当前对话对象和其他对话
|
# 使用 s4u 对话构建模式:分离当前对话对象和其他对话
|
||||||
try:
|
try:
|
||||||
user_id_value = await person_info_manager.get_value(person_id, "user_id")
|
user_id_value = await person_info_manager.get_value(person_id, "user_id")
|
||||||
@@ -841,44 +797,6 @@ class DefaultReplyer:
|
|||||||
keywords_reaction_prompt=keywords_reaction_prompt,
|
keywords_reaction_prompt=keywords_reaction_prompt,
|
||||||
moderation_prompt=moderation_prompt_block,
|
moderation_prompt=moderation_prompt_block,
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
self.build_mai_think_context(
|
|
||||||
chat_id=chat_id,
|
|
||||||
memory_block=memory_block,
|
|
||||||
relation_info=relation_info,
|
|
||||||
time_block=time_block,
|
|
||||||
chat_target_1=chat_target_1,
|
|
||||||
chat_target_2=chat_target_2,
|
|
||||||
mood_prompt=mood_prompt,
|
|
||||||
identity_block=identity_block,
|
|
||||||
sender=sender,
|
|
||||||
target=target,
|
|
||||||
chat_info=chat_talking_prompt,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 使用原有的模式
|
|
||||||
return await global_prompt_manager.format_prompt(
|
|
||||||
template_name,
|
|
||||||
expression_habits_block=expression_habits_block,
|
|
||||||
chat_target=chat_target_1,
|
|
||||||
chat_info=chat_talking_prompt,
|
|
||||||
memory_block=memory_block,
|
|
||||||
tool_info_block=tool_info,
|
|
||||||
knowledge_prompt=prompt_info,
|
|
||||||
extra_info_block=extra_info_block,
|
|
||||||
relation_info_block=relation_info,
|
|
||||||
time_block=time_block,
|
|
||||||
reply_target_block=reply_target_block,
|
|
||||||
moderation_prompt=moderation_prompt_block,
|
|
||||||
keywords_reaction_prompt=keywords_reaction_prompt,
|
|
||||||
identity=identity_block,
|
|
||||||
target_message=target,
|
|
||||||
sender_name=sender,
|
|
||||||
config_expression_style=global_config.expression.expression_style,
|
|
||||||
action_descriptions=action_descriptions,
|
|
||||||
chat_target_2=chat_target_2,
|
|
||||||
mood_state=mood_prompt,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def build_prompt_rewrite_context(
|
async def build_prompt_rewrite_context(
|
||||||
self,
|
self,
|
||||||
@@ -1079,9 +997,7 @@ async def get_prompt_info(message: str, threshold: float):
|
|||||||
logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}")
|
logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}")
|
||||||
|
|
||||||
# 格式化知识信息
|
# 格式化知识信息
|
||||||
formatted_prompt_info = await global_prompt_manager.format_prompt(
|
formatted_prompt_info = f"你有以下这些**知识**:\n{related_info}\n请你**记住上面的知识**,之后可能会用到。\n"
|
||||||
"knowledge_prompt", prompt_info=related_info
|
|
||||||
)
|
|
||||||
return formatted_prompt_info
|
return formatted_prompt_info
|
||||||
else:
|
else:
|
||||||
logger.debug("从LPMM知识库获取知识失败,可能是从未导入过知识,返回空知识...")
|
logger.debug("从LPMM知识库获取知识失败,可能是从未导入过知识,返回空知识...")
|
||||||
|
|||||||
@@ -77,15 +77,12 @@ class ChatConfig(ConfigBase):
|
|||||||
选择普通模型的概率为 1 - reasoning_normal_model_probability
|
选择普通模型的概率为 1 - reasoning_normal_model_probability
|
||||||
"""
|
"""
|
||||||
|
|
||||||
thinking_timeout: int = 30
|
thinking_timeout: int = 40
|
||||||
"""麦麦最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢)"""
|
"""麦麦最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢)"""
|
||||||
|
|
||||||
talk_frequency: float = 1
|
talk_frequency: float = 1
|
||||||
"""回复频率阈值"""
|
"""回复频率阈值"""
|
||||||
|
|
||||||
use_s4u_prompt_mode: bool = False
|
|
||||||
"""是否使用 s4u 对话构建模式,该模式会分开处理当前对话对象和其他所有对话的内容进行 prompt 构建"""
|
|
||||||
|
|
||||||
mentioned_bot_inevitable_reply: bool = False
|
mentioned_bot_inevitable_reply: bool = False
|
||||||
"""提及 bot 必然回复"""
|
"""提及 bot 必然回复"""
|
||||||
|
|
||||||
@@ -302,11 +299,8 @@ class ExpressionConfig(ConfigBase):
|
|||||||
class ToolConfig(ConfigBase):
|
class ToolConfig(ConfigBase):
|
||||||
"""工具配置类"""
|
"""工具配置类"""
|
||||||
|
|
||||||
enable_in_normal_chat: bool = False
|
enable_tool: bool = False
|
||||||
"""是否在普通聊天中启用工具"""
|
"""是否在聊天中启用工具"""
|
||||||
|
|
||||||
enable_in_focus_chat: bool = True
|
|
||||||
"""是否在专注聊天中启用工具"""
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class VoiceConfig(ConfigBase):
|
class VoiceConfig(ConfigBase):
|
||||||
|
|||||||
@@ -108,9 +108,13 @@ async def generate_reply(
|
|||||||
|
|
||||||
logger.debug("[GeneratorAPI] 开始生成回复")
|
logger.debug("[GeneratorAPI] 开始生成回复")
|
||||||
|
|
||||||
|
if not reply_to:
|
||||||
|
reply_to = action_data.get("reply_to", "")
|
||||||
|
if not extra_info and action_data:
|
||||||
|
extra_info = action_data.get("extra_info", "")
|
||||||
|
|
||||||
# 调用回复器生成回复
|
# 调用回复器生成回复
|
||||||
success, content, prompt = await replyer.generate_reply_with_context(
|
success, content, prompt = await replyer.generate_reply_with_context(
|
||||||
reply_data=action_data or {},
|
|
||||||
reply_to=reply_to,
|
reply_to=reply_to,
|
||||||
extra_info=extra_info,
|
extra_info=extra_info,
|
||||||
available_actions=available_actions,
|
available_actions=available_actions,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
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
|
|
||||||
from src.plugin_system.core.events_manager import events_manager
|
from src.plugin_system.core.events_manager import events_manager
|
||||||
from src.plugin_system.core.global_announcement_manager import global_announcement_manager
|
from src.plugin_system.core.global_announcement_manager import global_announcement_manager
|
||||||
from src.plugin_system.core.tool_use import tool_user
|
from src.plugin_system.core.tool_use import tool_user
|
||||||
@@ -14,7 +13,6 @@ from src.plugin_system.core.tool_use import tool_user
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"plugin_manager",
|
"plugin_manager",
|
||||||
"component_registry",
|
"component_registry",
|
||||||
"dependency_manager",
|
|
||||||
"events_manager",
|
"events_manager",
|
||||||
"global_announcement_manager",
|
"global_announcement_manager",
|
||||||
"tool_user",
|
"tool_user",
|
||||||
|
|||||||
@@ -1,190 +0,0 @@
|
|||||||
"""
|
|
||||||
插件依赖管理器
|
|
||||||
|
|
||||||
负责检查和安装插件的Python包依赖
|
|
||||||
"""
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import importlib
|
|
||||||
from typing import List, Dict, Tuple, Any
|
|
||||||
|
|
||||||
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 self._is_package_available(dep.package_name):
|
|
||||||
logger.debug(f"依赖包已存在: {dep.package_name}")
|
|
||||||
elif 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}")
|
|
||||||
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("# 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()
|
|
||||||
@@ -8,10 +8,9 @@ from pathlib import Path
|
|||||||
|
|
||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.plugin_system.base.plugin_base import PluginBase
|
from src.plugin_system.base.plugin_base import PluginBase
|
||||||
from src.plugin_system.base.component_types import ComponentType, PythonDependency
|
from src.plugin_system.base.component_types import ComponentType
|
||||||
from src.plugin_system.utils.manifest_utils import VersionComparator
|
from src.plugin_system.utils.manifest_utils import VersionComparator
|
||||||
from .component_registry import component_registry
|
from .component_registry import component_registry
|
||||||
from .dependency_manager import dependency_manager
|
|
||||||
|
|
||||||
logger = get_logger("plugin_manager")
|
logger = get_logger("plugin_manager")
|
||||||
|
|
||||||
@@ -207,104 +206,6 @@ class PluginManager:
|
|||||||
"""
|
"""
|
||||||
return self.loaded_plugins.get(plugin_name)
|
return self.loaded_plugins.get(plugin_name)
|
||||||
|
|
||||||
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: List[PythonDependency] = []
|
|
||||||
all_optional_missing: List[PythonDependency] = []
|
|
||||||
plugin_status = {}
|
|
||||||
|
|
||||||
for plugin_name in self.loaded_plugins:
|
|
||||||
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({dep.package_name for dep in all_required_missing})
|
|
||||||
total_optional_missing = len({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 = {dep.package_name: dep for dep in all_required_missing}
|
|
||||||
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 in self.loaded_plugins:
|
|
||||||
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)
|
|
||||||
|
|
||||||
# === 查询方法 ===
|
# === 查询方法 ===
|
||||||
def list_loaded_plugins(self) -> List[str]:
|
def list_loaded_plugins(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -24,11 +24,6 @@
|
|||||||
"is_built_in": true,
|
"is_built_in": true,
|
||||||
"plugin_type": "action_provider",
|
"plugin_type": "action_provider",
|
||||||
"components": [
|
"components": [
|
||||||
{
|
|
||||||
"type": "action",
|
|
||||||
"name": "reply",
|
|
||||||
"description": "参与聊天回复,发送文本进行表达"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "action",
|
"type": "action",
|
||||||
"name": "no_reply",
|
"name": "no_reply",
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ class NoReplyAction(BaseAction):
|
|||||||
|
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
async def execute(self) -> Tuple[bool, str]:
|
||||||
"""执行不回复动作"""
|
"""执行不回复动作"""
|
||||||
import asyncio
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
reason = self.action_data.get("reason", "")
|
reason = self.action_data.get("reason", "")
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from src.common.logger import get_logger
|
|||||||
# 导入API模块 - 标准Python包方式
|
# 导入API模块 - 标准Python包方式
|
||||||
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
||||||
from src.plugins.built_in.core_actions.emoji import EmojiAction
|
from src.plugins.built_in.core_actions.emoji import EmojiAction
|
||||||
from src.plugins.built_in.core_actions.reply import ReplyAction
|
|
||||||
|
|
||||||
logger = get_logger("core_actions")
|
logger = get_logger("core_actions")
|
||||||
|
|
||||||
@@ -52,10 +51,9 @@ class CoreActionsPlugin(BasePlugin):
|
|||||||
config_schema: dict = {
|
config_schema: dict = {
|
||||||
"plugin": {
|
"plugin": {
|
||||||
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
|
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
|
||||||
"config_version": ConfigField(type=str, default="0.4.0", description="配置文件版本"),
|
"config_version": ConfigField(type=str, default="0.5.0", description="配置文件版本"),
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"enable_reply": ConfigField(type=bool, default=True, description="是否启用回复动作"),
|
|
||||||
"enable_no_reply": ConfigField(type=bool, default=True, description="是否启用不回复动作"),
|
"enable_no_reply": ConfigField(type=bool, default=True, description="是否启用不回复动作"),
|
||||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"),
|
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"),
|
||||||
},
|
},
|
||||||
@@ -74,8 +72,6 @@ class CoreActionsPlugin(BasePlugin):
|
|||||||
|
|
||||||
# --- 根据配置注册组件 ---
|
# --- 根据配置注册组件 ---
|
||||||
components = []
|
components = []
|
||||||
if self.get_config("components.enable_reply", True):
|
|
||||||
components.append((ReplyAction.get_action_info(), ReplyAction))
|
|
||||||
if self.get_config("components.enable_no_reply", True):
|
if self.get_config("components.enable_no_reply", True):
|
||||||
components.append((NoReplyAction.get_action_info(), NoReplyAction))
|
components.append((NoReplyAction.get_action_info(), NoReplyAction))
|
||||||
if self.get_config("components.enable_emoji", True):
|
if self.get_config("components.enable_emoji", True):
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
# 导入新插件系统
|
|
||||||
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
|
|
||||||
from src.config.config import global_config
|
|
||||||
import random
|
|
||||||
import time
|
|
||||||
from typing import Tuple
|
|
||||||
import asyncio
|
|
||||||
import re
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
# 导入依赖的系统组件
|
|
||||||
from src.common.logger import get_logger
|
|
||||||
|
|
||||||
# 导入API模块 - 标准Python包方式
|
|
||||||
from src.plugin_system.apis import generator_api, message_api
|
|
||||||
# 注释:不再需要导入NoReplyAction,因为计数器管理已移至heartFC_chat.py
|
|
||||||
# from src.plugins.built_in.core_actions.no_reply import NoReplyAction
|
|
||||||
from src.person_info.person_info import get_person_info_manager
|
|
||||||
from src.mais4u.mai_think import mai_thinking_manager
|
|
||||||
from src.mais4u.constant_s4u import ENABLE_S4U
|
|
||||||
|
|
||||||
logger = get_logger("reply_action")
|
|
||||||
|
|
||||||
|
|
||||||
class ReplyAction(BaseAction):
|
|
||||||
"""回复动作 - 参与聊天回复"""
|
|
||||||
|
|
||||||
# 激活设置
|
|
||||||
focus_activation_type = ActionActivationType.NEVER
|
|
||||||
normal_activation_type = ActionActivationType.NEVER
|
|
||||||
mode_enable = ChatMode.FOCUS
|
|
||||||
parallel_action = False
|
|
||||||
|
|
||||||
# 动作基本信息
|
|
||||||
action_name = "reply"
|
|
||||||
action_description = ""
|
|
||||||
|
|
||||||
# 动作参数定义
|
|
||||||
action_parameters = {}
|
|
||||||
|
|
||||||
# 动作使用场景
|
|
||||||
action_require = [""]
|
|
||||||
|
|
||||||
# 关联类型
|
|
||||||
associated_types = ["text"]
|
|
||||||
|
|
||||||
def _parse_reply_target(self, target_message: str) -> tuple:
|
|
||||||
sender = ""
|
|
||||||
target = ""
|
|
||||||
# 添加None检查,防止NoneType错误
|
|
||||||
if target_message is None:
|
|
||||||
return sender, target
|
|
||||||
if ":" in target_message or ":" in target_message:
|
|
||||||
# 使用正则表达式匹配中文或英文冒号
|
|
||||||
parts = re.split(pattern=r"[::]", string=target_message, maxsplit=1)
|
|
||||||
if len(parts) == 2:
|
|
||||||
sender = parts[0].strip()
|
|
||||||
target = parts[1].strip()
|
|
||||||
return sender, target
|
|
||||||
|
|
||||||
async def execute(self) -> Tuple[bool, str]:
|
|
||||||
"""执行回复动作"""
|
|
||||||
logger.debug(f"{self.log_prefix} 决定进行回复")
|
|
||||||
start_time = self.action_data.get("loop_start_time", time.time())
|
|
||||||
|
|
||||||
user_id = self.user_id
|
|
||||||
platform = self.platform
|
|
||||||
# logger.info(f"{self.log_prefix} 用户ID: {user_id}, 平台: {platform}")
|
|
||||||
person_id = get_person_info_manager().get_person_id(platform, user_id) # type: ignore
|
|
||||||
# logger.info(f"{self.log_prefix} 人物ID: {person_id}")
|
|
||||||
person_name = get_person_info_manager().get_value_sync(person_id, "person_name")
|
|
||||||
reply_to = f"{person_name}:{self.action_message.get('processed_plain_text', '')}" # type: ignore
|
|
||||||
logger.info(f"{self.log_prefix} 决定进行回复,目标: {reply_to}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
if prepared_reply := self.action_data.get("prepared_reply", ""):
|
|
||||||
reply_text = prepared_reply
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
success, reply_set, _ = await asyncio.wait_for(
|
|
||||||
generator_api.generate_reply(
|
|
||||||
extra_info="",
|
|
||||||
reply_to=reply_to,
|
|
||||||
chat_id=self.chat_id,
|
|
||||||
request_type="chat.replyer.focus",
|
|
||||||
enable_tool=global_config.tool.enable_in_focus_chat,
|
|
||||||
),
|
|
||||||
timeout=global_config.chat.thinking_timeout,
|
|
||||||
)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
logger.warning(f"{self.log_prefix} 回复生成超时 ({global_config.chat.thinking_timeout}s)")
|
|
||||||
return False, "timeout"
|
|
||||||
|
|
||||||
# 检查从start_time以来的新消息数量
|
|
||||||
# 获取动作触发时间或使用默认值
|
|
||||||
current_time = time.time()
|
|
||||||
new_message_count = message_api.count_new_messages(
|
|
||||||
chat_id=self.chat_id, start_time=start_time, end_time=current_time
|
|
||||||
)
|
|
||||||
|
|
||||||
# 根据新消息数量决定是否使用reply_to
|
|
||||||
need_reply = new_message_count >= random.randint(2, 4)
|
|
||||||
if need_reply:
|
|
||||||
logger.info(
|
|
||||||
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.debug(
|
|
||||||
f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 构建回复文本
|
|
||||||
reply_text = ""
|
|
||||||
first_replied = False
|
|
||||||
reply_to_platform_id = f"{platform}:{user_id}"
|
|
||||||
for reply_seg in reply_set:
|
|
||||||
data = reply_seg[1]
|
|
||||||
if not first_replied:
|
|
||||||
if need_reply:
|
|
||||||
await self.send_text(
|
|
||||||
content=data, reply_to=reply_to, reply_to_platform_id=reply_to_platform_id, typing=False
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await self.send_text(content=data, reply_to_platform_id=reply_to_platform_id, typing=False)
|
|
||||||
first_replied = True
|
|
||||||
else:
|
|
||||||
await self.send_text(content=data, reply_to_platform_id=reply_to_platform_id, typing=True)
|
|
||||||
reply_text += data
|
|
||||||
|
|
||||||
# 存储动作记录
|
|
||||||
reply_text = f"你对{person_name}进行了回复:{reply_text}"
|
|
||||||
|
|
||||||
if ENABLE_S4U:
|
|
||||||
await mai_thinking_manager.get_mai_think(self.chat_id).do_think_after_response(reply_text)
|
|
||||||
|
|
||||||
await self.store_action_info(
|
|
||||||
action_build_into_prompt=False,
|
|
||||||
action_prompt_display=reply_text,
|
|
||||||
action_done=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 注释:重置NoReplyAction的连续计数器现在由heartFC_chat.py统一管理
|
|
||||||
# NoReplyAction.reset_consecutive_count()
|
|
||||||
|
|
||||||
return success, reply_text
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{self.log_prefix} 回复动作执行失败: {e}")
|
|
||||||
traceback.print_exc()
|
|
||||||
return False, f"回复失败: {str(e)}"
|
|
||||||
@@ -428,14 +428,14 @@ class PluginManagementPlugin(BasePlugin):
|
|||||||
config_file_name: str = "config.toml"
|
config_file_name: str = "config.toml"
|
||||||
config_schema: dict = {
|
config_schema: dict = {
|
||||||
"plugin": {
|
"plugin": {
|
||||||
"enable": ConfigField(bool, default=False, description="是否启用插件"),
|
"enabled": ConfigField(bool, default=False, description="是否启用插件"),
|
||||||
"config_version": ConfigField(type=str, default="1.0.0", description="配置文件版本"),
|
"config_version": ConfigField(type=str, default="1.1.0", description="配置文件版本"),
|
||||||
"permission": ConfigField(list, default=[], description="有权限使用插件管理命令的用户列表"),
|
"permission": ConfigField(list, default=[], description="有权限使用插件管理命令的用户列表,请填写字符串形式的用户ID"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_plugin_components(self) -> List[Tuple[CommandInfo, Type[BaseCommand]]]:
|
def get_plugin_components(self) -> List[Tuple[CommandInfo, Type[BaseCommand]]]:
|
||||||
components = []
|
components = []
|
||||||
if self.get_config("plugin.enable", True):
|
if self.get_config("plugin.enabled", True):
|
||||||
components.append((ManagementCommand.get_command_info(), ManagementCommand))
|
components.append((ManagementCommand.get_command_info(), ManagementCommand))
|
||||||
return components
|
return components
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[inner]
|
[inner]
|
||||||
version = "4.4.9"
|
version = "4.5.0"
|
||||||
|
|
||||||
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
|
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
|
||||||
#如果你想要修改配置文件,请在修改后将version的值进行变更
|
#如果你想要修改配置文件,请在修改后将version的值进行变更
|
||||||
@@ -58,14 +58,12 @@ focus_value = 1
|
|||||||
willing_amplifier = 1 # 麦麦回复意愿
|
willing_amplifier = 1 # 麦麦回复意愿
|
||||||
|
|
||||||
max_context_size = 25 # 上下文长度
|
max_context_size = 25 # 上下文长度
|
||||||
thinking_timeout = 20 # 麦麦一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢)
|
thinking_timeout = 40 # 麦麦一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢)
|
||||||
replyer_random_probability = 0.5 # 首要replyer模型被选择的概率
|
replyer_random_probability = 0.5 # 首要replyer模型被选择的概率
|
||||||
|
|
||||||
mentioned_bot_inevitable_reply = true # 提及 bot 大概率回复
|
mentioned_bot_inevitable_reply = true # 提及 bot 大概率回复
|
||||||
at_bot_inevitable_reply = true # @bot 或 提及bot 大概率回复
|
at_bot_inevitable_reply = true # @bot 或 提及bot 大概率回复
|
||||||
|
|
||||||
use_s4u_prompt_mode = true # 是否使用 s4u 对话构建模式,该模式会更好的把握当前对话对象的对话内容,但是对群聊整理理解能力较差(测试功能!!可能有未知问题!!)
|
|
||||||
|
|
||||||
|
|
||||||
talk_frequency = 1 # 麦麦回复频率,越高,麦麦回复越频繁
|
talk_frequency = 1 # 麦麦回复频率,越高,麦麦回复越频繁
|
||||||
|
|
||||||
@@ -109,8 +107,7 @@ ban_msgs_regex = [
|
|||||||
willing_mode = "classical" # 回复意愿模式 —— 经典模式:classical,mxp模式:mxp,自定义模式:custom(需要你自己实现)
|
willing_mode = "classical" # 回复意愿模式 —— 经典模式:classical,mxp模式:mxp,自定义模式:custom(需要你自己实现)
|
||||||
|
|
||||||
[tool]
|
[tool]
|
||||||
enable_in_normal_chat = false # 是否在普通聊天中启用工具
|
enable_tool = false # 是否在普通聊天中启用工具
|
||||||
enable_in_focus_chat = true # 是否在专注聊天中启用工具
|
|
||||||
|
|
||||||
[emoji]
|
[emoji]
|
||||||
emoji_chance = 0.6 # 麦麦激活表情包动作的概率
|
emoji_chance = 0.6 # 麦麦激活表情包动作的概率
|
||||||
|
|||||||
Reference in New Issue
Block a user