From bffc372d95454090ae301f12a5235cb91a751dc2 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sat, 26 Jul 2025 14:48:51 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat=EF=BC=9As4uprompt=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E6=AD=A3=E5=BC=8F=E4=B8=8A=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/planner_actions/planner.py | 3 +- src/chat/replyer/default_generator.py | 171 ++++++++------------------ src/config/official_configs.py | 3 - template/bot_config_template.toml | 4 +- 4 files changed, 55 insertions(+), 126 deletions(-) diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index a679c4953..33ec56418 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的消息: diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index 2e207c609..584ad4539 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} 注意不要复读你说过的话 @@ -781,103 +756,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 +1013,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..d852d5a3c 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -83,9 +83,6 @@ class ChatConfig(ConfigBase): talk_frequency: float = 1 """回复频率阈值""" - use_s4u_prompt_mode: bool = False - """是否使用 s4u 对话构建模式,该模式会分开处理当前对话对象和其他所有对话的内容进行 prompt 构建""" - mentioned_bot_inevitable_reply: bool = False """提及 bot 必然回复""" diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml index 34b91b35e..08a28637c 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的值进行变更 @@ -64,8 +64,6 @@ 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 # 麦麦回复频率,越高,麦麦回复越频繁 From ecfa25bf5a8b79349de6c752abe3492b775453d8 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 26 Jul 2025 17:13:12 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E4=BA=86dependency=5Fman?= =?UTF-8?q?ager=EF=BC=8Cdependency=E6=96=87=E6=A1=A3=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changes.md | 2 + docs/plugins/dependency-management.md | 301 +------------------ src/plugin_system/core/__init__.py | 2 - src/plugin_system/core/dependency_manager.py | 190 ------------ src/plugin_system/core/plugin_manager.py | 101 +------ 5 files changed, 11 insertions(+), 585 deletions(-) delete mode 100644 src/plugin_system/core/dependency_manager.py 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/src/plugin_system/core/__init__.py b/src/plugin_system/core/__init__.py index 3193828bf..eb794a30b 100644 --- a/src/plugin_system/core/__init__.py +++ b/src/plugin_system/core/__init__.py @@ -6,14 +6,12 @@ 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 __all__ = [ "plugin_manager", "component_registry", - "dependency_manager", "events_manager", "global_announcement_manager", ] 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]: """ From 3155c1bf24660c3e6406c20be16a9ebfe567be8b Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 26 Jul 2025 17:18:59 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E5=B0=8F=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/image/quick-start/1750332444690.png | Bin 18326 -> 0 bytes docs/plugins/index.md | 6 ++++++ docs/plugins/quick-start.md | 6 ++++++ 3 files changed, 12 insertions(+) delete mode 100644 docs/plugins/image/quick-start/1750332444690.png diff --git a/docs/plugins/image/quick-start/1750332444690.png b/docs/plugins/image/quick-start/1750332444690.png deleted file mode 100644 index aefbbb3e0d5d5d81deba562ff85d30980aef6fb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18326 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 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` 之类的方式来导入你需要的部分。 + ### 📂 准备工作 确保你已经: From 7d2cef9a9cb1bb50c3a126ba04aa2210ab4606c1 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sat, 26 Jul 2025 17:29:41 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat=EF=BC=9A=E7=A7=BB=E9=99=A4reply?= =?UTF-8?q?=E5=8A=A8=E4=BD=9C=EF=BC=8C=E5=90=88=E5=B9=B6tool=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/chat_loop/heartFC_chat.py | 157 ++++++++++-------- src/chat/chat_loop/hfc_utils.py | 34 +++- src/chat/planner_actions/planner.py | 2 +- src/chat/replyer/default_generator.py | 63 +++---- src/config/official_configs.py | 9 +- src/plugin_system/apis/generator_api.py | 6 +- .../built_in/core_actions/_manifest.json | 5 - src/plugins/built_in/core_actions/no_reply.py | 1 - src/plugins/built_in/core_actions/plugin.py | 6 +- src/plugins/built_in/core_actions/reply.py | 150 ----------------- template/bot_config_template.toml | 5 +- 11 files changed, 153 insertions(+), 285 deletions(-) delete mode 100644 src/plugins/built_in/core_actions/reply.py diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index 2db7ca42a..ee190f86a 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() @@ -310,7 +286,7 @@ class HeartFChatting: # 如果normal,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的) 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) @@ -326,7 +302,7 @@ class HeartFChatting: 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} 决定进行回复") elif is_parallel: logger.info( @@ -335,45 +311,86 @@ class HeartFChatting: else: logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定执行{action_type}动作") - if action_type == "no_action": + action_message: Dict[str, Any] = message_data or target_message # type: ignore + if action_type == "no_action" or (self.loop_mode == ChatMode.FOCUS and action_type == "reply"): # 等待回复生成完毕 - 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 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: + logger.warning(f"{self.log_prefix} 回复生成超时>{global_config.chat.thinking_timeout}s,已跳过") + response_set = None - if response_set: - content = " ".join([item[1] for item in response_set if item[0] == "text"]) + 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: - 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 not response_set: + logger.warning(f"{self.log_prefix}模型未生成回复内容") + return False + elif action_type not in ["no_action"] and not is_parallel: + logger.info( + f"{self.log_prefix}{global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复" + ) + 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 = "" - - 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 else: - action_message: Dict[str, Any] = message_data or target_message # type: ignore - # 动作执行计时 with Timer("动作执行", cycle_timers): success, reply_text, command = await self._handle_action( @@ -392,11 +409,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 +423,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 +566,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 +574,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 +604,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 33ec56418..f21cc8d32 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -312,7 +312,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 584ad4539..0b411a8eb 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -151,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, @@ -160,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 @@ -282,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 "" @@ -381,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: @@ -395,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: @@ -577,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, @@ -602,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) @@ -612,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) @@ -630,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(), @@ -664,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"), ) @@ -700,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 = "" diff --git a/src/config/official_configs.py b/src/config/official_configs.py index d852d5a3c..2c9f847c4 100644 --- a/src/config/official_configs.py +++ b/src/config/official_configs.py @@ -77,7 +77,7 @@ class ChatConfig(ConfigBase): 选择普通模型的概率为 1 - reasoning_normal_model_probability """ - thinking_timeout: int = 30 + thinking_timeout: int = 40 """麦麦最长思考规划时间,超过这个时间的思考会放弃(往往是api反应太慢)""" talk_frequency: float = 1 @@ -299,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/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/template/bot_config_template.toml b/template/bot_config_template.toml index 08a28637c..39857d669 100644 --- a/template/bot_config_template.toml +++ b/template/bot_config_template.toml @@ -58,7 +58,7 @@ 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 大概率回复 @@ -107,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 # 麦麦激活表情包动作的概率 From 8c6dadc6f3dae678ee46cab96181330c6b2ae4fe Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Sat, 26 Jul 2025 17:43:18 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix=EF=BC=9A=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chat/chat_loop/heartFC_chat.py | 56 ++++++++++++++++++++--------- src/chat/planner_actions/planner.py | 5 +-- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index ee190f86a..31e5b94d1 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -284,6 +284,8 @@ 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, "chat.replyer.normal")) @@ -301,20 +303,32 @@ class HeartFChatting: action_data["loop_start_time"] = loop_start_time - if self.loop_mode == ChatMode.NORMAL: - if action_type == "reply": - 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}动作") + 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},不发表回复" + ) + action_message: Dict[str, Any] = message_data or target_message # type: ignore - if action_type == "no_action" or (self.loop_mode == ChatMode.FOCUS and action_type == "reply"): + if action_type == "reply" or is_parallel: # 等待回复生成完毕 - if action_type == "no_action": + 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) @@ -322,18 +336,10 @@ class HeartFChatting: logger.warning(f"{self.log_prefix} 回复生成超时>{global_config.chat.thinking_timeout}s,已跳过") 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: - logger.info( - f"{self.log_prefix}{global_config.bot.nickname} 原本想要回复:{content},但选择执行{action_type},不发表回复" - ) - return False else: logger.info(f"{self.log_prefix}{global_config.bot.nickname} 决定进行回复 (focus模式)") @@ -391,6 +397,24 @@ class HeartFChatting: return True else: + # 如果是并行执行且在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( diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index f21cc8d32..4e33bafc8 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -225,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, From 82f4b1b1eae1b5502bbc13a14a6ad31ef82471c4 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 26 Jul 2025 18:37:51 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E8=AE=A9=E6=8F=92=E4=BB=B6=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=85=8D=E7=BD=AE=E7=9C=9F=E6=AD=A3=E6=9C=89=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/built_in/plugin_management/plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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