refactor(schedule): 优化月度计划与日程的生成逻辑

将月度计划和每日日程的生成流程从异步非阻塞任务改为同步阻塞模式。这确保了在需要计划数据时,生成过程会先完成,从而避免了因数据未及时生成而导致的潜在问题。

主要变更:
- `ScheduleManager`: 在加载日程失败或数据不存在时,直接调用并等待日程生成,而不是创建一个后台任务。
- `ScheduleManager`: 当日程生成过程中发现月度计划不足时,会直接调用并等待月度计划生成完成,然后再继续。
- `MonthlyPlanManager`: 简化了生成逻辑,移除了原有的非阻塞触发方法 `trigger_generate_monthly_plans`,统一为同步执行的 `generate_monthly_plans`。
- `monthly_plan_db`: 在查询月度计划时增加了按创建时间降序排序,以支持更可靠地删除多余计划。
- `main.py`: 移除了对已不存在的 `ImportError` 的处理。
This commit is contained in:
minecraft1024a
2025-08-31 13:06:06 +08:00
parent cc2d8a89ee
commit e2a2d102e0
4 changed files with 52 additions and 56 deletions

View File

@@ -63,7 +63,7 @@ def get_active_plans_for_month(month: str) -> List[MonthlyPlan]:
plans = session.query(MonthlyPlan).filter( plans = session.query(MonthlyPlan).filter(
MonthlyPlan.target_month == month, MonthlyPlan.target_month == month,
MonthlyPlan.status == 'active' MonthlyPlan.status == 'active'
).all() ).order_by(MonthlyPlan.created_at.desc()).all()
return plans return plans
except Exception as e: except Exception as e:
logger.error(f"查询 {month} 的有效月度计划时发生错误: {e}") logger.error(f"查询 {month} 的有效月度计划时发生错误: {e}")

View File

@@ -216,8 +216,6 @@ MoFox_Bot(第三方修改版)
from src.chat.memory_system.async_memory_optimizer import async_memory_manager from src.chat.memory_system.async_memory_optimizer import async_memory_manager
await async_memory_manager.initialize() await async_memory_manager.initialize()
logger.info("记忆管理器初始化成功") logger.info("记忆管理器初始化成功")
except ImportError:
logger.warning("异步记忆优化方法不可用,将回退使用同步模式")
except Exception as e: except Exception as e:
logger.error(f"记忆管理器初始化失败: {e}") logger.error(f"记忆管理器初始化失败: {e}")
else: else:

View File

@@ -66,8 +66,9 @@ class MonthlyPlanManager:
target_month = datetime.now().strftime("%Y-%m") target_month = datetime.now().strftime("%Y-%m")
if not has_active_plans(target_month): if not has_active_plans(target_month):
logger.info(f" {target_month} 没有任何有效的月度计划,将立即生成。") logger.info(f" {target_month} 没有任何有效的月度计划,将触发同步生成。")
return await self.generate_monthly_plans(target_month) generation_successful = await self._generate_monthly_plans_logic(target_month)
return generation_successful
else: else:
logger.info(f"{target_month} 已存在有效的月度计划。") logger.info(f"{target_month} 已存在有效的月度计划。")
plans = get_active_plans_for_month(target_month) plans = get_active_plans_for_month(target_month)
@@ -76,8 +77,8 @@ class MonthlyPlanManager:
max_plans = global_config.monthly_plan_system.max_plans_per_month max_plans = global_config.monthly_plan_system.max_plans_per_month
if len(plans) > max_plans: if len(plans) > max_plans:
logger.warning(f"当前月度计划数量 ({len(plans)}) 超出上限 ({max_plans}),将自动删除多余的计划。") logger.warning(f"当前月度计划数量 ({len(plans)}) 超出上限 ({max_plans}),将自动删除多余的计划。")
# 按创建时间序排序(的在前),然后删除超出上限的部分(新的) # 数据库查询结果已按创建时间序排序(的在前),直接截取超出上限的部分进行删除
plans_to_delete = sorted(plans, key=lambda p: p.created_at, reverse=True)[:len(plans)-max_plans] plans_to_delete = plans[:len(plans)-max_plans]
delete_ids = [p.id for p in plans_to_delete] delete_ids = [p.id for p in plans_to_delete]
delete_plans_by_ids(delete_ids) delete_plans_by_ids(delete_ids)
# 重新获取计划列表 # 重新获取计划列表
@@ -88,9 +89,20 @@ class MonthlyPlanManager:
logger.info(f"当前月度计划内容:\n{plan_texts}") logger.info(f"当前月度计划内容:\n{plan_texts}")
return True # 已经有计划,也算成功 return True # 已经有计划,也算成功
async def generate_monthly_plans(self, target_month: Optional[str] = None) -> bool: async def generate_monthly_plans(self, target_month: Optional[str] = None):
""" """
生成指定月份的月度计划 启动月度计划生成。
"""
if self.generation_running:
logger.info("月度计划生成任务已在运行中,跳过重复启动")
return
logger.info(f"已触发 {target_month or '当前月份'} 的月度计划生成任务。")
await self._generate_monthly_plans_logic(target_month)
async def _generate_monthly_plans_logic(self, target_month: Optional[str] = None) -> bool:
"""
生成指定月份的月度计划的核心逻辑
:param target_month: 目标月份,格式为 "YYYY-MM"。如果为 None则为当前月份。 :param target_month: 目标月份,格式为 "YYYY-MM"。如果为 None则为当前月份。
:return: 是否生成成功 :return: 是否生成成功
@@ -138,14 +150,6 @@ class MonthlyPlanManager:
finally: finally:
self.generation_running = False self.generation_running = False
def trigger_generate_monthly_plans(self, target_month: Optional[str] = None):
"""
以非阻塞的方式启动月度计划生成任务。
这允许其他模块如ScheduleManager触发计划生成而无需等待其完成。
"""
logger.info(f"已触发 {target_month or '当前月份'} 的非阻塞月度计划生成任务。")
asyncio.create_task(self.generate_monthly_plans(target_month))
def _get_previous_month(self, current_month: str) -> str: def _get_previous_month(self, current_month: str) -> str:
"""获取上个月的月份字符串""" """获取上个月的月份字符串"""
try: try:
@@ -289,8 +293,6 @@ class MonthlyPlanManager:
except Exception as e: except Exception as e:
logger.error(f" 归档 {target_month} 月度计划时发生错误: {e}") logger.error(f" 归档 {target_month} 月度计划时发生错误: {e}")
class MonthlyPlanGenerationTask(AsyncTask): class MonthlyPlanGenerationTask(AsyncTask):
"""每月初自动生成新月度计划的任务""" """每月初自动生成新月度计划的任务"""
@@ -324,7 +326,7 @@ class MonthlyPlanGenerationTask(AsyncTask):
# 生成新月份的计划 # 生成新月份的计划
current_month = next_month.strftime("%Y-%m") current_month = next_month.strftime("%Y-%m")
logger.info(f" 到达月初,开始生成 {current_month} 的月度计划...") logger.info(f" 到达月初,开始生成 {current_month} 的月度计划...")
await self.monthly_plan_manager.generate_monthly_plans(current_month) await self.monthly_plan_manager._generate_monthly_plans_logic(current_month)
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info(" 每月月度计划生成任务被取消。") logger.info(" 每月月度计划生成任务被取消。")

View File

@@ -159,11 +159,7 @@ class ScheduleManager:
schedule_record = session.query(Schedule).filter(Schedule.date == today_str).first() schedule_record = session.query(Schedule).filter(Schedule.date == today_str).first()
if schedule_record: if schedule_record:
logger.info(f"从数据库加载今天的日程 ({today_str})。") logger.info(f"从数据库加载今天的日程 ({today_str})。")
try:
schedule_data = orjson.loads(str(schedule_record.schedule_data)) schedule_data = orjson.loads(str(schedule_record.schedule_data))
# 使用Pydantic验证日程数据
if self._validate_schedule_with_pydantic(schedule_data): if self._validate_schedule_with_pydantic(schedule_data):
self.today_schedule = schedule_data self.today_schedule = schedule_data
schedule_str = f"已成功加载今天的日程 ({today_str})\n" schedule_str = f"已成功加载今天的日程 ({today_str})\n"
@@ -171,30 +167,28 @@ class ScheduleManager:
for item in self.today_schedule: for item in self.today_schedule:
schedule_str += f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n" schedule_str += f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n"
logger.info(schedule_str) logger.info(schedule_str)
return # 成功加载,直接返回
else: else:
logger.warning("数据库中的日程数据格式无效,将异步重新生成日程") logger.warning("数据库中的日程数据格式无效,将重新生成日程")
await self.generate_and_save_schedule()
except orjson.JSONDecodeError as e:
logger.error(f"日程数据JSON解析失败: {e},将异步重新生成日程")
await self.generate_and_save_schedule()
else: else:
logger.info(f"数据库中未找到今天的日程 ({today_str}),将异步调用 LLM 生成。") logger.info(f"数据库中未找到今天的日程 ({today_str}),将调用 LLM 生成。")
# 仅在需要时生成
await self.generate_and_save_schedule() await self.generate_and_save_schedule()
except Exception as e: except Exception as e:
logger.error(f"加载或生成日程时出错: {e}") logger.error(f"加载或生成日程时出错: {e}")
# 出错时也尝试异步生成 logger.info("尝试生成日程作为备用方案...")
logger.info("尝试异步生成日程作为备用方案...")
await self.generate_and_save_schedule() await self.generate_and_save_schedule()
async def generate_and_save_schedule(self): async def generate_and_save_schedule(self):
"""启动异步日程生成任务,避免阻塞主程序""" """启动日程生成任务"""
if self.schedule_generation_running: if self.schedule_generation_running:
logger.info("日程生成任务已在运行中,跳过重复启动") logger.info("日程生成任务已在运行中,跳过重复启动")
return return
# 创建异步任务进行日程生成,不阻塞主程序 # 直接执行日程生成
asyncio.create_task(self._async_generate_and_save_schedule()) await self._async_generate_and_save_schedule()
logger.info("已启动异步日程生成任务")
async def _async_generate_and_save_schedule(self): async def _async_generate_and_save_schedule(self):
"""异步生成并保存日程的内部方法""" """异步生成并保存日程的内部方法"""
@@ -234,12 +228,14 @@ class ScheduleManager:
logger.info("可用的月度计划已耗尽或不足,触发后台补充生成...") logger.info("可用的月度计划已耗尽或不足,触发后台补充生成...")
from mmc.src.schedule.monthly_plan_manager import monthly_plan_manager from mmc.src.schedule.monthly_plan_manager import monthly_plan_manager
# 以非阻塞方式触发月度计划生成 # 等待月度计划生成完成
monthly_plan_manager.trigger_generate_monthly_plans(current_month_str) await monthly_plan_manager.ensure_and_generate_plans_if_needed(current_month_str)
# 注意:这里不再等待生成结果,因此后续代码不会立即获得新计划 # 重新获取月度计划
# 日程将基于当前可用的信息生成,新计划将在下一次日程生成时可用。 sampled_plans = get_smart_plans_for_daily_schedule(
logger.info("月度计划的后台生成任务已启动,本次日程将不包含新计划。") current_month_str, max_count=3, avoid_days=avoid_days
)
logger.info("月度计划补充生成完毕,继续日程生成任务。")
if sampled_plans: if sampled_plans:
plan_texts = "\n".join([f"- {plan.plan_text}" for plan in sampled_plans]) plan_texts = "\n".join([f"- {plan.plan_text}" for plan in sampled_plans])
@@ -471,9 +467,9 @@ class DailyScheduleGenerationTask(AsyncTask):
# 2. 等待直到零点 # 2. 等待直到零点
await asyncio.sleep(sleep_seconds) await asyncio.sleep(sleep_seconds)
# 3. 执行异步日程生成 # 3. 执行日程生成
logger.info("到达每日零点,开始异步生成新的一天日程...") logger.info("到达每日零点,开始生成新的一天日程...")
await self.schedule_manager.generate_and_save_schedule() await self.schedule_manager._async_generate_and_save_schedule()
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info("每日日程生成任务被取消。") logger.info("每日日程生成任务被取消。")