From 9f49a453da665856724f27d8c2b949dc469f5d78 Mon Sep 17 00:00:00 2001 From: Windpicker-owo <3431391539@qq.com> Date: Mon, 1 Sep 2025 21:12:55 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=EF=BC=9A3804124=EF=BC=8C9e9e?= =?UTF-8?q?796=20=EF=BC=88feat=EF=BC=9A=E5=B0=86no=5Freply=E5=86=85?= =?UTF-8?q?=E7=BD=AE=E3=80=81fix=EF=BC=9A=E4=BC=98=E5=8C=96reply=EF=BC=8C?= =?UTF-8?q?=E5=A1=AB=E8=A1=A5=E7=BC=BA=E5=A4=B1=E5=80=BC=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FIXES_SUMMARY.md | 122 ------ bot.py | 2 - scripts/mongodb_to_sqlite.py | 2 +- src/chat/chat_loop/cycle_processor.py | 328 ++++++++++++--- src/chat/memory_system/action_diagnostics.py | 380 ------------------ src/chat/planner_actions/action_modifier.py | 5 +- src/chat/planner_actions/planner.py | 2 +- src/chat/replyer/default_generator.py | 62 +-- src/chat/utils/chat_message_builder.py | 4 + src/chat/utils/smart_prompt.py | 58 +-- .../built_in/core_actions/_manifest.json | 29 +- src/plugins/built_in/core_actions/no_reply.py | 60 --- src/plugins/built_in/core_actions/plugin.py | 6 +- 13 files changed, 364 insertions(+), 696 deletions(-) delete mode 100644 FIXES_SUMMARY.md delete mode 100644 src/chat/memory_system/action_diagnostics.py delete mode 100644 src/plugins/built_in/core_actions/no_reply.py diff --git a/FIXES_SUMMARY.md b/FIXES_SUMMARY.md deleted file mode 100644 index 584d1cd72..000000000 --- a/FIXES_SUMMARY.md +++ /dev/null @@ -1,122 +0,0 @@ -# MoFox_Bot 修复总结 - -## 修复的问题 - -### 1. Action组件可用性问题 -**问题描述**: 用户反馈"no_reply动作还是不可用",并且可用动作列表中缺少 `reply` 和 `no_reply` 动作。 - -**根本原因**: -- `reply` 动作没有在 `core_actions` 插件中注册 -- `_manifest.json` 文件缺少 `reply` 动作的声明 -- `config.toml` 配置文件没有 `enable_reply` 选项 - -**修复内容**: -1. **plugin.py**: 添加了 `ReplyAction` 的导入和注册 - ```python - from src.plugins.built_in.core_actions.reply import ReplyAction - # 在配置schema中添加 - "enable_reply": ConfigField(type=bool, default=True, description="是否启用基本回复动作") - # 在组件注册中添加 - if self.get_config("components.enable_reply", True): - components.append((ReplyAction.get_action_info(), ReplyAction)) - ``` - -2. **_manifest.json**: 添加了 `reply` 动作的组件声明 - ```json - { - "type": "action", - "name": "reply", - "description": "执行基本回复动作" - } - ``` - -3. **config.toml**: 添加了完整的组件配置 - ```toml - enable_no_reply = true - enable_reply = true - enable_emoji = true - enable_anti_injector_manager = true - ``` - -### 2. 思考循环触发机制问题 -**问题描述**: -- 用户反馈"思考间隔明显太短了,才1秒左右,应该等到有新的消息才进行下一个思考循环" -- 系统使用固定0.1秒间隔无论是否有新消息都进行思考循环,造成资源浪费 - -**根本原因**: -- 主聊天循环使用固定的短间隔轮询 -- 不区分是否有新消息,即使没有新消息也会进行思考循环 -- 违反了"消息驱动"的设计理念 - -**修复内容**: -1. **消息驱动机制**: 修改为只有在有新消息时才触发思考循环 - ```python - # 只有在有新消息时才进行思考循环处理 - if has_new_messages: - # 根据聊天模式处理新消息 - if self.context.loop_mode == ChatMode.FOCUS: - for message in recent_messages: - await self.cycle_processor.observe(message) - ``` - -2. **优化等待策略**: - - 有新消息时: 0.1秒快速检查后续消息 - - 无新消息时: 1.0秒轻量级状态检查 - - 完全避免无意义的思考循环 - -3. **保持主动思考独立性**: 主动思考系统有自己的时间间隔,不受此修改影响 - -## 修复验证 - -### 已验证的修复项目 -✅ **reply 动作注册**: manifest、config和plugin.py中都已正确配置 -✅ **no_reply 动作注册**: 配置完整且可用 -✅ **循环间隔优化**: 动态间隔逻辑已实现 -✅ **配置文件完整性**: 所有必需的配置项都已添加 - -### 预期效果 -1. **Action系统**: - - `no_reply` 和 `reply` 动作将出现在可用动作列表中 - - Action回退机制将正常工作 - - 不再出现"未找到Action组件"错误 - -2. **思考循环性能**: - - **消息驱动机制**: 只有新消息到达时才触发思考循环 - - **无消息时仅状态检查**: 避免无意义的思考处理 - - **CPU使用率大幅降低**: 消除连续的高频思考循环 - - **快速消息响应**: 有新消息时仍保持0.1秒响应速度 - - **主动思考独立**: 不影响主动思考系统的时间间隔机制 - -## 技术细节 - -### Action注册流程 -``` -plugin.py 导入 → _manifest.json 声明 → config.toml 启用 → 运行时注册 -``` - -### 消息驱动思考策略 -``` -消息状态 → 系统行为 -有新消息 → 0.1秒快速响应 + 思考循环处理 -无新消息 → 1.0秒状态检查 + 跳过思考循环 -主动思考 → 独立时间间隔(1500秒) + 独立触发机制 -``` - -## 部署建议 - -1. **重启服务**: 修改了核心循环逻辑,建议重启MaiBot服务 -2. **监控性能**: 观察CPU使用率是否有明显下降 -3. **测试Action**: 验证no_reply和reply动作是否在可用列表中出现 -4. **检查日志**: 确认不再出现Action组件错误 - -## 后续优化建议 - -1. **消息事件驱动**: 考虑使用事件驱动机制完全消除轮询 -2. **配置化间隔**: 将循环间隔参数添加到配置文件中 -3. **性能监控**: 添加循环性能指标收集 -4. **Action热重载**: 实现Action组件的热重载机制 - ---- -**修复日期**: 2025年1月17日 -**修复范围**: Action系统 + 聊天循环优化 -**预计效果**: 大幅减少CPU使用率,解决Action可用性问题 diff --git a/bot.py b/bot.py index dc256e011..9f90832a7 100644 --- a/bot.py +++ b/bot.py @@ -24,8 +24,6 @@ else: from src.common.logger import initialize_logging, get_logger, shutdown_logging # UI日志适配器 - - initialize_logging() from src.main import MainSystem # noqa diff --git a/scripts/mongodb_to_sqlite.py b/scripts/mongodb_to_sqlite.py index e97c16ad0..789c5860a 100644 --- a/scripts/mongodb_to_sqlite.py +++ b/scripts/mongodb_to_sqlite.py @@ -30,7 +30,7 @@ from rich.panel import Panel # from rich.text import Text from src.common.database.database import db -from src.common.database.database_model import ( +from src.common.database.sqlalchemy_models import ( ChatStreams, Emoji, Messages, diff --git a/src/chat/chat_loop/cycle_processor.py b/src/chat/chat_loop/cycle_processor.py index 85297bdea..ef474b402 100644 --- a/src/chat/chat_loop/cycle_processor.py +++ b/src/chat/chat_loop/cycle_processor.py @@ -1,13 +1,16 @@ import asyncio import time import traceback -from typing import Optional, Dict, Any +from typing import Optional, Dict, Any, Tuple +from src.chat.message_receive.chat_stream import get_chat_manager from src.chat.utils.timer_calculator import Timer from src.common.logger import get_logger from src.config.config import global_config from src.chat.planner_actions.planner import ActionPlanner from src.chat.planner_actions.action_modifier import ActionModifier +from src.person_info.person_info import get_person_info_manager +from src.plugin_system.apis import database_api from src.plugin_system.base.component_types import ChatMode from src.mais4u.constant_s4u import ENABLE_S4U from src.chat.chat_loop.hfc_utils import send_typing, stop_typing @@ -28,6 +31,8 @@ class CycleProcessor: response_handler: 响应处理器,负责生成和发送回复 cycle_tracker: 循环跟踪器,负责记录和管理每次思考循环的信息 """ + self.log_prefix = f"[{get_chat_manager().get_stream_name(self.stream_id) or self.stream_id}]" + self.context = context self.response_handler = response_handler self.cycle_tracker = cycle_tracker @@ -35,7 +40,54 @@ class CycleProcessor: self.action_modifier = ActionModifier( action_manager=self.context.action_manager, chat_id=self.context.stream_id ) + + async def _send_and_store_reply( + self, + response_set, + reply_to_str, + loop_start_time, + action_message, + cycle_timers: Dict[str, float], + thinking_id, + plan_result, + ) -> Tuple[Dict[str, Any], str, Dict[str, float]]: + with Timer("回复发送", cycle_timers): + reply_text = await self._send_response(response_set, reply_to_str, loop_start_time, action_message) + # 存储reply action信息 + 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: Dict[str, Any] = { + "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(), + }, + } + + return loop_info, reply_text, cycle_timers + async def observe(self, message_data: Optional[Dict[str, Any]] = None) -> bool: """ 观察和处理单次思考循环的核心方法 @@ -79,34 +131,24 @@ class CycleProcessor: at_bot_mentioned = (global_config.chat.mentioned_bot_inevitable_reply and is_mentioned_bot) or ( global_config.chat.at_bot_inevitable_reply and is_mentioned_bot ) - + + # 专注模式下提及bot必定回复 if self.context.loop_mode == ChatMode.FOCUS and at_bot_mentioned and "no_reply" in available_actions: available_actions = {k: v for k, v in available_actions.items() if k != "no_reply"} # 检查是否在normal模式下没有可用动作(除了reply相关动作) skip_planner = False - gen_task = None + if self.context.loop_mode == ChatMode.NORMAL: non_reply_actions = { k: v for k, v in available_actions.items() if k not in ["reply", "no_reply", "no_action"] } if not non_reply_actions: skip_planner = True - logger.info(f"{self.log_prefix} Normal模式下没有可用动作,直接回复") + logger.info(f"Normal模式下没有可用动作,直接回复") plan_result = self._get_direct_reply_plan(loop_start_time) target_message = message_data - # 如果normal模式且不跳过规划器,开始一个回复生成进程,先准备好回复(其实是和planer同时进行的) - if not skip_planner: - reply_to_str = await self._build_reply_to_str(message_data) - gen_task = asyncio.create_task( - self.response_handler.generate_response( - message_data=message_data, - available_actions=available_actions, - reply_to=reply_to_str, - request_type="chat.replyer.normal", - ) - ) # Focus模式 if not skip_planner: @@ -123,57 +165,237 @@ class CycleProcessor: with Timer("规划器", cycle_timers): plan_result, target_message = await self.action_planner.plan(mode=self.context.loop_mode) - action_result = plan_result.get("action_result", {}) if isinstance(plan_result, dict) else {} - if not isinstance(action_result, dict): - action_result = {} + action_result = plan_result.get("action_result", {}) + action_type = action_result.get("action_type", "error") action_data = action_result.get("action_data", {}) reasoning = action_result.get("reasoning", "未提供理由") is_parallel = action_result.get("is_parallel", True) + action_data["loop_start_time"] = loop_start_time + action_message = message_data or target_message - is_private_chat = self.context.chat_stream.group_info is None if self.context.chat_stream else False - if self.context.loop_mode == ChatMode.FOCUS and is_private_chat and action_type == "no_reply": - action_type = "reply" + # is_private_chat = self.context.chat_stream.group_info is None if self.context.chat_stream else False + + # 重构后的动作处理逻辑:先汇总所有动作,然后并行执行 + actions = [] - if action_type == "reply": - # 使用 action_planner 获取的 target_message,如果为空则使用原始 message_data - actual_message = target_message or message_data - await self._handle_reply_action( - actual_message, available_actions, gen_task, loop_start_time, cycle_timers, thinking_id, plan_result + # 1. 添加Planner取得的动作 + actions.append({ + "action_type": action_type, + "reasoning": reasoning, + "action_data": action_data, + "action_message": action_message, + "available_actions": available_actions # 添加这个字段 + }) + + # 2. 如果不是reply动作且需要并行执行,额外添加reply动作 + if action_type != "reply" and is_parallel: + actions.append({ + "action_type": "reply", + "action_message": action_message, + "available_actions": available_actions + }) + + async def execute_action(action_info): + """执行单个动作的通用函数""" + try: + if action_info["action_type"] == "no_reply": + # 直接处理no_reply逻辑,不再通过动作系统 + reason = action_info.get("reasoning", "选择不回复") + logger.info(f"{self.log_prefix} 选择不回复,原因: {reason}") + + # 存储no_reply信息到数据库 + await database_api.store_action_info( + chat_stream=self.context.chat_stream, + action_build_into_prompt=False, + action_prompt_display=reason, + action_done=True, + thinking_id=thinking_id, + action_data={"reason": reason}, + action_name="no_reply", + ) + + return { + "action_type": "no_reply", + "success": True, + "reply_text": "", + "command": "" + } + elif action_info["action_type"] != "reply": + # 执行普通动作 + with Timer("动作执行", cycle_timers): + success, reply_text, command = await self._handle_action( + action_info["action_type"], + action_info["reasoning"], + action_info["action_data"], + cycle_timers, + thinking_id, + action_info["action_message"] + ) + return { + "action_type": action_info["action_type"], + "success": success, + "reply_text": reply_text, + "command": command + } + else: + # 执行回复动作 + reply_to_str = await self._build_reply_to_str(action_info["action_message"]) + request_type = "chat.replyer" + + # 生成回复 + gather_timeout = global_config.chat.thinking_timeout + try: + response_set = await asyncio.wait_for( + self.response_handler._generate_response( + message_data=action_info["action_message"], + available_actions=action_info["available_actions"], + reply_to=reply_to_str, + request_type=request_type, + ), + timeout=gather_timeout + ) + except asyncio.TimeoutError: + logger.warning( + f"{self.log_prefix} 并行执行:回复生成超时>{global_config.chat.thinking_timeout}s,已跳过" + ) + return { + "action_type": "reply", + "success": False, + "reply_text": "", + "loop_info": None + } + except asyncio.CancelledError: + logger.debug(f"{self.log_prefix} 并行执行:回复生成任务已被取消") + return { + "action_type": "reply", + "success": False, + "reply_text": "", + "loop_info": None + } + + if not response_set: + logger.warning(f"{self.log_prefix} 模型超时或生成回复内容为空") + return { + "action_type": "reply", + "success": False, + "reply_text": "", + "loop_info": None + } + + # TODO: Where is my fucking _send_and_store_reply? + loop_info, reply_text, cycle_timers_reply = await self._send_and_store_reply( + response_set, + reply_to_str, + loop_start_time, + action_info["action_message"], + cycle_timers, + thinking_id, + plan_result, + ) + return { + "action_type": "reply", + "success": True, + "reply_text": reply_text, + "loop_info": loop_info + } + except Exception as e: + logger.error(f"{self.log_prefix} 执行动作时出错: {e}") + return { + "action_type": action_info["action_type"], + "success": False, + "reply_text": "", + "loop_info": None, + "error": str(e) + } + + # 创建所有动作的后台任务 + action_tasks = [asyncio.create_task(execute_action(action)) for action in actions] + + # 并行执行所有任务 + results = await asyncio.gather(*action_tasks, return_exceptions=True) + + # 处理执行结果 + reply_loop_info = None + reply_text_from_reply = "" + action_success = False + action_reply_text = "" + action_command = "" + + for i, result in enumerate(results): + if isinstance(result, BaseException): + logger.error(f"{self.log_prefix} 动作执行异常: {result}") + continue + + action_info = actions[i] + if result["action_type"] != "reply": + action_success = result["success"] + action_reply_text = result["reply_text"] + action_command = result.get("command", "") + elif result["action_type"] == "reply": + if result["success"]: + reply_loop_info = result["loop_info"] + reply_text_from_reply = result["reply_text"] + else: + logger.warning(f"{self.log_prefix} 回复动作执行失败") + + # 构建最终的循环信息 + if reply_loop_info: + # 如果有回复信息,使用回复的loop_info作为基础 + loop_info = reply_loop_info + # 更新动作执行信息 + loop_info["loop_action_info"].update( + { + "action_taken": action_success, + "command": action_command, + "taken_time": time.time(), + } ) + reply_text = reply_text_from_reply else: - await self._handle_other_actions( - action_type, - reasoning, - action_data, - is_parallel, - gen_task, - target_message or message_data, - cycle_timers, - thinking_id, - plan_result, - loop_start_time, - ) - - self.context.last_action = action_type - - # 处理no_reply相关的逻辑 - if action_type != "no_reply": - self.context.no_reply_consecutive = 0 - if hasattr(self.context, 'chat_instance') and self.context.chat_instance: - self.context.chat_instance.recent_interest_records.clear() - logger.info(f"{self.context.log_prefix} 执行了{action_type}动作,重置no_reply计数器和兴趣度记录") - - if action_type == "no_reply": - self.context.no_reply_consecutive += 1 - # 调用HeartFChatting中的_determine_form_type方法 - if hasattr(self.context, 'chat_instance') and self.context.chat_instance: - self.context.chat_instance._determine_form_type() + # 没有回复信息,构建纯动作的loop_info + loop_info = { + "loop_plan_info": { + "action_result": plan_result.get("action_result", {}), + }, + "loop_action_info": { + "action_taken": action_success, + "reply_text": action_reply_text, + "command": action_command, + "taken_time": time.time(), + }, + } + reply_text = action_reply_text + + self.last_action = action_type if ENABLE_S4U: await stop_typing() + self.context.chat_instance.cycle_tracker.end_cycle(loop_info, cycle_timers) + self.context.chat_instance.cycle_tracker.print_cycle_info(cycle_timers) + + if self.context.loop_mode == ChatMode.NORMAL: + await self.context.chat_instance.willing_manager.after_generate_reply_handle(message_data.get("message_id", "")) + + # 管理no_reply计数器:当执行了非no_reply动作时,重置计数器 + if action_type != "no_reply" and action_type != "no_action": + # no_reply逻辑已集成到heartFC_chat.py中,直接重置计数器 + self.context.chat_instance.recent_interest_records.clear() + self.context.no_reply_consecutive = 0 + logger.info(f"{self.log_prefix} 执行了{action_type}动作,重置no_reply计数器") + return True + elif action_type == "no_action": + # 当执行回复动作时,也重置no_reply计数 + self.context.chat_instance.recent_interest_records.clear() + self.context.no_reply_consecutive = 0 + logger.info(f"{self.log_prefix} 执行了回复动作,重置no_reply计数器") + + if action_type == "no_reply": + self.context.no_reply_consecutive += 1 + self.context.chat_instance._determine_form_type() + # 在一轮动作执行完毕后,增加睡眠压力 if self.context.energy_manager and global_config.sleep_system.enable_insomnia_system: if action_type not in ["no_reply", "no_action"]: diff --git a/src/chat/memory_system/action_diagnostics.py b/src/chat/memory_system/action_diagnostics.py deleted file mode 100644 index 2d9dfa6fc..000000000 --- a/src/chat/memory_system/action_diagnostics.py +++ /dev/null @@ -1,380 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Action组件诊断和修复脚本 -检查no_reply等核心Action是否正确注册,并尝试修复相关问题 -""" - -import sys -import os -from typing import Dict, Any - -# 添加项目路径 -sys.path.append(os.path.join(os.path.dirname(__file__), "../../../")) - -from src.common.logger import get_logger -from src.plugin_system.core.component_registry import component_registry -from src.plugin_system.core.plugin_manager import plugin_manager -from src.plugin_system.base.component_types import ComponentType - -logger = get_logger("action_diagnostics") - - -class ActionDiagnostics: - """Action组件诊断器""" - - def __init__(self): - self.required_actions = ["no_reply", "reply", "emoji", "at_user"] - - def check_plugin_loading(self) -> Dict[str, Any]: - """检查插件加载状态""" - logger.info("开始检查插件加载状态...") - - result = { - "plugins_loaded": False, - "total_plugins": 0, - "loaded_plugins": [], - "failed_plugins": [], - "core_actions_plugin": None, - } - - try: - # 加载所有插件 - plugin_manager.load_all_plugins() - - # 获取插件统计信息 - stats = plugin_manager.get_stats() - result["plugins_loaded"] = True - result["total_plugins"] = stats.get("total_plugins", 0) - - # 检查是否有core_actions插件 - for plugin_name in plugin_manager.loaded_plugins: - result["loaded_plugins"].append(plugin_name) - if "core_actions" in plugin_name.lower(): - result["core_actions_plugin"] = plugin_name - - logger.info(f"插件加载成功,总数: {result['total_plugins']}") - logger.info(f"已加载插件: {result['loaded_plugins']}") - - except Exception as e: - logger.error(f"插件加载失败: {e}") - result["error"] = str(e) - - return result - - def check_action_registry(self) -> Dict[str, Any]: - """检查Action注册状态""" - logger.info("开始检查Action组件注册状态...") - - result = {"registered_actions": [], "missing_actions": [], "default_actions": {}, "total_actions": 0} - - try: - # 获取所有注册的Action - all_components = component_registry.get_all_components(ComponentType.ACTION) - result["total_actions"] = len(all_components) - - for name, info in all_components.items(): - result["registered_actions"].append(name) - logger.debug(f"已注册Action: {name} (插件: {info.plugin_name})") - - # 检查必需的Action是否存在 - for required_action in self.required_actions: - if required_action not in all_components: - result["missing_actions"].append(required_action) - logger.warning(f"缺失必需Action: {required_action}") - else: - logger.info(f"找到必需Action: {required_action}") - - # 获取默认Action - default_actions = component_registry.get_default_actions() - result["default_actions"] = {name: info.plugin_name for name, info in default_actions.items()} - - logger.info(f"总注册Action数量: {result['total_actions']}") - logger.info(f"缺失Action: {result['missing_actions']}") - - except Exception as e: - logger.error(f"Action注册检查失败: {e}") - result["error"] = str(e) - - return result - - def check_specific_action(self, action_name: str) -> Dict[str, Any]: - """检查特定Action的详细信息""" - logger.info(f"检查Action详细信息: {action_name}") - - result = { - "exists": False, - "component_info": None, - "component_class": None, - "is_default": False, - "plugin_name": None, - } - - try: - # 检查组件信息 - component_info = component_registry.get_component_info(action_name, ComponentType.ACTION) - if component_info: - result["exists"] = True - result["component_info"] = { - "name": component_info.name, - "description": component_info.description, - "plugin_name": component_info.plugin_name, - "version": component_info.version, - } - result["plugin_name"] = component_info.plugin_name - logger.info(f"找到Action组件信息: {action_name}") - else: - logger.warning(f"未找到Action组件信息: {action_name}") - return result - - # 检查组件类 - component_class = component_registry.get_component_class(action_name, ComponentType.ACTION) - if component_class: - result["component_class"] = component_class.__name__ - logger.info(f"找到Action组件类: {component_class.__name__}") - else: - logger.warning(f"未找到Action组件类: {action_name}") - - # 检查是否为默认Action - default_actions = component_registry.get_default_actions() - result["is_default"] = action_name in default_actions - - logger.info(f"Action {action_name} 检查完成: 存在={result['exists']}, 默认={result['is_default']}") - - except Exception as e: - logger.error(f"检查Action {action_name} 失败: {e}") - result["error"] = str(e) - - return result - - def attempt_fix_missing_actions(self) -> Dict[str, Any]: - """尝试修复缺失的Action""" - logger.info("尝试修复缺失的Action组件...") - - result = {"fixed_actions": [], "still_missing": [], "errors": []} - - try: - # 重新加载插件 - plugin_manager.load_all_plugins() - - # 再次检查Action注册状态 - registry_check = self.check_action_registry() - - for required_action in self.required_actions: - if required_action in registry_check["missing_actions"]: - try: - # 尝试手动注册核心Action - if required_action == "no_reply": - self._register_no_reply_action() - result["fixed_actions"].append(required_action) - else: - result["still_missing"].append(required_action) - except Exception as e: - error_msg = f"修复Action {required_action} 失败: {e}" - logger.error(error_msg) - result["errors"].append(error_msg) - result["still_missing"].append(required_action) - - logger.info(f"Action修复完成: 已修复={result['fixed_actions']}, 仍缺失={result['still_missing']}") - - except Exception as e: - error_msg = f"Action修复过程失败: {e}" - logger.error(error_msg) - result["errors"].append(error_msg) - - return result - - def _register_no_reply_action(self): - """手动注册no_reply Action""" - try: - from src.plugins.built_in.core_actions.no_reply import NoReplyAction - from src.plugin_system.base.component_types import ActionInfo - - # 创建Action信息 - action_info = ActionInfo( - name="no_reply", description="暂时不回复消息", plugin_name="built_in.core_actions", version="1.0.0" - ) - - # 注册Action - success = component_registry._register_action_component(action_info, NoReplyAction) - if success: - logger.info("手动注册no_reply Action成功") - else: - raise Exception("注册失败") - - except Exception as e: - raise Exception(f"手动注册no_reply Action失败: {e}") from e - - def run_full_diagnosis(self) -> Dict[str, Any]: - """运行完整诊断""" - logger.info("🔧 开始Action组件完整诊断") - logger.info("=" * 60) - - diagnosis_result = { - "plugin_status": {}, - "registry_status": {}, - "action_details": {}, - "fix_attempts": {}, - "summary": {}, - } - - # 1. 检查插件加载 - logger.info("\n📦 步骤1: 检查插件加载状态") - diagnosis_result["plugin_status"] = self.check_plugin_loading() - - # 2. 检查Action注册 - logger.info("\n📋 步骤2: 检查Action注册状态") - diagnosis_result["registry_status"] = self.check_action_registry() - - # 3. 检查特定Action详细信息 - logger.info("\n🔍 步骤3: 检查特定Action详细信息") - diagnosis_result["action_details"] = {} - for action in self.required_actions: - diagnosis_result["action_details"][action] = self.check_specific_action(action) - - # 4. 尝试修复缺失的Action - if diagnosis_result["registry_status"].get("missing_actions"): - logger.info("\n🔧 步骤4: 尝试修复缺失的Action") - diagnosis_result["fix_attempts"] = self.attempt_fix_missing_actions() - - # 5. 生成诊断摘要 - logger.info("\n📊 步骤5: 生成诊断摘要") - diagnosis_result["summary"] = self._generate_summary(diagnosis_result) - - self._print_diagnosis_results(diagnosis_result) - - return diagnosis_result - - def _generate_summary(self, diagnosis_result: Dict[str, Any]) -> Dict[str, Any]: - """生成诊断摘要""" - summary = {"overall_status": "unknown", "critical_issues": [], "recommendations": []} - - try: - # 检查插件加载状态 - if not diagnosis_result["plugin_status"].get("plugins_loaded"): - summary["critical_issues"].append("插件加载失败") - summary["recommendations"].append("检查插件系统配置") - - # 检查必需Action - missing_actions = diagnosis_result["registry_status"].get("missing_actions", []) - if "no_reply" in missing_actions: - summary["critical_issues"].append("缺失no_reply Action") - summary["recommendations"].append("检查core_actions插件是否正确加载") - - # 检查修复结果 - if diagnosis_result.get("fix_attempts"): - still_missing = diagnosis_result["fix_attempts"].get("still_missing", []) - if still_missing: - summary["critical_issues"].append(f"修复后仍缺失Action: {still_missing}") - summary["recommendations"].append("需要手动修复插件注册问题") - - # 确定整体状态 - if not summary["critical_issues"]: - summary["overall_status"] = "healthy" - elif len(summary["critical_issues"]) <= 2: - summary["overall_status"] = "warning" - else: - summary["overall_status"] = "critical" - - except Exception as e: - summary["critical_issues"].append(f"摘要生成失败: {e}") - summary["overall_status"] = "error" - - return summary - - def _print_diagnosis_results(self, diagnosis_result: Dict[str, Any]): - """打印诊断结果""" - logger.info("\n" + "=" * 60) - logger.info("📈 诊断结果摘要") - logger.info("=" * 60) - - summary = diagnosis_result.get("summary", {}) - overall_status = summary.get("overall_status", "unknown") - - # 状态指示器 - status_indicators = { - "healthy": "✅ 系统健康", - "warning": "⚠️ 存在警告", - "critical": "❌ 存在严重问题", - "error": "💥 诊断出错", - "unknown": "❓ 状态未知", - } - - logger.info(f"🎯 整体状态: {status_indicators.get(overall_status, overall_status)}") - - # 关键问题 - critical_issues = summary.get("critical_issues", []) - if critical_issues: - logger.info("\n🚨 关键问题:") - for issue in critical_issues: - logger.info(f" • {issue}") - - # 建议 - recommendations = summary.get("recommendations", []) - if recommendations: - logger.info("\n💡 建议:") - for rec in recommendations: - logger.info(f" • {rec}") - - # 详细状态 - plugin_status = diagnosis_result.get("plugin_status", {}) - if plugin_status.get("plugins_loaded"): - logger.info(f"\n📦 插件状态: 已加载 {plugin_status.get('total_plugins', 0)} 个插件") - else: - logger.info("\n📦 插件状态: ❌ 插件加载失败") - - registry_status = diagnosis_result.get("registry_status", {}) - total_actions = registry_status.get("total_actions", 0) - missing_actions = registry_status.get("missing_actions", []) - logger.info(f"📋 Action状态: 已注册 {total_actions} 个,缺失 {len(missing_actions)} 个") - - if missing_actions: - logger.info(f" 缺失的Action: {missing_actions}") - - logger.info("\n" + "=" * 60) - - -def main(): - """主函数""" - diagnostics = ActionDiagnostics() - - try: - result = diagnostics.run_full_diagnosis() - - # 保存诊断结果 - import orjson - - with open("action_diagnosis_results.json", "w", encoding="utf-8") as f: - f.write(orjson.dumps(result, option=orjson.OPT_INDENT_2).decode("utf-8")) - - logger.info("📄 诊断结果已保存到: action_diagnosis_results.json") - - # 根据诊断结果返回适当的退出代码 - summary = result.get("summary", {}) - overall_status = summary.get("overall_status", "unknown") - - if overall_status == "healthy": - return 0 - elif overall_status == "warning": - return 1 - else: - return 2 - - except KeyboardInterrupt: - logger.info("❌ 诊断被用户中断") - return 3 - except Exception as e: - logger.error(f"❌ 诊断执行失败: {e}") - import traceback - - traceback.print_exc() - return 4 - - -if __name__ == "__main__": - import logging - - logging.basicConfig(level=logging.INFO) - - exit_code = main() - sys.exit(exit_code) diff --git a/src/chat/planner_actions/action_modifier.py b/src/chat/planner_actions/action_modifier.py index a0434ed18..b68e639f5 100644 --- a/src/chat/planner_actions/action_modifier.py +++ b/src/chat/planner_actions/action_modifier.py @@ -159,8 +159,11 @@ class ActionModifier: if all_removals: removals_summary = " | ".join([f"{name}({reason})" for name, reason in all_removals]) + available_actions = list(self.action_manager.get_using_actions().keys()) + available_actions_text = "、".join(available_actions) if available_actions else "无" + logger.info( - f"{self.log_prefix} 动作修改流程结束,最终可用动作: {list(self.action_manager.get_using_actions().keys())}||移除记录: {removals_summary}" + f"{self.log_prefix} 当前可用动作: {available_actions_text}||移除: {removals_summary}" ) def _check_action_associated_types(self, all_actions: Dict[str, ActionInfo], chat_context: ChatMessageContext): diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 7c704f2d3..ab62f6108 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -495,7 +495,7 @@ class ActionPlanner: by_what = "聊天内容" target_prompt = '\n "target_message_id":"触发action的消息id"' no_action_block = f"""重要说明: -- 'no_reply' 表示只进行不进行回复,等待合适的回复时机 +- 'no_reply' 表示只进行不进行回复,等待合适的回复时机(由系统直接处理) - 当你刚刚发送了消息,没有人回复时,选择no_reply - 当你一次发送了太多消息,为了避免打扰聊天节奏,选择no_reply diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index c7462aec4..3b87f741f 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -99,13 +99,12 @@ def init_prompt(): {identity} {action_descriptions} -你现在的主要任务是和 {sender_name} 聊天。同时,也有其他用户会参与你们的聊天,你可以参考他们的回复内容,但是你主要还是关注你和{sender_name}的聊天内容。 -{background_dialogue_prompt} -------------------------------- {time_block} -这是你和{sender_name}的对话,你们正在交流中: +你现在的主要任务是和 {sender_name} 聊天。同时,也有其他用户会参与聊天,你可以参考他们的回复内容,但是你现在想回复{sender_name}的发言。 +{background_dialogue_prompt} {core_dialogue_prompt} {reply_target_block} @@ -678,7 +677,7 @@ class DefaultReplyer: return name, result, duration def build_s4u_chat_history_prompts( - self, message_list_before_now: List[Dict[str, Any]], target_user_id: str + self, message_list_before_now: List[Dict[str, Any]], target_user_id: str, sender: str ) -> Tuple[str, str]: """ 构建 s4u 风格的分离对话 prompt @@ -691,7 +690,6 @@ class DefaultReplyer: Tuple[str, str]: (核心对话prompt, 背景对话prompt) """ core_dialogue_list = [] - background_dialogue_list = [] bot_id = str(global_config.bot.qq_account) # 过滤消息:分离bot和目标用户的对话 vs 其他用户的对话 @@ -703,41 +701,53 @@ class DefaultReplyer: if (msg_user_id == bot_id and reply_to_user_id == target_user_id) or msg_user_id == target_user_id: # bot 和目标用户的对话 core_dialogue_list.append(msg_dict) - else: - # 其他用户的对话 - background_dialogue_list.append(msg_dict) except Exception as e: logger.error(f"处理消息记录时出错: {msg_dict}, 错误: {e}") # 构建背景对话 prompt - background_dialogue_prompt = "" - if background_dialogue_list: - latest_25_msgs = background_dialogue_list[-int(global_config.chat.max_context_size * 0.5) :] - background_dialogue_prompt_str = build_readable_messages( + all_dialogue_prompt = "" + if message_list_before_now: + latest_25_msgs = message_list_before_now[-int(global_config.chat.max_context_size) :] + all_dialogue_prompt_str = build_readable_messages( latest_25_msgs, replace_bot_name=True, timestamp_mode="normal", truncate=True, ) - background_dialogue_prompt = f"这是其他用户的发言:\n{background_dialogue_prompt_str}" + all_dialogue_prompt = f"所有用户的发言:\n{all_dialogue_prompt_str}" # 构建核心对话 prompt core_dialogue_prompt = "" if core_dialogue_list: - core_dialogue_list = core_dialogue_list[-int(global_config.chat.max_context_size * 2) :] # 限制消息数量 + # 检查最新五条消息中是否包含bot自己说的消息 + latest_5_messages = core_dialogue_list[-5:] if len(core_dialogue_list) >= 5 else core_dialogue_list + has_bot_message = any(str(msg.get("user_id")) == bot_id for msg in latest_5_messages) + + # logger.info(f"最新五条消息:{latest_5_messages}") + # logger.info(f"最新五条消息中是否包含bot自己说的消息:{has_bot_message}") + + # 如果最新五条消息中不包含bot的消息,则返回空字符串 + if not has_bot_message: + core_dialogue_prompt = "" + else: + core_dialogue_list = core_dialogue_list[-int(global_config.chat.max_context_size * 2) :] # 限制消息数量 + + core_dialogue_prompt_str = build_readable_messages( + core_dialogue_list, + replace_bot_name=True, + merge_messages=False, + timestamp_mode="normal_no_YMD", + read_mark=0.0, + truncate=True, + show_actions=True, + ) + core_dialogue_prompt = f"""-------------------------------- +这是你和{sender}的对话,你们正在交流中: +{core_dialogue_prompt_str} +-------------------------------- +""" - core_dialogue_prompt_str = build_readable_messages( - core_dialogue_list, - replace_bot_name=True, - merge_messages=False, - timestamp_mode="normal", - read_mark=0.0, - truncate=True, - show_actions=True, - ) - core_dialogue_prompt = core_dialogue_prompt_str - - return core_dialogue_prompt, background_dialogue_prompt + return core_dialogue_prompt, all_dialogue_prompt def build_mai_think_context( self, diff --git a/src/chat/utils/chat_message_builder.py b/src/chat/utils/chat_message_builder.py index 6e478d0a4..5b25f1a46 100644 --- a/src/chat/utils/chat_message_builder.py +++ b/src/chat/utils/chat_message_builder.py @@ -1250,6 +1250,10 @@ async def get_person_id_list(messages: List[Dict[str, Any]]) -> List[str]: # 检查必要信息是否存在 且 不是机器人自己 if not all([platform, user_id]) or user_id == global_config.bot.qq_account: continue + + # 添加空值检查,防止 platform 为 None 时出错 + if platform is None: + platform = "unknown" if person_id := PersonInfoManager.get_person_id(platform, user_id): person_ids_set.add(person_id) diff --git a/src/chat/utils/smart_prompt.py b/src/chat/utils/smart_prompt.py index ff1b4e744..aba79f7ec 100644 --- a/src/chat/utils/smart_prompt.py +++ b/src/chat/utils/smart_prompt.py @@ -194,6 +194,7 @@ class SmartPromptBuilder: core_dialogue, background_dialogue = await self._build_s4u_chat_history_prompts( params.message_list_before_now_long, params.target_user_info.get("user_id") if params.target_user_info else "", + params.sender ) context_data["core_dialogue_prompt"] = core_dialogue @@ -208,11 +209,10 @@ class SmartPromptBuilder: {params.chat_talking_prompt_short}""" async def _build_s4u_chat_history_prompts( - self, message_list_before_now: List[Dict[str, Any]], target_user_id: str + self, message_list_before_now: List[Dict[str, Any]], target_user_id: str, sender: str ) -> Tuple[str, str]: """构建S4U风格的分离对话prompt - 完整实现""" core_dialogue_list = [] - background_dialogue_list = [] bot_id = str(global_config.bot.qq_account) # 过滤消息:分离bot和目标用户的对话 vs 其他用户的对话 @@ -224,41 +224,53 @@ class SmartPromptBuilder: if (msg_user_id == bot_id and reply_to_user_id == target_user_id) or msg_user_id == target_user_id: # bot 和目标用户的对话 core_dialogue_list.append(msg_dict) - else: - # 其他用户的对话 - background_dialogue_list.append(msg_dict) except Exception as e: logger.error(f"处理消息记录时出错: {msg_dict}, 错误: {e}") # 构建背景对话 prompt - background_dialogue_prompt = "" - if background_dialogue_list: - latest_25_msgs = background_dialogue_list[-int(global_config.chat.max_context_size * 0.5) :] - background_dialogue_prompt_str = build_readable_messages( + all_dialogue_prompt = "" + if message_list_before_now: + latest_25_msgs = message_list_before_now[-int(global_config.chat.max_context_size) :] + all_dialogue_prompt_str = build_readable_messages( latest_25_msgs, replace_bot_name=True, timestamp_mode="normal", truncate=True, ) - background_dialogue_prompt = f"这是其他用户的发言:\n{background_dialogue_prompt_str}" + all_dialogue_prompt = f"所有用户的发言:\n{all_dialogue_prompt_str}" # 构建核心对话 prompt core_dialogue_prompt = "" if core_dialogue_list: - core_dialogue_list = core_dialogue_list[-int(global_config.chat.max_context_size * 2) :] # 限制消息数量 + # 检查最新五条消息中是否包含bot自己说的消息 + latest_5_messages = core_dialogue_list[-5:] if len(core_dialogue_list) >= 5 else core_dialogue_list + has_bot_message = any(str(msg.get("user_id")) == bot_id for msg in latest_5_messages) + + # logger.info(f"最新五条消息:{latest_5_messages}") + # logger.info(f"最新五条消息中是否包含bot自己说的消息:{has_bot_message}") + + # 如果最新五条消息中不包含bot的消息,则返回空字符串 + if not has_bot_message: + core_dialogue_prompt = "" + else: + core_dialogue_list = core_dialogue_list[-int(global_config.chat.max_context_size * 2) :] # 限制消息数量 + + core_dialogue_prompt_str = build_readable_messages( + core_dialogue_list, + replace_bot_name=True, + merge_messages=False, + timestamp_mode="normal_no_YMD", + read_mark=0.0, + truncate=True, + show_actions=True, + ) + core_dialogue_prompt = f"""-------------------------------- +这是你和{sender}的对话,你们正在交流中: +{core_dialogue_prompt_str} +-------------------------------- +""" - core_dialogue_prompt_str = build_readable_messages( - core_dialogue_list, - replace_bot_name=True, - merge_messages=False, - timestamp_mode="normal", - read_mark=0.0, - truncate=True, - show_actions=True, - ) - core_dialogue_prompt = core_dialogue_prompt_str - - return core_dialogue_prompt, background_dialogue_prompt + return core_dialogue_prompt, all_dialogue_prompt async def _build_mai_think_context(self, params: SmartPromptParameters) -> Any: """构建mai_think上下文 - 完全继承DefaultReplyer功能""" diff --git a/src/plugins/built_in/core_actions/_manifest.json b/src/plugins/built_in/core_actions/_manifest.json index 66321c84b..48ba76378 100644 --- a/src/plugins/built_in/core_actions/_manifest.json +++ b/src/plugins/built_in/core_actions/_manifest.json @@ -1,21 +1,21 @@ { "manifest_version": 1, - "name": "核心动作插件 (Core Actions)", + "name": "Emoji插件 (Emoji Actions)", "version": "1.0.0", - "description": "系统核心动作插件,提供基础聊天交互功能,包括回复、不回复、表情包发送和聊天模式切换等核心功能。", + "description": "可以发送和管理Emoji", "author": { - "name": "MaiBot团队", + "name": "SengokuCola", "url": "https://github.com/MaiM-with-u" }, "license": "GPL-v3.0-or-later", "host_application": { - "min_version": "0.8.0" + "min_version": "0.10.0" }, "homepage_url": "https://github.com/MaiM-with-u/maibot", "repository_url": "https://github.com/MaiM-with-u/maibot", - "keywords": ["core", "chat", "reply", "emoji", "action", "built-in"], - "categories": ["Core System", "Chat Management"], + "keywords": ["emoji", "action", "built-in"], + "categories": ["Emoji"], "default_locale": "zh-CN", "locales_path": "_locales", @@ -24,26 +24,11 @@ "is_built_in": true, "plugin_type": "action_provider", "components": [ - { - "type": "action", - "name": "no_reply", - "description": "暂时不回复消息,等待新消息或超时" - }, - { - "type": "action", - "name": "reply", - "description": "执行基本回复动作" - }, { "type": "action", "name": "emoji", "description": "发送表情包辅助表达情绪" - }, - { - "type": "action", - "name": "anti_injector_manager", - "description": "管理和监控反注入系统" } ] } -} +} \ No newline at end of file diff --git a/src/plugins/built_in/core_actions/no_reply.py b/src/plugins/built_in/core_actions/no_reply.py deleted file mode 100644 index 138ca53eb..000000000 --- a/src/plugins/built_in/core_actions/no_reply.py +++ /dev/null @@ -1,60 +0,0 @@ -from typing import Tuple - -# 导入新插件系统 -from src.plugin_system import BaseAction, ActionActivationType, ChatMode - -# 导入依赖的系统组件 -from src.common.logger import get_logger - - -logger = get_logger("no_reply_action") - - -class NoReplyAction(BaseAction): - """不回复动作,支持waiting和breaking两种形式.""" - - focus_activation_type = ActionActivationType.ALWAYS # 修复:在focus模式下应该始终可用 - normal_activation_type = ActionActivationType.ALWAYS # 修复:在normal模式下应该始终可用 - mode_enable = ChatMode.FOCUS # 修复:只在专注模式下有用 - parallel_action = False - - # 动作基本信息 - action_name = "no_reply" - action_description = "暂时不回复消息" - - # 动作参数定义 - action_parameters = { - "reason": "不回复的原因", - } - - # 动作使用场景 - action_require = [""] - - # 关联类型 - associated_types = [] - - async def execute(self) -> Tuple[bool, str]: - """执行不回复动作""" - - try: - reason = self.action_data.get("reason", "") - - logger.info(f"{self.log_prefix} 选择不回复,原因: {reason}") - - await self.store_action_info( - action_build_into_prompt=False, - action_prompt_display=reason, - action_done=True, - ) - return True, reason - - except Exception as e: - logger.error(f"{self.log_prefix} 不回复动作执行失败: {e}") - exit_reason = f"执行异常: {str(e)}" - full_prompt = f"no_reply执行异常: {exit_reason},你思考是否要进行回复" - await self.store_action_info( - action_build_into_prompt=True, - action_prompt_display=full_prompt, - action_done=True, - ) - return False, f"不回复动作执行失败: {e}" diff --git a/src/plugins/built_in/core_actions/plugin.py b/src/plugins/built_in/core_actions/plugin.py index 5316d6658..14c86f246 100644 --- a/src/plugins/built_in/core_actions/plugin.py +++ b/src/plugins/built_in/core_actions/plugin.py @@ -16,7 +16,6 @@ from src.plugin_system.base.config_types import ConfigField 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.reply import ReplyAction from src.plugins.built_in.core_actions.emoji import EmojiAction from src.plugins.built_in.core_actions.anti_injector_manager import AntiInjectorStatusCommand @@ -53,10 +52,9 @@ class CoreActionsPlugin(BasePlugin): config_schema: dict = { "plugin": { "enabled": ConfigField(type=bool, default=True, description="是否启用插件"), - "config_version": ConfigField(type=str, default="0.5.0", description="配置文件版本"), + "config_version": ConfigField(type=str, default="0.6.0", description="配置文件版本"), }, "components": { - "enable_no_reply": ConfigField(type=bool, default=True, description="是否启用不回复动作"), "enable_reply": ConfigField(type=bool, default=True, description="是否启用基本回复动作"), "enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"), "enable_anti_injector_manager": ConfigField( @@ -70,8 +68,6 @@ class CoreActionsPlugin(BasePlugin): # --- 根据配置注册组件 --- components = [] - if self.get_config("components.enable_no_reply", True): - components.append((NoReplyAction.get_action_info(), NoReplyAction)) if self.get_config("components.enable_reply", True): components.append((ReplyAction.get_action_info(), ReplyAction)) if self.get_config("components.enable_emoji", True):