From 3948fbacbe73a7e753d5f6219d2747abe829e92b Mon Sep 17 00:00:00 2001 From: minecraft1024a Date: Wed, 27 Aug 2025 19:46:01 +0800 Subject: [PATCH] =?UTF-8?q?refactor(schedule):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=97=A5=E7=A8=8B=E4=B8=8E=E6=9C=88=E5=BA=A6=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 `schedule_manager` 和 `monthly_plan_manager` 从 `src/manager` 移动到 `src/schedule` 目录,以更好地组织与日程和计划相关的功能。 主要变更: - 将 `schedule_manager` 和 `monthly_plan_manager` 移动到新的 `src/schedule` 目录。 - 更新了所有对这两个模块的导入路径,以反映新的文件位置。 - 删除了旧的 `src/manager` 目录下的模块文件。 - `monthly_plan_manager` 内部实现被重构,取代了原有的 `plan_generator.py`,简化了计划生成逻辑。 --- src/chat/chat_loop/heartFC_chat.py | 2 +- src/chat/chat_loop/wakeup_manager.py | 2 +- src/chat/planner_actions/planner.py | 2 +- src/chat/replyer/default_generator.py | 2 +- src/main.py | 4 +- src/manager/monthly_plan_manager.py | 313 -------------- .../services/scheduler_service.py | 2 +- src/schedule/monthly_plan_manager.py | 389 ++++++++++++++---- src/schedule/plan_generator.py | 116 ------ src/{manager => schedule}/schedule_manager.py | 2 +- 10 files changed, 306 insertions(+), 528 deletions(-) delete mode 100644 src/manager/monthly_plan_manager.py delete mode 100644 src/schedule/plan_generator.py rename src/{manager => schedule}/schedule_manager.py (99%) diff --git a/src/chat/chat_loop/heartFC_chat.py b/src/chat/chat_loop/heartFC_chat.py index da67eac81..1b6046ef3 100644 --- a/src/chat/chat_loop/heartFC_chat.py +++ b/src/chat/chat_loop/heartFC_chat.py @@ -8,7 +8,7 @@ from src.config.config import global_config from src.person_info.relationship_builder_manager import relationship_builder_manager from src.chat.express.expression_learner import expression_learner_manager from src.plugin_system.base.component_types import ChatMode -from src.manager.schedule_manager import schedule_manager +from src.schedule.schedule_manager import schedule_manager from src.plugin_system.apis import message_api from .hfc_context import HfcContext diff --git a/src/chat/chat_loop/wakeup_manager.py b/src/chat/chat_loop/wakeup_manager.py index 46af60249..9be420f03 100644 --- a/src/chat/chat_loop/wakeup_manager.py +++ b/src/chat/chat_loop/wakeup_manager.py @@ -105,7 +105,7 @@ class WakeUpManager: if not self.enabled: return False - from src.manager.schedule_manager import schedule_manager + from src.schedule.schedule_manager import schedule_manager # 只有在休眠状态下才累积唤醒度 if not schedule_manager.is_sleeping(): diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py index 72fa707fc..4ffcc7168 100644 --- a/src/chat/planner_actions/planner.py +++ b/src/chat/planner_actions/planner.py @@ -21,7 +21,7 @@ from src.chat.planner_actions.action_manager import ActionManager from src.chat.message_receive.chat_stream import get_chat_manager from src.plugin_system.base.component_types import ActionInfo, ChatMode, ComponentType from src.plugin_system.core.component_registry import component_registry -from src.manager.schedule_manager import schedule_manager +from src.schedule.schedule_manager import schedule_manager from src.mood.mood_manager import mood_manager from src.chat.memory_system.Hippocampus import hippocampus_manager logger = get_logger("planner") diff --git a/src/chat/replyer/default_generator.py b/src/chat/replyer/default_generator.py index bbdcf6f88..2691fd3a5 100644 --- a/src/chat/replyer/default_generator.py +++ b/src/chat/replyer/default_generator.py @@ -30,7 +30,7 @@ from src.mood.mood_manager import mood_manager from src.person_info.person_info import Person, is_person_known from src.plugin_system.base.component_types import ActionInfo, EventType from src.plugin_system.apis import llm_api -from src.manager.schedule_manager import schedule_manager +from src.schedule.schedule_manager import schedule_manager logger = get_logger("replyer") diff --git a/src/main.py b/src/main.py index eea276c87..4ff4b4825 100644 --- a/src/main.py +++ b/src/main.py @@ -16,8 +16,8 @@ from src.individuality.individuality import get_individuality, Individuality from src.common.server import get_global_server, Server from src.mood.mood_manager import mood_manager from rich.traceback import install -from src.manager.schedule_manager import schedule_manager -from src.manager.monthly_plan_manager import monthly_plan_manager +from src.schedule.schedule_manager import schedule_manager +from src.schedule.monthly_plan_manager import monthly_plan_manager from src.plugin_system.core.event_manager import event_manager from src.plugin_system.base.component_types import EventType # from src.api.main import start_api_server diff --git a/src/manager/monthly_plan_manager.py b/src/manager/monthly_plan_manager.py deleted file mode 100644 index f5f20cef2..000000000 --- a/src/manager/monthly_plan_manager.py +++ /dev/null @@ -1,313 +0,0 @@ -# mmc/src/manager/monthly_plan_manager.py - -import asyncio -from datetime import datetime, timedelta -from typing import List, Optional - -from src.common.database.monthly_plan_db import ( - add_new_plans, - get_archived_plans_for_month, - archive_active_plans_for_month, - has_active_plans -) -from src.config.config import global_config, model_config -from src.llm_models.utils_model import LLMRequest -from src.common.logger import get_logger -from src.manager.async_task_manager import AsyncTask, async_task_manager - -logger = get_logger("monthly_plan_manager") - -# 默认的月度计划生成指导原则 -DEFAULT_MONTHLY_PLAN_GUIDELINES = """ -我希望你能为自己制定一些有意义的月度小目标和计划。 -这些计划应该涵盖学习、娱乐、社交、个人成长等各个方面。 -每个计划都应该是具体可行的,能够在一个月内通过日常活动逐步实现。 -请确保计划既有挑战性又不会过于繁重,保持生活的平衡和乐趣。 -""" - -class MonthlyPlanManager: - """月度计划管理器 - - 负责月度计划的生成、管理和生命周期控制。 - 与 ScheduleManager 解耦,专注于月度层面的计划管理。 - """ - - def __init__(self): - self.llm = LLMRequest( - model_set=model_config.model_task_config.schedule_generator, - request_type="monthly_plan" - ) - self.generation_running = False - self.monthly_task_started = False - - async def start_monthly_plan_generation(self): - """启动每月初自动生成新月度计划的任务,并在启动时检查一次""" - if not self.monthly_task_started: - logger.info("正在启动每月月度计划生成任务...") - task = MonthlyPlanGenerationTask(self) - await async_task_manager.add_task(task) - self.monthly_task_started = True - logger.info("每月月度计划生成任务已成功启动。") - - # 启动时立即检查并按需生成 - logger.info("执行启动时月度计划检查...") - await self.ensure_and_generate_plans_if_needed() - else: - logger.info("每月月度计划生成任务已在运行中。") - - async def ensure_and_generate_plans_if_needed(self, target_month: Optional[str] = None) -> bool: - """ - 确保指定月份有计划,如果没有则触发生成。 - 这是按需生成的主要入口点。 - """ - if target_month is None: - target_month = datetime.now().strftime("%Y-%m") - - if not has_active_plans(target_month): - logger.info(f"{target_month} 没有任何有效的月度计划,将立即生成。") - return await self.generate_monthly_plans(target_month) - else: - # logger.info(f"{target_month} 已存在有效的月度计划,跳过生成。") - return True # 已经有计划,也算成功 - - async def generate_monthly_plans(self, target_month: Optional[str] = None) -> bool: - """ - 生成指定月份的月度计划 - - :param target_month: 目标月份,格式为 "YYYY-MM"。如果为 None,则为当前月份。 - :return: 是否生成成功 - """ - if self.generation_running: - logger.info("月度计划生成任务已在运行中,跳过重复启动") - return False - - self.generation_running = True - - try: - # 确定目标月份 - if target_month is None: - target_month = datetime.now().strftime("%Y-%m") - - logger.info(f"开始为 {target_month} 生成月度计划...") - - # 检查是否启用月度计划系统 - if not global_config.monthly_plan_system or not global_config.monthly_plan_system.enable: - logger.info("月度计划系统已禁用,跳过计划生成。") - return False - - # 获取上个月的归档计划作为参考 - last_month = self._get_previous_month(target_month) - archived_plans = get_archived_plans_for_month(last_month) - - # 构建生成 Prompt - prompt = self._build_generation_prompt(target_month, archived_plans) - - # 调用 LLM 生成计划 - plans = await self._generate_plans_with_llm(prompt) - - if plans: - # 保存到数据库 - add_new_plans(plans, target_month) - logger.info(f"成功为 {target_month} 生成并保存了 {len(plans)} 条月度计划。") - return True - else: - logger.warning(f"未能为 {target_month} 生成有效的月度计划。") - return False - - except Exception as e: - logger.error(f"生成 {target_month} 月度计划时发生错误: {e}") - return False - finally: - self.generation_running = False - - def _get_previous_month(self, current_month: str) -> str: - """获取上个月的月份字符串""" - try: - year, month = map(int, current_month.split('-')) - if month == 1: - return f"{year-1}-12" - else: - return f"{year}-{month-1:02d}" - except Exception: - # 如果解析失败,返回一个不存在的月份 - return "1900-01" - - def _build_generation_prompt(self, target_month: str, archived_plans: List) -> str: - """构建月度计划生成的 Prompt""" - - # 获取配置 - guidelines = getattr(global_config.monthly_plan_system, 'guidelines', None) or DEFAULT_MONTHLY_PLAN_GUIDELINES - personality = global_config.personality.personality_core - personality_side = global_config.personality.personality_side - max_plans = global_config.monthly_plan_system.max_plans_per_month - - # 构建上月未完成计划的参考信息 - archived_plans_block = "" - if archived_plans: - archived_texts = [f"- {plan.plan_text}" for plan in archived_plans[:5]] # 最多显示5个 - archived_plans_block = f""" -**上个月未完成的一些计划(可作为参考)**: -{chr(10).join(archived_texts)} - -你可以考虑是否要在这个月继续推进这些计划,或者制定全新的计划。 -""" - - prompt = f""" -我,{global_config.bot.nickname},需要为自己制定 {target_month} 的月度计划。 - -**关于我**: -- **核心人设**: {personality} -- **具体习惯与兴趣**: -{personality_side} - -{archived_plans_block} - -**我的月度计划制定原则**: -{guidelines} - -**重要要求**: -1. 请为我生成 {max_plans} 条左右的月度计划 -2. 每条计划都应该是一句话,简洁明了,具体可行 -3. 计划应该涵盖不同的生活方面(学习、娱乐、社交、个人成长等) -4. 返回格式必须是纯文本,每行一条计划,不要使用 JSON 或其他格式 -5. 不要包含任何解释性文字,只返回计划列表 - -**示例格式**: -学习一门新的编程语言或技术 -每周至少看两部有趣的电影 -与朋友们组织一次户外活动 -阅读3本感兴趣的书籍 -尝试制作一道新的料理 - -请你扮演我,以我的身份和兴趣,为 {target_month} 制定合适的月度计划。 -""" - - return prompt - - async def _generate_plans_with_llm(self, prompt: str) -> List[str]: - """使用 LLM 生成月度计划列表""" - max_retries = 3 - - for attempt in range(1, max_retries + 1): - try: - logger.info(f"正在生成月度计划 (第 {attempt} 次尝试)") - - response, _ = await self.llm.generate_response_async(prompt) - - # 解析响应 - plans = self._parse_plans_response(response) - - if plans: - logger.info(f"成功生成 {len(plans)} 条月度计划") - return plans - else: - logger.warning(f"第 {attempt} 次生成的计划为空,继续重试...") - - except Exception as e: - logger.error(f"第 {attempt} 次生成月度计划失败: {e}") - - # 添加短暂延迟,避免过于频繁的请求 - if attempt < max_retries: - await asyncio.sleep(2) - - logger.error("所有尝试都失败,无法生成月度计划") - return [] - - def _parse_plans_response(self, response: str) -> List[str]: - """解析 LLM 响应,提取计划列表""" - try: - # 清理响应文本 - response = response.strip() - - # 按行分割 - lines = [line.strip() for line in response.split('\n') if line.strip()] - - # 过滤掉明显不是计划的行(比如包含特殊标记的行) - plans = [] - for line in lines: - # 跳过包含特殊标记的行 - if any(marker in line for marker in ['**', '##', '```', '---', '===', '###']): - continue - - # 移除可能的序号前缀 - line = line.lstrip('0123456789.- ') - - # 确保计划不为空且有意义 - if len(line) > 5 and not line.startswith(('请', '以上', '总结', '注意')): - plans.append(line) - - # 限制计划数量 - max_plans = global_config.monthly_plan_system.max_plans_per_month - if len(plans) > max_plans: - plans = plans[:max_plans] - - return plans - - except Exception as e: - logger.error(f"解析月度计划响应时发生错误: {e}") - return [] - - async def archive_current_month_plans(self, target_month: Optional[str] = None): - """ - 归档当前月份的活跃计划 - - :param target_month: 目标月份,格式为 "YYYY-MM"。如果为 None,则为当前月份。 - """ - try: - if target_month is None: - target_month = datetime.now().strftime("%Y-%m") - - logger.info(f"开始归档 {target_month} 的活跃月度计划...") - archived_count = archive_active_plans_for_month(target_month) - logger.info(f"成功归档了 {archived_count} 条 {target_month} 的月度计划。") - - except Exception as e: - logger.error(f"归档 {target_month} 月度计划时发生错误: {e}") - - -class MonthlyPlanGenerationTask(AsyncTask): - """每月初自动生成新月度计划的任务""" - - def __init__(self, monthly_plan_manager: MonthlyPlanManager): - super().__init__(task_name="MonthlyPlanGenerationTask") - self.monthly_plan_manager = monthly_plan_manager - - async def run(self): - while True: - try: - # 计算到下个月1号凌晨的时间 - now = datetime.now() - - # 获取下个月的第一天 - if now.month == 12: - next_month = datetime(now.year + 1, 1, 1) - else: - next_month = datetime(now.year, now.month + 1, 1) - - sleep_seconds = (next_month - now).total_seconds() - - logger.info(f"下一次月度计划生成任务将在 {sleep_seconds:.2f} 秒后运行 (北京时间 {next_month.strftime('%Y-%m-%d %H:%M:%S')})") - - # 等待直到下个月1号 - await asyncio.sleep(sleep_seconds) - - # 先归档上个月的计划 - last_month = (next_month - timedelta(days=1)).strftime("%Y-%m") - await self.monthly_plan_manager.archive_current_month_plans(last_month) - - # 生成新月份的计划 - current_month = next_month.strftime("%Y-%m") - logger.info(f"到达月初,开始生成 {current_month} 的月度计划...") - await self.monthly_plan_manager.generate_monthly_plans(current_month) - - except asyncio.CancelledError: - logger.info("每月月度计划生成任务被取消。") - break - except Exception as e: - logger.error(f"每月月度计划生成任务发生未知错误: {e}") - # 发生错误后,等待1小时再重试,避免频繁失败 - await asyncio.sleep(3600) - - -# 全局实例 -monthly_plan_manager = MonthlyPlanManager() \ No newline at end of file diff --git a/src/plugins/built_in/maizone_refactored/services/scheduler_service.py b/src/plugins/built_in/maizone_refactored/services/scheduler_service.py index 86dea92e9..e6498ab9b 100644 --- a/src/plugins/built_in/maizone_refactored/services/scheduler_service.py +++ b/src/plugins/built_in/maizone_refactored/services/scheduler_service.py @@ -10,7 +10,7 @@ import traceback from typing import Callable from src.common.logger import get_logger -from src.manager.schedule_manager import schedule_manager +from src.schedule.schedule_manager import schedule_manager from src.common.database.sqlalchemy_database_api import get_db_session from src.common.database.sqlalchemy_models import MaiZoneScheduleStatus diff --git a/src/schedule/monthly_plan_manager.py b/src/schedule/monthly_plan_manager.py index 85ec08019..2b7899d83 100644 --- a/src/schedule/monthly_plan_manager.py +++ b/src/schedule/monthly_plan_manager.py @@ -1,106 +1,313 @@ -# mmc/src/schedule/monthly_plan_manager.py -# 我要混提交 -import datetime -from src.config.config import global_config -from src.common.database.monthly_plan_db import get_active_plans_for_month, add_new_plans -from src.schedule.plan_generator import PlanGenerator +# mmc/src/manager/monthly_plan_manager.py + +import asyncio +from datetime import datetime, timedelta +from typing import List, Optional + +from src.common.database.monthly_plan_db import ( + add_new_plans, + get_archived_plans_for_month, + archive_active_plans_for_month, + has_active_plans +) +from src.config.config import global_config, model_config +from src.llm_models.utils_model import LLMRequest from src.common.logger import get_logger +from src.manager.async_task_manager import AsyncTask, async_task_manager logger = get_logger("monthly_plan_manager") +# 默认的月度计划生成指导原则 +DEFAULT_MONTHLY_PLAN_GUIDELINES = """ +我希望你能为自己制定一些有意义的月度小目标和计划。 +这些计划应该涵盖学习、娱乐、社交、个人成长等各个方面。 +每个计划都应该是具体可行的,能够在一个月内通过日常活动逐步实现。 +请确保计划既有挑战性又不会过于繁重,保持生活的平衡和乐趣。 +""" + class MonthlyPlanManager: + """月度计划管理器 + + 负责月度计划的生成、管理和生命周期控制。 + 与 ScheduleManager 解耦,专注于月度层面的计划管理。 """ - 管理月度计划的生成和填充。 - """ + + def __init__(self): + self.llm = LLMRequest( + model_set=model_config.model_task_config.schedule_generator, + request_type="monthly_plan" + ) + self.generation_running = False + self.monthly_task_started = False - @staticmethod - async def initialize_monthly_plans(): - """ - 程序启动时调用,检查并按需填充当月的计划池。 - """ - config = global_config.monthly_plan_system - if not config or not config.enable: - logger.info("月层计划系统未启用,跳过初始化。") - return + async def start_monthly_plan_generation(self): + """启动每月初自动生成新月度计划的任务,并在启动时检查一次""" + if not self.monthly_task_started: + logger.info(" 正在启动每月月度计划生成任务...") + task = MonthlyPlanGenerationTask(self) + await async_task_manager.add_task(task) + self.monthly_task_started = True + logger.info(" 每月月度计划生成任务已成功启动。") + + # 启动时立即检查并按需生成 + logger.info(" 执行启动时月度计划检查...") + await self.ensure_and_generate_plans_if_needed() + else: + logger.info(" 每月月度计划生成任务已在运行中。") - now = datetime.datetime.now() - current_month_str = now.strftime("%Y-%m") + async def ensure_and_generate_plans_if_needed(self, target_month: Optional[str] = None) -> bool: + """ + 确保指定月份有计划,如果没有则触发生成。 + 这是按需生成的主要入口点。 + """ + if target_month is None: + target_month = datetime.now().strftime("%Y-%m") + + if not has_active_plans(target_month): + logger.info(f" {target_month} 没有任何有效的月度计划,将立即生成。") + return await self.generate_monthly_plans(target_month) + else: + # logger.info(f"{target_month} 已存在有效的月度计划,跳过生成。") + return True # 已经有计划,也算成功 + + async def generate_monthly_plans(self, target_month: Optional[str] = None) -> bool: + """ + 生成指定月份的月度计划 + + :param target_month: 目标月份,格式为 "YYYY-MM"。如果为 None,则为当前月份。 + :return: 是否生成成功 + """ + if self.generation_running: + logger.info("月度计划生成任务已在运行中,跳过重复启动") + return False + + self.generation_running = True try: - # 1. 检查当月已有计划数量 - existing_plans = get_active_plans_for_month(current_month_str) - plan_count = len(existing_plans) + # 确定目标月份 + if target_month is None: + target_month = datetime.now().strftime("%Y-%m") - header = "📅 月度计划检查" + logger.info(f"开始为 {target_month} 生成月度计划...") - # 2. 判断是否需要生成新计划 - if plan_count >= config.generation_threshold: - summary = f"计划数量充足 ({plan_count}/{config.generation_threshold}),无需生成。" - log_message = ( - f"\n┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n" - f"┃ {header}\n" - f"┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫\n" - f"┃ 月份: {current_month_str}\n" - f"┃ 状态: {summary}\n" - f"┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" - ) - logger.info(log_message) - return - - # 3. 计算需要生成的计划数量并调用生成器 - needed_plans = config.generation_threshold - plan_count - summary = f"计划不足 ({plan_count}/{config.generation_threshold}),需要生成 {needed_plans} 条。" - generation_info = f"即将生成 {config.plans_per_generation} 条新计划..." - log_message = ( - f"\n┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n" - f"┃ {header}\n" - f"┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫\n" - f"┃ 月份: {current_month_str}\n" - f"┃ 状态: {summary}\n" - f"┃ 操作: {generation_info}\n" - f"┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" - ) - logger.info(log_message) + # 检查是否启用月度计划系统 + if not global_config.monthly_plan_system or not global_config.monthly_plan_system.enable: + logger.info(" 月度计划系统已禁用,跳过计划生成。") + return False - generator = PlanGenerator() - new_plans = await generator.generate_plans( - year=now.year, - month=now.month, - count=config.plans_per_generation # 每次生成固定数量以保证质量 - ) - - # 4. 将新计划存入数据库 - if new_plans: - add_new_plans(new_plans, current_month_str) - completion_header = "✅ 月度计划生成完毕" - completion_summary = f"成功添加 {len(new_plans)} 条新计划。" - - # 构建计划详情 - plan_details = "\n".join([f"┃ - {plan}" for plan in new_plans]) - - log_message = ( - f"\n┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n" - f"┃ {completion_header}\n" - f"┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫\n" - f"┃ 月份: {current_month_str}\n" - f"┃ 结果: {completion_summary}\n" - f"┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫\n" - f"{plan_details}\n" - f"┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" - ) - logger.info(log_message) + # 获取上个月的归档计划作为参考 + last_month = self._get_previous_month(target_month) + archived_plans = get_archived_plans_for_month(last_month) + + # 构建生成 Prompt + prompt = self._build_generation_prompt(target_month, archived_plans) + + # 调用 LLM 生成计划 + plans = await self._generate_plans_with_llm(prompt) + + if plans: + # 保存到数据库 + add_new_plans(plans, target_month) + logger.info(f"成功为 {target_month} 生成并保存了 {len(plans)} 条月度计划。") + return True else: - completion_header = "❌ 月度计划生成失败" - completion_summary = "未能生成任何新的月度计划。" - log_message = ( - f"\n┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n" - f"┃ {completion_header}\n" - f"┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫\n" - f"┃ 月份: {current_month_str}\n" - f"┃ 结果: {completion_summary}\n" - f"┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" - ) - logger.warning(log_message) - + logger.warning(f"未能为 {target_month} 生成有效的月度计划。") + return False + except Exception as e: - logger.error(f"初始化月度计划时发生严重错误: {e}", exc_info=True) \ No newline at end of file + logger.error(f" 生成 {target_month} 月度计划时发生错误: {e}") + return False + finally: + self.generation_running = False + + def _get_previous_month(self, current_month: str) -> str: + """获取上个月的月份字符串""" + try: + year, month = map(int, current_month.split('-')) + if month == 1: + return f"{year-1}-12" + else: + return f"{year}-{month-1:02d}" + except Exception: + # 如果解析失败,返回一个不存在的月份 + return "1900-01" + + def _build_generation_prompt(self, target_month: str, archived_plans: List) -> str: + """构建月度计划生成的 Prompt""" + + # 获取配置 + guidelines = getattr(global_config.monthly_plan_system, 'guidelines', None) or DEFAULT_MONTHLY_PLAN_GUIDELINES + personality = global_config.personality.personality_core + personality_side = global_config.personality.personality_side + max_plans = global_config.monthly_plan_system.max_plans_per_month + + # 构建上月未完成计划的参考信息 + archived_plans_block = "" + if archived_plans: + archived_texts = [f"- {plan.plan_text}" for plan in archived_plans[:5]] # 最多显示5个 + archived_plans_block = f""" +**上个月未完成的一些计划(可作为参考)**: +{chr(10).join(archived_texts)} + +你可以考虑是否要在这个月继续推进这些计划,或者制定全新的计划。 +""" + + prompt = f""" +我,{global_config.bot.nickname},需要为自己制定 {target_month} 的月度计划。 + +**关于我**: +- **核心人设**: {personality} +- **具体习惯与兴趣**: +{personality_side} + +{archived_plans_block} + +**我的月度计划制定原则**: +{guidelines} + +**重要要求**: +1. 请为我生成 {max_plans} 条左右的月度计划 +2. 每条计划都应该是一句话,简洁明了,具体可行 +3. 计划应该涵盖不同的生活方面(学习、娱乐、社交、个人成长等) +4. 返回格式必须是纯文本,每行一条计划,不要使用 JSON 或其他格式 +5. 不要包含任何解释性文字,只返回计划列表 + +**示例格式**: +学习一门新的编程语言或技术 +每周至少看两部有趣的电影 +与朋友们组织一次户外活动 +阅读3本感兴趣的书籍 +尝试制作一道新的料理 + +请你扮演我,以我的身份和兴趣,为 {target_month} 制定合适的月度计划。 +""" + + return prompt + + async def _generate_plans_with_llm(self, prompt: str) -> List[str]: + """使用 LLM 生成月度计划列表""" + max_retries = 3 + + for attempt in range(1, max_retries + 1): + try: + logger.info(f" 正在生成月度计划 (第 {attempt} 次尝试)") + + response, _ = await self.llm.generate_response_async(prompt) + + # 解析响应 + plans = self._parse_plans_response(response) + + if plans: + logger.info(f"成功生成 {len(plans)} 条月度计划") + return plans + else: + logger.warning(f"第 {attempt} 次生成的计划为空,继续重试...") + + except Exception as e: + logger.error(f"第 {attempt} 次生成月度计划失败: {e}") + + # 添加短暂延迟,避免过于频繁的请求 + if attempt < max_retries: + await asyncio.sleep(2) + + logger.error(" 所有尝试都失败,无法生成月度计划") + return [] + + def _parse_plans_response(self, response: str) -> List[str]: + """解析 LLM 响应,提取计划列表""" + try: + # 清理响应文本 + response = response.strip() + + # 按行分割 + lines = [line.strip() for line in response.split('\n') if line.strip()] + + # 过滤掉明显不是计划的行(比如包含特殊标记的行) + plans = [] + for line in lines: + # 跳过包含特殊标记的行 + if any(marker in line for marker in ['**', '##', '```', '---', '===', '###']): + continue + + # 移除可能的序号前缀 + line = line.lstrip('0123456789.- ') + + # 确保计划不为空且有意义 + if len(line) > 5 and not line.startswith(('请', '以上', '总结', '注意')): + plans.append(line) + + # 限制计划数量 + max_plans = global_config.monthly_plan_system.max_plans_per_month + if len(plans) > max_plans: + plans = plans[:max_plans] + + return plans + + except Exception as e: + logger.error(f"解析月度计划响应时发生错误: {e}") + return [] + + async def archive_current_month_plans(self, target_month: Optional[str] = None): + """ + 归档当前月份的活跃计划 + + :param target_month: 目标月份,格式为 "YYYY-MM"。如果为 None,则为当前月份。 + """ + try: + if target_month is None: + target_month = datetime.now().strftime("%Y-%m") + + logger.info(f" 开始归档 {target_month} 的活跃月度计划...") + archived_count = archive_active_plans_for_month(target_month) + logger.info(f" 成功归档了 {archived_count} 条 {target_month} 的月度计划。") + + except Exception as e: + logger.error(f" 归档 {target_month} 月度计划时发生错误: {e}") + + +class MonthlyPlanGenerationTask(AsyncTask): + """每月初自动生成新月度计划的任务""" + + def __init__(self, monthly_plan_manager: MonthlyPlanManager): + super().__init__(task_name="MonthlyPlanGenerationTask") + self.monthly_plan_manager = monthly_plan_manager + + async def run(self): + while True: + try: + # 计算到下个月1号凌晨的时间 + now = datetime.now() + + # 获取下个月的第一天 + if now.month == 12: + next_month = datetime(now.year + 1, 1, 1) + else: + next_month = datetime(now.year, now.month + 1, 1) + + sleep_seconds = (next_month - now).total_seconds() + + logger.info(f" 下一次月度计划生成任务将在 {sleep_seconds:.2f} 秒后运行 (北京时间 {next_month.strftime('%Y-%m-%d %H:%M:%S')})") + + # 等待直到下个月1号 + await asyncio.sleep(sleep_seconds) + + # 先归档上个月的计划 + last_month = (next_month - timedelta(days=1)).strftime("%Y-%m") + await self.monthly_plan_manager.archive_current_month_plans(last_month) + + # 生成新月份的计划 + current_month = next_month.strftime("%Y-%m") + logger.info(f" 到达月初,开始生成 {current_month} 的月度计划...") + await self.monthly_plan_manager.generate_monthly_plans(current_month) + + except asyncio.CancelledError: + logger.info(" 每月月度计划生成任务被取消。") + break + except Exception as e: + logger.error(f" 每月月度计划生成任务发生未知错误: {e}") + # 发生错误后,等待1小时再重试,避免频繁失败 + await asyncio.sleep(3600) + + +# 全局实例 +monthly_plan_manager = MonthlyPlanManager() \ No newline at end of file diff --git a/src/schedule/plan_generator.py b/src/schedule/plan_generator.py deleted file mode 100644 index 6599cb7de..000000000 --- a/src/schedule/plan_generator.py +++ /dev/null @@ -1,116 +0,0 @@ -# mmc/src/schedule/plan_generator.py - -import orjson -from typing import List -from pydantic import BaseModel, ValidationError -from json_repair import repair_json - -from src.config.config import global_config, model_config -from src.llm_models.utils_model import LLMRequest -from src.common.logger import get_logger - -logger = get_logger("plan_generator") - -class PlanResponse(BaseModel): - """ - 用于验证月度计划LLM响应的Pydantic模型。 - """ - plans: List[str] - -class PlanGenerator: - """ - 负责生成月度计划。 - """ - - def __init__(self): - self.bot_personality = self._get_bot_personality() - task_config = model_config.model_task_config.get_task("monthly_plan_generator") - self.llm_request = LLMRequest(model_set=task_config, request_type="monthly_plan_generator") - - def _get_bot_personality(self) -> str: - """ - 从全局配置中获取Bot的人设描述。 - """ - core = global_config.personality.personality_core or "" - side = global_config.personality.personality_side or "" - identity = global_config.personality.identity or "" - return f"核心人设: {core}\n侧面人设: {side}\n身份设定: {identity}" - - def _build_prompt(self, year: int, month: int, count: int) -> str: - """ - 构建用于生成月度计划的Prompt。 - """ - prompt_template = f""" - 你是一个富有想象力的助手,你的任务是为一位虚拟角色生成月度计划。 - - **角色设定:** - --- - {self.bot_personality} - --- - - 请为即将到来的 **{year}年{month}月** 设计 **{count}** 个符合该角色身份的、独立的、积极向上的月度计划或小目标。 - - **要求:** - 1. 每个计划都应简短、清晰,用一两句话描述。 - 2. 语言风格必须自然、口语化,严格符合角色的性格设定。 - 3. 计划内容要具有创造性,避免陈词滥调。 - 4. 请以严格的JSON格式返回,格式为:{{"plans": ["计划一", "计划二", ...]}} - 5. 除了JSON对象,不要包含任何额外的解释、注释或前后导语。 - """ - return prompt_template.strip() - - async def generate_plans(self, year: int, month: int, count: int) -> List[str]: - """ - 调用LLM生成指定月份的计划。 - - :param year: 年份 - :param month: 月份 - :param count: 需要生成的计划数量 - :return: 生成的计划文本列表 - """ - try: - # 1. 构建Prompt - prompt = self._build_prompt(year, month, count) - logger.info(f"正在为 {year}-{month} 生成 {count} 个月度计划...") - - # 2. 调用LLM - llm_content, (reasoning, model_name, _) = await self.llm_request.generate_response_async(prompt=prompt) - - logger.info(f"使用模型 '{model_name}' 生成完成。") - if reasoning: - logger.debug(f"模型推理过程: {reasoning}") - - if not llm_content: - logger.error("LLM未能返回有效的计划内容。") - return [] - - # 3. 解析并验证LLM返回的JSON - try: - # 移除可能的Markdown代码块标记 - clean_content = llm_content.strip() - if clean_content.startswith("```json"): - clean_content = clean_content[7:] - if clean_content.endswith("```"): - clean_content = clean_content[:-3] - - # 修复并解析JSON - repaired_json_str = repair_json(clean_content) - data = orjson.loads(repaired_json_str) - - # 使用Pydantic进行验证 - validated_response = PlanResponse.model_validate(data) - plans = validated_response.plans - - logger.info(f"成功生成并验证了 {len(plans)} 个月度计划。") - return plans - - except orjson.JSONDecodeError: - logger.error(f"修复后仍然无法解析LLM返回的JSON: {llm_content}") - return [] - except ValidationError as e: - logger.error(f"LLM返回的JSON格式不符合预期: {e}\n原始响应: {llm_content}") - return [] - - except Exception as e: - logger.error(f"调用LLM生成月度计划时发生未知错误: {e}", exc_info=True) - return [] \ No newline at end of file diff --git a/src/manager/schedule_manager.py b/src/schedule/schedule_manager.py similarity index 99% rename from src/manager/schedule_manager.py rename to src/schedule/schedule_manager.py index 95cfbb0ee..4eeade57f 100644 --- a/src/manager/schedule_manager.py +++ b/src/schedule/schedule_manager.py @@ -226,7 +226,7 @@ class ScheduleManager: # 如果计划耗尽,则触发补充生成 if not sampled_plans: logger.info("可用的月度计划已耗尽或不足,尝试进行补充生成...") - from src.manager.monthly_plan_manager import monthly_plan_manager + from mmc.src.schedule.monthly_plan_manager import monthly_plan_manager success = await monthly_plan_manager.generate_monthly_plans(current_month_str) if success: logger.info("补充生成完成,重新抽取月度计划...")