diff --git a/changes.md b/changes.md index 7d4f2ae8f..b776991de 100644 --- a/changes.md +++ b/changes.md @@ -23,6 +23,8 @@ 6. 增加了插件和组件管理的API。 7. `BaseCommand`的`execute`方法现在返回一个三元组,包含是否执行成功、可选的回复消息和是否拦截消息。 - 这意味着你终于可以动态控制是否继续后续消息的处理了。 +8. 移除了dependency_manager,但是依然保留了`python_dependencies`属性,等待后续重构。 + - 一并移除了文档有关manager的内容。 # 插件系统修改 1. 现在所有的匹配模式不再是关键字了,而是枚举类。**(可能有遗漏)** diff --git a/docs/plugins/dependency-management.md b/docs/plugins/dependency-management.md index 9b9695846..4bb4ed000 100644 --- a/docs/plugins/dependency-management.md +++ b/docs/plugins/dependency-management.md @@ -1,93 +1,6 @@ # 📦 插件依赖管理系统 -> 🎯 **简介**: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): - 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") -``` +现在的Python依赖包管理依然存在问题,请保留你的`python_dependencies`属性,等待后续重构。 ## 📚 详细教程 @@ -97,11 +10,11 @@ plugin_manager.generate_plugin_requirements("plugin_requirements.txt") ```python PythonDependency( - package_name="requests", # 导入时的包名 - version=">=2.25.0", # 版本要求 - optional=False, # 是否为可选依赖 - description="HTTP请求库", # 依赖描述 - install_name="" # pip安装时的包名(可选) + package_name="PIL", # 导入时的包名 + version=">=11.2.0", # 版本要求 + optional=False, # 是否为可选依赖 + description="图像处理库", # 依赖描述 + install_name="pillow" # pip安装时的包名(可选) ) ``` @@ -110,10 +23,10 @@ PythonDependency( | 参数 | 类型 | 必需 | 说明 | |------|------|------|------| | `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` | | `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") # 排除特定版本 ``` -#### 特殊情况处理 - -**导入名与安装名不同的包:** - -```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']) - ``` diff --git a/docs/plugins/image/quick-start/1750332444690.png b/docs/plugins/image/quick-start/1750332444690.png deleted file mode 100644 index aefbbb3e0..000000000 Binary files a/docs/plugins/image/quick-start/1750332444690.png and /dev/null differ diff --git a/docs/plugins/index.md b/docs/plugins/index.md index b8e29e67d..af8fad852 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -72,3 +72,9 @@ Command vs Action 选择指南 2. 查看相关示例代码 3. 参考其他类似插件 4. 提交文档仓库issue + +## 一个方便的小设计 + +我们在`__init__.py`中定义了一个`__all__`变量,包含了所有需要导出的类和函数。 +这样在其他地方导入时,可以直接使用 `from src.plugin_system import *` 来导入所有插件相关的类和函数。 +或者你可以直接使用 `from src.plugin_system import BasePlugin, register_plugin, ComponentInfo` 之类的方式来导入你需要的部分。 \ No newline at end of file diff --git a/docs/plugins/quick-start.md b/docs/plugins/quick-start.md index dda37ab84..48eff603d 100644 --- a/docs/plugins/quick-start.md +++ b/docs/plugins/quick-start.md @@ -8,6 +8,12 @@ 以下代码都在我们的`plugins/hello_world_plugin/`目录下。 +### 一个方便的小设计 + +在开发中,我们在`__init__.py`中定义了一个`__all__`变量,包含了所有需要导出的类和函数。 +这样在其他地方导入时,可以直接使用 `from src.plugin_system import *` 来导入所有插件相关的类和函数。 +或者你可以直接使用 `from src.plugin_system import BasePlugin, register_plugin, ComponentInfo` 之类的方式来导入你需要的部分。 + ### 📂 准备工作 确保你已经: diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index 2db7ca42a..31e5b94d1 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -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.person_info import get_person_info_manager 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.mais4u.mai_think import mai_thinking_manager -from maim_message.message_base import GroupInfo 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 = { "loop_plan_info": { @@ -254,44 +255,19 @@ class HeartFChatting: person_name = await person_info_manager.get_value(person_id, "person_name") 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): # sourcery skip: hoist-statement-from-if, merge-comparisons, reintroduce-else if not message_data: message_data = {} action_type = "no_action" + reply_text = "" # 初始化reply_text变量,避免UnboundLocalError # 创建新的循环信息 cycle_timers, thinking_id = self.start_cycle() logger.info(f"{self.log_prefix} 开始第{self._cycle_counter}次思考[模式:{self.loop_mode}]") 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()): loop_start_time = time.time() @@ -308,9 +284,11 @@ class HeartFChatting: logger.error(f"{self.log_prefix} 动作修改失败: {e}") # 如果normal,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的) + gen_task = None + reply_to_str = "" if self.loop_mode == ChatMode.NORMAL: 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): plan_result, target_message = await self.action_planner.plan(mode=self.loop_mode) @@ -325,55 +303,118 @@ class HeartFChatting: action_data["loop_start_time"] = loop_start_time - if self.loop_mode == ChatMode.NORMAL: - if action_type == "no_action": - logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复") - elif is_parallel: + + if action_type == "reply": + logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复") + elif is_parallel: + logger.info( + f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复, 同时执行{action_type}动作" + ) + else: + 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}动作" + f"{self.log_prefix}{global_config.bot.nickname} 原本想要回复,但选择执行{action_type},不发表回复" ) else: - logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定执行{action_type}动作") - - if action_type == "no_action": - # 等待回复生成完毕 - gather_timeout = global_config.chat.thinking_timeout - try: - response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout) - except asyncio.TimeoutError: - response_set = None - - if response_set: - content = " ".join([item[1] for item in response_set if item[0] == "text"]) - - # 模型炸了,没有回复内容生成 - if not response_set: - logger.warning(f"{self.log_prefix}模型未生成回复内容") - return False - elif action_type not in ["no_action"] and not is_parallel: + 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},不发表回复" ) - return False - logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定的回复内容: {content}") - # 发送回复 (不再需要传入 chat) - reply_text = await self._send_response(response_set, reply_to_str, loop_start_time,message_data) - - - - - - if ENABLE_S4U: - await self.stop_typing() - await mai_thinking_manager.get_mai_think(self.stream_id).do_think_after_response(reply_text) + 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 + try: + response_set = await asyncio.wait_for(gen_task, timeout=gather_timeout) + except asyncio.TimeoutError: + logger.warning(f"{self.log_prefix} 回复生成超时>{global_config.chat.thinking_timeout}s,已跳过") + response_set = None + # 模型炸了或超时,没有回复内容生成 + if not response_set: + logger.warning(f"{self.log_prefix}模型未生成回复内容") + return False + else: + logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复 (focus模式)") + + # 构建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 + + # 发送回复 + with Timer("回复发送", cycle_timers): + reply_text = await self._send_response(response_set, reply_to_str, loop_start_time, action_message) + + # 存储reply action信息 (focus模式) + 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 = "" + return True 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): success, reply_text, command = await self._handle_action( @@ -392,11 +433,11 @@ class HeartFChatting: }, } - if loop_info["loop_action_info"]["command"] == "stop_focus_chat": - logger.info(f"{self.log_prefix} 麦麦决定停止专注聊天") - return False - # 停止该聊天模式的循环 + if ENABLE_S4U: + 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.print_cycle_info(cycle_timers) @@ -406,13 +447,11 @@ class HeartFChatting: # 管理no_reply计数器:当执行了非no_reply动作时,重置计数器 if action_type != "no_reply" and action_type != "no_action": # 导入NoReplyAction并重置计数器 - from src.plugins.built_in.core_actions.no_reply import NoReplyAction NoReplyAction.reset_consecutive_count() logger.info(f"{self.log_prefix} 执行了{action_type}动作,重置no_reply计数器") return True elif action_type == "no_action": - # 当执行回复动作时,也重置no_reply计数器 - from src.plugins.built_in.core_actions.no_reply import NoReplyAction + # 当执行回复动作时,也重置no_reply计数器s NoReplyAction.reset_consecutive_count() logger.info(f"{self.log_prefix} 执行了回复动作,重置no_reply计数器") @@ -551,7 +590,7 @@ class HeartFChatting: return False 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]: """生成普通回复""" try: @@ -559,8 +598,8 @@ class HeartFChatting: chat_stream=self.chat_stream, reply_to=reply_to, available_actions=available_actions, - enable_tool=global_config.tool.enable_in_normal_chat, - request_type="chat.replyer.normal", + enable_tool=global_config.tool.enable_tool, + request_type=request_type, ) if not success or not reply_set: @@ -589,7 +628,7 @@ class HeartFChatting: f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,使用引用回复" ) else: - logger.debug( + logger.info( f"{self.log_prefix} 从思考到回复,共有{new_message_count}条新消息,不使用引用回复" ) diff --git a/src/chat/chat_loop/hfc_utils.py b/src/chat/chat_loop/hfc_utils.py index a24656665..973c4f948 100644 --- a/src/chat/chat_loop/hfc_utils.py +++ b/src/chat/chat_loop/hfc_utils.py @@ -1,10 +1,13 @@ import time - from typing import Optional, Dict, Any from src.config.config import global_config -from src.common.message_repository import count_messages 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__) @@ -106,3 +109,30 @@ def get_recent_message_stats(minutes: float = 30, chat_id: Optional[str] = None) bot_reply_count = count_messages(bot_filter) 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 + ) \ No newline at end of file diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index a679c4953..4e33bafc8 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -33,10 +33,11 @@ def init_prompt(): {time_block} {identity_block} 你现在需要根据聊天内容,选择的合适的action来参与聊天。 -{chat_context_description},以下是具体的聊天内容: +{chat_context_description},以下是具体的聊天内容 {chat_content_block} + {moderation_prompt} 现在请你根据{by_what}选择合适的action和触发action的消息: @@ -224,8 +225,9 @@ class ActionPlanner: reasoning = f"Planner 内部处理错误: {outer_e}" is_parallel = False - if action in current_available_actions: - is_parallel = current_available_actions[action].parallel_action + if mode == ChatMode.NORMAL: + if action in current_available_actions: + is_parallel = current_available_actions[action].parallel_action action_result = { "action_type": action, @@ -311,7 +313,7 @@ class ActionPlanner: by_what = "聊天内容和用户的最新消息" target_prompt = "" no_action_block = """重要说明: -- 'no_action' 表示只进行普通聊天回复,不执行任何额外动作 +- 'reply' 表示只进行普通聊天回复,不执行任何额外动作 - 其他action表示在普通回复的基础上,执行相应的额外动作""" chat_context_description = "你现在正在一个群聊中" diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 24ee95e35..487797642 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -40,33 +40,7 @@ def init_prompt(): Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1") Prompt("在群里聊天", "chat_target_group2") 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( """ {expression_habits_block} @@ -114,7 +88,8 @@ def init_prompt(): {core_dialogue_prompt} {reply_target_block} -对方最新发送的内容:{message_txt} + + 你现在的心情是:{mood_state} {config_expression_style} 注意不要复读你说过的话 @@ -176,7 +151,6 @@ class DefaultReplyer: async def generate_reply_with_context( self, - reply_data: Optional[Dict[str, Any]] = None, reply_to: str = "", extra_info: str = "", available_actions: Optional[Dict[str, ActionInfo]] = None, @@ -185,29 +159,24 @@ class DefaultReplyer: ) -> Tuple[bool, Optional[str], Optional[str]]: """ 回复器 (Replier): 核心逻辑,负责生成回复文本。 - (已整合原 HeartFCGenerator 的功能) """ prompt = None if available_actions is None: available_actions = {} 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 with Timer("构建Prompt", {}): # 内部计时器,可选保留 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, enable_timeout=enable_timeout, enable_tool=enable_tool, ) + + if not prompt: + logger.warning("构建prompt失败,跳过回复生成") + return False, None, None # 4. 调用 LLM 生成回复 content = None @@ -307,14 +276,13 @@ class DefaultReplyer: traceback.print_exc() 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: return "" relationship_fetcher = relationship_fetcher_manager.get_fetcher(self.chat_stream.stream_id) - if not reply_data: + if not reply_to: return "" - reply_to = reply_data.get("reply_to", "") sender, text = self._parse_reply_target(reply_to) if not sender or not text: return "" @@ -406,7 +374,7 @@ class DefaultReplyer: 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: @@ -420,10 +388,9 @@ class DefaultReplyer: if not enable_tool: return "" - if not reply_data: + if not reply_to: return "" - reply_to = reply_data.get("reply_to", "") sender, text = self._parse_reply_target(reply_to) if not text: @@ -602,7 +569,8 @@ class DefaultReplyer: async def build_prompt_reply_context( self, - reply_data: Dict[str, Any], + reply_to: str, + extra_info: str = "", available_actions: Optional[Dict[str, ActionInfo]] = None, enable_timeout: bool = False, enable_tool: bool = True, @@ -627,8 +595,6 @@ class DefaultReplyer: chat_id = chat_stream.stream_id person_info_manager = get_person_info_manager() 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: chat_mood = mood_manager.get_mood_by_chat_id(chat_id) @@ -637,6 +603,13 @@ class DefaultReplyer: mood_prompt = "" 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) @@ -655,21 +628,6 @@ class DefaultReplyer: 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( chat_id=chat_id, timestamp=time.time(), @@ -689,10 +647,10 @@ class DefaultReplyer: self._time_and_run_task( 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_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"), ) @@ -725,8 +683,8 @@ class DefaultReplyer: keywords_reaction_prompt = await self.build_keywords_reaction_prompt(target) - if extra_info_block: - extra_info_block = f"以下是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策\n{extra_info_block}\n以上是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策" + if extra_info: + extra_info_block = f"以下是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策\n{extra_info}\n以上是你在回复时需要参考的信息,现在请你阅读以下内容,进行决策" else: extra_info_block = "" @@ -781,103 +739,63 @@ class DefaultReplyer: # 根据sender通过person_info_manager反向查找person_id,再获取user_id 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 对话构建模式:分离当前对话对象和其他对话 - try: - user_id_value = await person_info_manager.get_value(person_id, "user_id") - if user_id_value: - target_user_id = str(user_id_value) - except Exception as e: - logger.warning(f"无法从person_id {person_id} 获取user_id: {e}") - target_user_id = "" + # 使用 s4u 对话构建模式:分离当前对话对象和其他对话 + try: + user_id_value = await person_info_manager.get_value(person_id, "user_id") + if user_id_value: + target_user_id = str(user_id_value) + except Exception as e: + logger.warning(f"无法从person_id {person_id} 获取user_id: {e}") + target_user_id = "" - # 构建分离的对话 prompt - core_dialogue_prompt, background_dialogue_prompt = self.build_s4u_chat_history_prompts( - message_list_before_now_long, target_user_id - ) + # 构建分离的对话 prompt + core_dialogue_prompt, background_dialogue_prompt = self.build_s4u_chat_history_prompts( + message_list_before_now_long, target_user_id + ) - 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=f""" + 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=f""" {background_dialogue_prompt} -------------------------------- {time_block} 这是你和{sender}的对话,你们正在交流中: {core_dialogue_prompt}""", - ) + ) - # 使用 s4u 风格的模板 - template_name = "s4u_style_prompt" + # 使用 s4u 风格的模板 + template_name = "s4u_style_prompt" - return await global_prompt_manager.format_prompt( - template_name, - expression_habits_block=expression_habits_block, - tool_info_block=tool_info, - knowledge_prompt=prompt_info, - memory_block=memory_block, - relation_info_block=relation_info, - extra_info_block=extra_info_block, - identity=identity_block, - action_descriptions=action_descriptions, - sender_name=sender, - mood_state=mood_prompt, - background_dialogue_prompt=background_dialogue_prompt, - time_block=time_block, - core_dialogue_prompt=core_dialogue_prompt, - reply_target_block=reply_target_block, - message_txt=target, - config_expression_style=global_config.expression.expression_style, - keywords_reaction_prompt=keywords_reaction_prompt, - 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, - ) + return await global_prompt_manager.format_prompt( + template_name, + expression_habits_block=expression_habits_block, + tool_info_block=tool_info, + knowledge_prompt=prompt_info, + memory_block=memory_block, + relation_info_block=relation_info, + extra_info_block=extra_info_block, + identity=identity_block, + action_descriptions=action_descriptions, + sender_name=sender, + mood_state=mood_prompt, + background_dialogue_prompt=background_dialogue_prompt, + time_block=time_block, + core_dialogue_prompt=core_dialogue_prompt, + reply_target_block=reply_target_block, + message_txt=target, + config_expression_style=global_config.expression.expression_style, + keywords_reaction_prompt=keywords_reaction_prompt, + moderation_prompt=moderation_prompt_block, + ) async def build_prompt_rewrite_context( self, @@ -1078,9 +996,7 @@ async def get_prompt_info(message: str, threshold: float): logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}") # 格式化知识信息 - formatted_prompt_info = await global_prompt_manager.format_prompt( - "knowledge_prompt", prompt_info=related_info - ) + formatted_prompt_info = f"你有以下这些**知识**:\n{related_info}\n请你**记住上面的知识**,之后可能会用到。\n" return formatted_prompt_info else: logger.debug("从LPMM知识库获取知识失败,可能是从未导入过知识,返回空知识...") diff --git a/src/config/official_configs.py b/src/config/official_configs.py index 73310f769..2c9f847c4 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -77,15 +77,12 @@ class ChatConfig(ConfigBase): 选择普通模型的概率为 1 - reasoning_normal_model_probability """ - thinking_timeout: int = 30 + thinking_timeout: int = 40 """麦麦最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢)""" talk_frequency: float = 1 """回复频率阈值""" - use_s4u_prompt_mode: bool = False - """是否使用 s4u 对话构建模式,该模式会分开处理当前对话对象和其他所有对话的内容进行 prompt 构建""" - mentioned_bot_inevitable_reply: bool = False """提及 bot 必然回复""" @@ -302,11 +299,8 @@ class ExpressionConfig(ConfigBase): class ToolConfig(ConfigBase): """工具配置类""" - enable_in_normal_chat: bool = False - """是否在普通聊天中启用工具""" - - enable_in_focus_chat: bool = True - """是否在专注聊天中启用工具""" + enable_tool: bool = False + """是否在聊天中启用工具""" @dataclass class VoiceConfig(ConfigBase): diff --git a/src/plugin_system/apis/generator_api.py b/src/plugin_system/apis/generator_api.py index cbb1336ce..60ab7d5bb 100644 --- a/src/plugin_system/apis/generator_api.py +++ b/src/plugin_system/apis/generator_api.py @@ -107,10 +107,14 @@ async def generate_reply( return False, [], None 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( - reply_data=action_data or {}, reply_to=reply_to, extra_info=extra_info, available_actions=available_actions, diff --git a/src/plugin_system/core/__init__.py b/src/plugin_system/core/__init__.py index b40fa51fe..3eecad418 100644 --- a/src/plugin_system/core/__init__.py +++ b/src/plugin_system/core/__init__.py @@ -6,7 +6,6 @@ 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.dependency_manager import dependency_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.tool_use import tool_user @@ -14,7 +13,6 @@ from src.plugin_system.core.tool_use import tool_user __all__ = [ "plugin_manager", "component_registry", - "dependency_manager", "events_manager", "global_announcement_manager", "tool_user", diff --git a/src/plugin_system/core/dependency_manager.py b/src/plugin_system/core/dependency_manager.py deleted file mode 100644 index 266254e72..000000000 --- a/src/plugin_system/core/dependency_manager.py +++ /dev/null @@ -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() diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py index 8bb005a94..98bce4bdb 100644 --- a/src/plugin_system/core/plugin_manager.py +++ b/src/plugin_system/core/plugin_manager.py @@ -8,10 +8,9 @@ from pathlib import Path from src.common.logger import get_logger 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 .component_registry import component_registry -from .dependency_manager import dependency_manager logger = get_logger("plugin_manager") @@ -207,104 +206,6 @@ class PluginManager: """ 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]: """ diff --git a/src/plugins/built_in/core_actions/_manifest.json b/src/plugins/built_in/core_actions/_manifest.json index ba1b20d6b..d7446497c 100644 --- a/src/plugins/built_in/core_actions/_manifest.json +++ b/src/plugins/built_in/core_actions/_manifest.json @@ -24,11 +24,6 @@ "is_built_in": true, "plugin_type": "action_provider", "components": [ - { - "type": "action", - "name": "reply", - "description": "参与聊天回复,发送文本进行表达" - }, { "type": "action", "name": "no_reply", diff --git a/src/plugins/built_in/core_actions/no_reply.py b/src/plugins/built_in/core_actions/no_reply.py index eb584a23a..f23f4ac74 100644 --- a/src/plugins/built_in/core_actions/no_reply.py +++ b/src/plugins/built_in/core_actions/no_reply.py @@ -61,7 +61,6 @@ class NoReplyAction(BaseAction): async def execute(self) -> Tuple[bool, str]: """执行不回复动作""" - import asyncio try: reason = self.action_data.get("reason", "") diff --git a/src/plugins/built_in/core_actions/plugin.py b/src/plugins/built_in/core_actions/plugin.py index c34f5a871..5bf80af33 100644 --- a/src/plugins/built_in/core_actions/plugin.py +++ b/src/plugins/built_in/core_actions/plugin.py @@ -18,7 +18,6 @@ from src.common.logger import get_logger # 导入API模块 - 标准Python包方式 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.reply import ReplyAction logger = get_logger("core_actions") @@ -52,10 +51,9 @@ class CoreActionsPlugin(BasePlugin): config_schema: dict = { "plugin": { "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": { - "enable_reply": ConfigField(type=bool, default=True, description="是否启用回复动作"), "enable_no_reply": ConfigField(type=bool, default=True, description="是否启用不回复动作"), "enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"), }, @@ -74,8 +72,6 @@ class CoreActionsPlugin(BasePlugin): # --- 根据配置注册组件 --- 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): components.append((NoReplyAction.get_action_info(), NoReplyAction)) if self.get_config("components.enable_emoji", True): diff --git a/src/plugins/built_in/core_actions/reply.py b/src/plugins/built_in/core_actions/reply.py deleted file mode 100644 index 887879066..000000000 --- a/src/plugins/built_in/core_actions/reply.py +++ /dev/null @@ -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)}" diff --git a/src/plugins/built_in/plugin_management/plugin.py b/src/plugins/built_in/plugin_management/plugin.py index f150d8017..de846dd5c 100644 --- a/src/plugins/built_in/plugin_management/plugin.py +++ b/src/plugins/built_in/plugin_management/plugin.py @@ -428,14 +428,14 @@ class PluginManagementPlugin(BasePlugin): config_file_name: str = "config.toml" config_schema: dict = { "plugin": { - "enable": ConfigField(bool, default=False, description="是否启用插件"), - "config_version": ConfigField(type=str, default="1.0.0", description="配置文件版本"), - "permission": ConfigField(list, default=[], description="有权限使用插件管理命令的用户列表"), + "enabled": ConfigField(bool, default=False, description="是否启用插件"), + "config_version": ConfigField(type=str, default="1.1.0", description="配置文件版本"), + "permission": ConfigField(list, default=[], description="有权限使用插件管理命令的用户列表,请填写字符串形式的用户ID"), }, } def get_plugin_components(self) -> List[Tuple[CommandInfo, Type[BaseCommand]]]: components = [] - if self.get_config("plugin.enable", True): + if self.get_config("plugin.enabled", True): components.append((ManagementCommand.get_command_info(), ManagementCommand)) return components diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 34b91b35e..39857d669 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -1,5 +1,5 @@ [inner] -version = "4.4.9" +version = "4.5.0" #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #如果你想要修改配置文件,请在修改后将version的值进行变更 @@ -58,14 +58,12 @@ focus_value = 1 willing_amplifier = 1 # 麦麦回复意愿 max_context_size = 25 # 上下文长度 -thinking_timeout = 20 # 麦麦一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢) +thinking_timeout = 40 # 麦麦一次回复最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢) replyer_random_probability = 0.5 # 首要replyer模型被选择的概率 mentioned_bot_inevitable_reply = true # 提及 bot 大概率回复 at_bot_inevitable_reply = true # @bot 或 提及bot 大概率回复 -use_s4u_prompt_mode = true # 是否使用 s4u 对话构建模式,该模式会更好的把握当前对话对象的对话内容,但是对群聊整理理解能力较差(测试功能!!可能有未知问题!!) - talk_frequency = 1 # 麦麦回复频率,越高,麦麦回复越频繁 @@ -109,8 +107,7 @@ ban_msgs_regex = [ willing_mode = "classical" # 回复意愿模式 —— 经典模式:classical,mxp模式:mxp,自定义模式:custom(需要你自己实现) [tool] -enable_in_normal_chat = false # 是否在普通聊天中启用工具 -enable_in_focus_chat = true # 是否在专注聊天中启用工具 +enable_tool = false # 是否在普通聊天中启用工具 [emoji] emoji_chance = 0.6 # 麦麦激活表情包动作的概率