Merge branch 'master' of https://github.com/MoFox-Studio/MoFox_Bot
This commit is contained in:
124
README.md
124
README.md
@@ -1,9 +1,7 @@
|
||||
<div align="center">
|
||||
|
||||
# 🌟 MoFox_Bot
|
||||
|
||||
**🚀 基于 MaiCore 的增强版 AI 智能体,提供更完善的功能和更好的使用体验**
|
||||
|
||||
<p>
|
||||
<a href="https://www.python.org/">
|
||||
<img src="https://img.shields.io/badge/Python-3.10+-3776ab?logo=python&logoColor=white&style=for-the-badge" alt="Python">
|
||||
@@ -21,46 +19,46 @@
|
||||
<img src="https://img.shields.io/github/v/release/MoFox-Studio/MoFox_Bot?style=for-the-badge&logo=github&logoColor=white&color=orange" alt="Release">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
## 📖 项目介绍
|
||||
|
||||
|
||||
**MoFox_Bot** 是一个基于 [MaiCore](https://github.com/MaiM-with-u/MaiBot) `0.10.0 snapshot.5` 版本的增强型 `fork` 项目。
|
||||
我们在保留原版所有功能的基础上,进行了一系列的改进和功能拓展,致力于提供更强的稳定性、更丰富的功能和更流畅的用户体验。
|
||||
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **第三方项目声明**
|
||||
>
|
||||
> 本项目是由 **MoFox Studio** 独立维护的第三方项目,并非 MaiBot 官方版本。
|
||||
> 所有后续更新和维护均由我们团队负责,与 MaiBot 官方无直接关系。
|
||||
|
||||
|
||||
> [!WARNING]
|
||||
> **迁移风险提示**
|
||||
>
|
||||
> 由于我们对数据库结构进行了重构和优化,从 MaiBot 官方版本直接迁移到 MoFox_Bot **可能会遇到数据不兼容的问题**。
|
||||
> 在迁移前,请务必做好数据备份。
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
## ✨ 功能特性
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
|
||||
|
||||
### 🔧 原版功能(全部保留)
|
||||
|
||||
|
||||
- 🧠 **智能对话系统** - 基于 LLM 的自然语言交互,支持 normal 和 focus 统一化处理
|
||||
- 🔌 **强大插件系统** - 全面重构的插件架构,支持完整的管理 API 和权限控制
|
||||
- 💭 **实时思维系统** - 模拟人类思考过程
|
||||
@@ -69,12 +67,12 @@
|
||||
- 🧠 **持久记忆系统** - 基于图的长期记忆存储
|
||||
- 🎭 **动态人格系统** - 自适应的性格特征和表达方式
|
||||
- 📊 **数据分析** - 内置数据统计和分析功能,更好了解麦麦状态
|
||||
|
||||
|
||||
</td>
|
||||
<td width="50%">
|
||||
|
||||
|
||||
### 🚀 拓展功能
|
||||
|
||||
|
||||
- 🔄 **数据库切换** - 支持 SQLite 与 MySQL 自由切换,采用 SQLAlchemy 2.0 重新构建
|
||||
- 🛡️ **反注入集成** - 内置一整套回复前注入过滤系统,为人格保驾护航
|
||||
- 🎥 **视频分析** - 支持多种视频识别模式,拓展原版视觉
|
||||
@@ -84,118 +82,120 @@
|
||||
- 🎪 **完善的 Event** - 支持动态事件注册和处理器订阅,并实现了聚合结果管理
|
||||
- 🔍 **内嵌魔改插件** - 内置联网搜索等诸多功能,等你来探索
|
||||
- 🌟 **还有更多** - 请参阅详细修改 [commits](https://github.com/MoFox-Studio/MoFox_Bot/commits)
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
## 🔧 系统要求
|
||||
|
||||
|
||||
### 💻 基础环境
|
||||
|
||||
|
||||
| 项目 | 要求 |
|
||||
| ------------ | ---------------------------------------------------- |
|
||||
| 🖥️ 操作系统 | Windows 10/11, macOS 10.14+, Linux (Ubuntu 18.04+) |
|
||||
| 🐍 Python 版本 | Python 3.10 或更高版本 |
|
||||
| 💾 内存 | 建议 4GB 以上可用内存 |
|
||||
| 💿 存储空间 | 至少 2GB 可用空间 |
|
||||
|
||||
|
||||
### 🛠️ 依赖服务
|
||||
|
||||
|
||||
| 服务 | 描述 |
|
||||
| ------------ | ------------------------------------------ |
|
||||
| 🤖 QQ 协议端 | [NapCatQQ](https://github.com/NapNeko/NapCatQQ) 或其他兼容协议端 |
|
||||
| 🗃️ 数据库 | SQLite (内置) 或 MySQL (可选) |
|
||||
| 🔧 管理工具 | Chat2DB (可选,用于数据库管理) |
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
## 🏁 快速开始
|
||||
|
||||
|
||||
### 📦 安装与部署
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
> [!NOTE]
|
||||
> 详细的安装和配置步骤,请务必参考我们的官方文档:
|
||||
> * **Windows 用户部署指南**: [https://mofox-studio.github.io/MoFox-Bot-Docs/docs/deployment_guide.html](https://mofox-studio.github.io/MoFox-Bot-Docs/docs/deployment_guide.html)
|
||||
> * **`bot_config.toml` 究极详细教程**: [https://mofox-studio.github.io/MoFox-Bot-Docs/docs/guides/bot_config_guide.html](https://mofox-studio.github.io/MoFox-Bot-Docs/docs/guides/bot_config_guide.html)
|
||||
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
### ⚙️ 配置要点
|
||||
|
||||
|
||||
1. 📝 **核心配置**: 修改 `config/bot_config.toml` 中的基础设置,如 LLM API Key 等。
|
||||
2. 🤖 **协议端配置**: 设置 NapCatQQ 或其他兼容的 QQ 协议端,确保通信正常。
|
||||
3. 🗃️ **数据库配置**: 根据需求选择 SQLite 或配置你的 MySQL 服务器。
|
||||
4. 🔌 **插件配置**: 在 `config/plugins/` 目录下按需配置插件。
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
## 🙏 致谢
|
||||
|
||||
|
||||
我们衷心感谢以下优秀的开源项目,没有它们,就没有 MoFox_Bot。
|
||||
|
||||
|
||||
| 项目 | 描述 | 贡献 |
|
||||
| ------------------------------------------ | -------------------- | ---------------- |
|
||||
| 🎯 [MaiM-with-u/MaiBot](https://github.com/MaiM-with-u/MaiBot) | 原版 MaiBot 项目 | 提供优秀的基础框架 |
|
||||
| 🐱 [NapNeko/NapCatQQ](https://github.com/NapNeko/NapCatQQ) | 基于 NTQQ 的 Bot 协议端 | 现代化的 QQ 协议实现 |
|
||||
| 🌌 [internetsb/Maizone](https://github.com/internetsb/Maizone) | 魔改空间插件 | 插件部分功能借鉴 |
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
|
||||
> [!CAUTION]
|
||||
> **重要提醒**
|
||||
>
|
||||
> - 使用本项目前,你必须阅读并同意 [**📋 用户协议 (EULA.md)**](EULA.md)。
|
||||
> - 本应用生成的内容来自人工智能大模型,请仔细甄别其准确性,并请勿用于任何违反法律法规的用途。
|
||||
> - AI 生成的所有内容不代表本项目团队的任何观点和立场。
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
## 📄 开源协议
|
||||
|
||||
|
||||
本项目基于 **[GPL-3.0](LICENSE)** 协议开源。
|
||||
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
|
||||
```
|
||||
Copyright © 2025 MoFox Studio
|
||||
Licensed under the GNU General Public License v3.0
|
||||
```
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
**🌟 如果这个项目对你有帮助,请给我们一个 Star!**
|
||||
|
||||
|
||||
**💬 有任何问题或建议?欢迎提交 Issue 或 Pull Request!**
|
||||
|
||||
*Made with ❤️ by [MoFox Studio](https://github.com/MoFox-Studio)*
|
||||
**💬 [点击加入 QQ 交流群](https://qm.qq.com/q/jfeu7Dq7VS)**
|
||||
|
||||
_Made with ❤️ by [MoFox Studio](https://github.com/MoFox-Studio)_
|
||||
|
||||
</div>
|
||||
|
||||
@@ -103,6 +103,7 @@ class EnergyManager:
|
||||
self.context.sleep_pressure -= decay_per_10s
|
||||
self.context.sleep_pressure = max(self.context.sleep_pressure, 0)
|
||||
self._log_sleep_pressure_change("睡眠压力释放")
|
||||
self.context.save_context_state()
|
||||
else:
|
||||
# 清醒时:处理能量衰减
|
||||
is_group_chat = self.context.chat_stream.group_info is not None
|
||||
@@ -123,6 +124,7 @@ class EnergyManager:
|
||||
self.context.energy_value = max(self.context.energy_value, 0.3)
|
||||
|
||||
self._log_energy_change("能量值衰减")
|
||||
self.context.save_context_state()
|
||||
|
||||
def _should_log_energy(self) -> bool:
|
||||
"""
|
||||
@@ -150,6 +152,7 @@ class EnergyManager:
|
||||
self.context.sleep_pressure += increment
|
||||
self.context.sleep_pressure = min(self.context.sleep_pressure, 100.0) # 设置一个100的上限
|
||||
self._log_sleep_pressure_change("执行动作,睡眠压力累积")
|
||||
self.context.save_context_state()
|
||||
|
||||
def _log_energy_change(self, action: str, reason: str = ""):
|
||||
"""
|
||||
|
||||
@@ -2,7 +2,6 @@ from typing import List, Optional, TYPE_CHECKING
|
||||
import time
|
||||
from src.chat.message_receive.chat_stream import ChatStream, get_chat_manager
|
||||
from src.common.logger import get_logger
|
||||
from src.manager.local_store_manager import local_storage
|
||||
from src.person_info.relationship_builder_manager import RelationshipBuilder
|
||||
from src.chat.express.expression_learner import ExpressionLearner
|
||||
from src.plugin_system.base.component_types import ChatMode
|
||||
@@ -43,10 +42,10 @@ class HfcContext:
|
||||
self.expression_learner: Optional[ExpressionLearner] = None
|
||||
|
||||
self.loop_mode = ChatMode.NORMAL
|
||||
self.energy_value = 5.0
|
||||
self.sleep_pressure = 0.0
|
||||
self.was_sleeping = False # 用于检测睡眠状态的切换
|
||||
|
||||
self.energy_value = self.chat_stream.energy_value
|
||||
self.sleep_pressure = self.chat_stream.sleep_pressure
|
||||
self.was_sleeping = False # 用于检测睡眠状态的切换
|
||||
|
||||
self.last_message_time = time.time()
|
||||
self.last_read_time = time.time() - 10
|
||||
|
||||
@@ -62,30 +61,8 @@ class HfcContext:
|
||||
self.wakeup_manager: Optional["WakeUpManager"] = None
|
||||
self.energy_manager: Optional["EnergyManager"] = None
|
||||
|
||||
self._load_context_state()
|
||||
|
||||
def _get_storage_key(self) -> str:
|
||||
"""获取当前聊天流的本地存储键"""
|
||||
return f"hfc_context_state_{self.stream_id}"
|
||||
|
||||
def _load_context_state(self):
|
||||
"""从本地存储加载状态"""
|
||||
state = local_storage[self._get_storage_key()]
|
||||
if state and isinstance(state, dict):
|
||||
self.energy_value = state.get("energy_value", 5.0)
|
||||
self.sleep_pressure = state.get("sleep_pressure", 0.0)
|
||||
logger = get_logger("hfc_context")
|
||||
logger.info(f"{self.log_prefix} 成功从本地存储加载HFC上下文状态: {state}")
|
||||
else:
|
||||
logger = get_logger("hfc_context")
|
||||
logger.info(f"{self.log_prefix} 未找到本地HFC上下文状态,将使用默认值初始化。")
|
||||
|
||||
def save_context_state(self):
|
||||
"""将当前状态保存到本地存储"""
|
||||
state = {
|
||||
"energy_value": self.energy_value,
|
||||
"sleep_pressure": self.sleep_pressure,
|
||||
}
|
||||
local_storage[self._get_storage_key()] = state
|
||||
logger = get_logger("hfc_context")
|
||||
logger.debug(f"{self.log_prefix} 已将HFC上下文状态保存到本地存储: {state}")
|
||||
"""将当前状态保存到聊天流"""
|
||||
if self.chat_stream:
|
||||
self.chat_stream.energy_value = self.energy_value
|
||||
self.chat_stream.sleep_pressure = self.sleep_pressure
|
||||
@@ -273,9 +273,21 @@ class ProactiveThinker:
|
||||
|
||||
# 如果决策不是 do_nothing,则执行
|
||||
if action_result and action_result.get("action_type") != "do_nothing":
|
||||
logger.info(
|
||||
f"{self.context.log_prefix} 主动思考决策: {action_result.get('action_type')}, 原因: {action_result.get('reasoning')}"
|
||||
)
|
||||
logger.info(f"{self.context.log_prefix} 主动思考决策: {action_result.get('action_type')}, 原因: {action_result.get('reasoning')}")
|
||||
# 在主动思考时,如果 target_message 为 None,则默认选取最新 message 作为 target_message
|
||||
if target_message is None and self.context.chat_stream and self.context.chat_stream.context:
|
||||
from src.chat.message_receive.message import MessageRecv
|
||||
latest_message = self.context.chat_stream.context.get_last_message()
|
||||
if isinstance(latest_message, MessageRecv):
|
||||
user_info = latest_message.message_info.user_info
|
||||
target_message = {
|
||||
"chat_info_platform": latest_message.message_info.platform,
|
||||
"user_platform": user_info.platform if user_info else None,
|
||||
"user_id": user_info.user_id if user_info else None,
|
||||
"processed_plain_text": latest_message.processed_plain_text,
|
||||
"is_mentioned": latest_message.is_mentioned,
|
||||
}
|
||||
|
||||
# 将决策结果交给 cycle_processor 的后续流程处理
|
||||
await self.cycle_processor.execute_plan(action_result, target_message)
|
||||
else:
|
||||
|
||||
@@ -79,6 +79,8 @@ class ChatStream:
|
||||
self.group_info = group_info
|
||||
self.create_time = data.get("create_time", time.time()) if data else time.time()
|
||||
self.last_active_time = data.get("last_active_time", self.create_time) if data else self.create_time
|
||||
self.energy_value = data.get("energy_value", 5.0) if data else 5.0
|
||||
self.sleep_pressure = data.get("sleep_pressure", 0.0) if data else 0.0
|
||||
self.saved = False
|
||||
self.context: ChatMessageContext = None # type: ignore # 用于存储该聊天的上下文信息
|
||||
|
||||
@@ -91,6 +93,8 @@ class ChatStream:
|
||||
"group_info": self.group_info.to_dict() if self.group_info else None,
|
||||
"create_time": self.create_time,
|
||||
"last_active_time": self.last_active_time,
|
||||
"energy_value": self.energy_value,
|
||||
"sleep_pressure": self.sleep_pressure,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -251,7 +255,7 @@ class ChatManager:
|
||||
"user_cardname": model_instance.user_cardname or "",
|
||||
}
|
||||
group_info_data = None
|
||||
if model_instance.group_id: # 假设 group_id 为空字符串表示没有群组信息
|
||||
if model_instance.group_id:
|
||||
group_info_data = {
|
||||
"platform": model_instance.group_platform,
|
||||
"group_id": model_instance.group_id,
|
||||
@@ -265,6 +269,8 @@ class ChatManager:
|
||||
"group_info": group_info_data,
|
||||
"create_time": model_instance.create_time,
|
||||
"last_active_time": model_instance.last_active_time,
|
||||
"energy_value": model_instance.energy_value,
|
||||
"sleep_pressure": model_instance.sleep_pressure,
|
||||
}
|
||||
stream = ChatStream.from_dict(data_for_from_dict)
|
||||
# 更新用户信息和群组信息
|
||||
@@ -348,6 +354,8 @@ class ChatManager:
|
||||
"group_platform": group_info_d["platform"] if group_info_d else "",
|
||||
"group_id": group_info_d["group_id"] if group_info_d else "",
|
||||
"group_name": group_info_d["group_name"] if group_info_d else "",
|
||||
"energy_value": s_data_dict.get("energy_value", 5.0),
|
||||
"sleep_pressure": s_data_dict.get("sleep_pressure", 0.0),
|
||||
}
|
||||
|
||||
# 根据数据库类型选择插入语句
|
||||
@@ -407,6 +415,8 @@ class ChatManager:
|
||||
"group_info": group_info_data,
|
||||
"create_time": model_instance.create_time,
|
||||
"last_active_time": model_instance.last_active_time,
|
||||
"energy_value": model_instance.energy_value,
|
||||
"sleep_pressure": model_instance.sleep_pressure,
|
||||
}
|
||||
loaded_streams_data.append(data_for_from_dict)
|
||||
session.commit()
|
||||
|
||||
@@ -62,11 +62,10 @@ def get_active_plans_for_month(month: str) -> List[MonthlyPlan]:
|
||||
"""
|
||||
with get_db_session() as session:
|
||||
try:
|
||||
plans = (
|
||||
session.query(MonthlyPlan)
|
||||
.filter(MonthlyPlan.target_month == month, MonthlyPlan.status == "active")
|
||||
.all()
|
||||
)
|
||||
plans = session.query(MonthlyPlan).filter(
|
||||
MonthlyPlan.target_month == month,
|
||||
MonthlyPlan.status == 'active'
|
||||
).order_by(MonthlyPlan.created_at.desc()).all()
|
||||
return plans
|
||||
except Exception as e:
|
||||
logger.error(f"查询 {month} 的有效月度计划时发生错误: {e}")
|
||||
|
||||
@@ -51,6 +51,8 @@ class ChatStreams(Base):
|
||||
user_id = Column(get_string_field(100), nullable=False, index=True)
|
||||
user_nickname = Column(Text, nullable=False)
|
||||
user_cardname = Column(Text, nullable=True)
|
||||
energy_value = Column(Float, nullable=True, default=5.0)
|
||||
sleep_pressure = Column(Float, nullable=True, default=0.0)
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_chatstreams_stream_id", "stream_id"),
|
||||
|
||||
@@ -220,8 +220,6 @@ MoFox_Bot(第三方修改版)
|
||||
|
||||
await async_memory_manager.initialize()
|
||||
logger.info("记忆管理器初始化成功")
|
||||
except ImportError:
|
||||
logger.warning("异步记忆优化方法不可用,将回退使用同步模式")
|
||||
except Exception as e:
|
||||
logger.error(f"记忆管理器初始化失败: {e}")
|
||||
else:
|
||||
|
||||
@@ -67,8 +67,8 @@ class EventManager:
|
||||
|
||||
event = BaseEvent(event_name, allowed_subscribers, allowed_triggers)
|
||||
self._events[event_name] = event
|
||||
logger.info(f"事件 {event_name} 注册成功")
|
||||
|
||||
logger.debug(f"事件 {event_name} 注册成功")
|
||||
|
||||
# 检查是否有缓存的订阅需要处理
|
||||
self._process_pending_subscriptions(event_name)
|
||||
|
||||
|
||||
@@ -53,9 +53,7 @@ class MaiZoneRefactoredPlugin(BasePlugin):
|
||||
"enable_reply": ConfigField(type=bool, default=True, description="完成后是否回复"),
|
||||
"ai_image_number": ConfigField(type=int, default=1, description="AI生成图片数量"),
|
||||
"image_number": ConfigField(type=int, default=1, description="本地配图数量(1-9张)"),
|
||||
"image_directory": ConfigField(
|
||||
type=str, default=str(Path(__file__).parent / "images"), description="图片存储目录"
|
||||
),
|
||||
"image_directory": ConfigField(type=str, default=(Path(__file__).parent / "images").as_posix(), description="图片存储目录")
|
||||
},
|
||||
"read": {
|
||||
"permission": ConfigField(type=list, default=[], description="阅读权限QQ号列表"),
|
||||
@@ -77,9 +75,7 @@ class MaiZoneRefactoredPlugin(BasePlugin):
|
||||
"forbidden_hours_end": ConfigField(type=int, default=6, description="禁止发送的结束小时(24小时制)"),
|
||||
},
|
||||
"cookie": {
|
||||
"http_fallback_host": ConfigField(
|
||||
type=str, default="172.20.130.55", description="备用Cookie获取服务的主机地址"
|
||||
),
|
||||
"http_fallback_host": ConfigField(type=str, default="127.0.0.1", description="备用Cookie获取服务的主机地址"),
|
||||
"http_fallback_port": ConfigField(type=int, default=9999, description="备用Cookie获取服务的端口"),
|
||||
"napcat_token": ConfigField(type=str, default="", description="Napcat服务的认证Token(可选)"),
|
||||
},
|
||||
|
||||
@@ -64,8 +64,9 @@ class MonthlyPlanManager:
|
||||
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)
|
||||
logger.info(f" {target_month} 没有任何有效的月度计划,将触发同步生成。")
|
||||
generation_successful = await self._generate_monthly_plans_logic(target_month)
|
||||
return generation_successful
|
||||
else:
|
||||
logger.info(f"{target_month} 已存在有效的月度计划。")
|
||||
plans = get_active_plans_for_month(target_month)
|
||||
@@ -74,8 +75,8 @@ class MonthlyPlanManager:
|
||||
max_plans = global_config.monthly_plan_system.max_plans_per_month
|
||||
if 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_plans_by_ids(delete_ids)
|
||||
# 重新获取计划列表
|
||||
@@ -86,10 +87,21 @@ class MonthlyPlanManager:
|
||||
logger.info(f"当前月度计划内容:\n{plan_texts}")
|
||||
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,则为当前月份。
|
||||
:return: 是否生成成功
|
||||
"""
|
||||
@@ -136,14 +148,6 @@ class MonthlyPlanManager:
|
||||
finally:
|
||||
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:
|
||||
"""获取上个月的月份字符串"""
|
||||
try:
|
||||
@@ -287,8 +291,6 @@ class MonthlyPlanManager:
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f" 归档 {target_month} 月度计划时发生错误: {e}")
|
||||
|
||||
|
||||
class MonthlyPlanGenerationTask(AsyncTask):
|
||||
"""每月初自动生成新月度计划的任务"""
|
||||
|
||||
@@ -324,8 +326,8 @@ class MonthlyPlanGenerationTask(AsyncTask):
|
||||
# 生成新月份的计划
|
||||
current_month = next_month.strftime("%Y-%m")
|
||||
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:
|
||||
logger.info(" 每月月度计划生成任务被取消。")
|
||||
break
|
||||
|
||||
@@ -159,42 +159,37 @@ class ScheduleManager:
|
||||
schedule_record = session.query(Schedule).filter(Schedule.date == today_str).first()
|
||||
if schedule_record:
|
||||
logger.info(f"从数据库加载今天的日程 ({today_str})。")
|
||||
|
||||
try:
|
||||
schedule_data = orjson.loads(str(schedule_record.schedule_data))
|
||||
|
||||
# 使用Pydantic验证日程数据
|
||||
if self._validate_schedule_with_pydantic(schedule_data):
|
||||
self.today_schedule = schedule_data
|
||||
schedule_str = f"已成功加载今天的日程 ({today_str}):\n"
|
||||
if self.today_schedule:
|
||||
for item in self.today_schedule:
|
||||
schedule_str += f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n"
|
||||
logger.info(schedule_str)
|
||||
else:
|
||||
logger.warning("数据库中的日程数据格式无效,将异步重新生成日程")
|
||||
await self.generate_and_save_schedule()
|
||||
except orjson.JSONDecodeError as e:
|
||||
logger.error(f"日程数据JSON解析失败: {e},将异步重新生成日程")
|
||||
await self.generate_and_save_schedule()
|
||||
schedule_data = orjson.loads(str(schedule_record.schedule_data))
|
||||
if self._validate_schedule_with_pydantic(schedule_data):
|
||||
self.today_schedule = schedule_data
|
||||
schedule_str = f"已成功加载今天的日程 ({today_str}):\n"
|
||||
if self.today_schedule:
|
||||
for item in self.today_schedule:
|
||||
schedule_str += f" - {item.get('time_range', '未知时间')}: {item.get('activity', '未知活动')}\n"
|
||||
logger.info(schedule_str)
|
||||
return # 成功加载,直接返回
|
||||
else:
|
||||
logger.warning("数据库中的日程数据格式无效,将重新生成日程")
|
||||
else:
|
||||
logger.info(f"数据库中未找到今天的日程 ({today_str}),将异步调用 LLM 生成。")
|
||||
await self.generate_and_save_schedule()
|
||||
logger.info(f"数据库中未找到今天的日程 ({today_str}),将调用 LLM 生成。")
|
||||
|
||||
# 仅在需要时生成
|
||||
await self.generate_and_save_schedule()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"加载或生成日程时出错: {e}")
|
||||
# 出错时也尝试异步生成
|
||||
logger.info("尝试异步生成日程作为备用方案...")
|
||||
logger.info("尝试生成日程作为备用方案...")
|
||||
await self.generate_and_save_schedule()
|
||||
|
||||
async def generate_and_save_schedule(self):
|
||||
"""启动异步日程生成任务,避免阻塞主程序"""
|
||||
"""将日程生成任务提交到后台执行"""
|
||||
if self.schedule_generation_running:
|
||||
logger.info("日程生成任务已在运行中,跳过重复启动")
|
||||
return
|
||||
|
||||
# 创建异步任务进行日程生成,不阻塞主程序
|
||||
asyncio.create_task(self._async_generate_and_save_schedule())
|
||||
logger.info("已启动异步日程生成任务")
|
||||
logger.info("检测到需要生成日程,已提交后台任务。")
|
||||
task = OnDemandScheduleGenerationTask(self)
|
||||
await async_task_manager.add_task(task)
|
||||
|
||||
async def _async_generate_and_save_schedule(self):
|
||||
"""异步生成并保存日程的内部方法"""
|
||||
@@ -234,12 +229,14 @@ class ScheduleManager:
|
||||
logger.info("可用的月度计划已耗尽或不足,触发后台补充生成...")
|
||||
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)
|
||||
|
||||
# 注意:这里不再等待生成结果,因此后续代码不会立即获得新计划。
|
||||
# 日程将基于当前可用的信息生成,新计划将在下一次日程生成时可用。
|
||||
logger.info("月度计划的后台生成任务已启动,本次日程将不包含新计划。")
|
||||
# 重新获取月度计划
|
||||
sampled_plans = get_smart_plans_for_daily_schedule(
|
||||
current_month_str, max_count=3, avoid_days=avoid_days
|
||||
)
|
||||
logger.info("月度计划补充生成完毕,继续日程生成任务。")
|
||||
|
||||
if sampled_plans:
|
||||
plan_texts = "\n".join([f"- {plan.plan_text}" for plan in sampled_plans])
|
||||
@@ -448,6 +445,20 @@ class ScheduleManager:
|
||||
return True
|
||||
|
||||
|
||||
class OnDemandScheduleGenerationTask(AsyncTask):
|
||||
"""按需生成日程的后台任务"""
|
||||
|
||||
def __init__(self, schedule_manager: "ScheduleManager"):
|
||||
task_name = f"OnDemandScheduleGenerationTask-{datetime.now().strftime('%Y%m%d%H%M%S')}"
|
||||
super().__init__(task_name=task_name)
|
||||
self.schedule_manager = schedule_manager
|
||||
|
||||
async def run(self):
|
||||
logger.info(f"后台任务 {self.task_name} 开始执行日程生成。")
|
||||
await self.schedule_manager._async_generate_and_save_schedule()
|
||||
logger.info(f"后台任务 {self.task_name} 完成。")
|
||||
|
||||
|
||||
class DailyScheduleGenerationTask(AsyncTask):
|
||||
"""每日零点自动生成新日程的任务"""
|
||||
|
||||
@@ -471,9 +482,9 @@ class DailyScheduleGenerationTask(AsyncTask):
|
||||
# 2. 等待直到零点
|
||||
await asyncio.sleep(sleep_seconds)
|
||||
|
||||
# 3. 执行异步日程生成
|
||||
logger.info("到达每日零点,开始异步生成新的一天日程...")
|
||||
await self.schedule_manager.generate_and_save_schedule()
|
||||
# 3. 执行日程生成
|
||||
logger.info("到达每日零点,开始生成新的一天日程...")
|
||||
await self.schedule_manager._async_generate_and_save_schedule()
|
||||
|
||||
except asyncio.CancelledError:
|
||||
logger.info("每日日程生成任务被取消。")
|
||||
|
||||
Reference in New Issue
Block a user