From b53206d0d151e1d0f4093c6558432942732e0919 Mon Sep 17 00:00:00 2001 From: liuwei Date: Wed, 29 Apr 2026 17:27:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=85=A8=E5=B1=80=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E4=BF=9D=E5=AD=98=E5=90=8E=E7=AB=8B=E5=8D=B3=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=88=B0=E8=BF=90=E8=A1=8C=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 Robot.apply_runtime_config 统一刷新邮件发送器、管理员列表与 LLM 运行时缓存\n- 新增 LLMRegistry.invalidate_cache 主动清理目录与 legacy 配置缓存\n- 后台保存全局配置与 LLM 目录后立即应用运行时配置,减少重启依赖 --- admin/dashboard/blueprints/system.py | 14 ++++++++++-- robot.py | 33 ++++++++++++++++++++++++++++ utils/ai/llm_registry.py | 15 +++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/admin/dashboard/blueprints/system.py b/admin/dashboard/blueprints/system.py index 766fad9..948a5c6 100644 --- a/admin/dashboard/blueprints/system.py +++ b/admin/dashboard/blueprints/system.py @@ -13,6 +13,7 @@ import json import yaml import toml from utils.markdown_to_image import get_md2img_health_snapshot, warmup_md2img_browser_sync +from utils.ai.llm_registry import LLMRegistry # 创建系统信息蓝图 system_bp = Blueprint('system', __name__) @@ -479,8 +480,13 @@ def update_system_config(): if getattr(server, "robot", None) and getattr(server.robot, "config", None): server.robot.config.reload() + # 保存 YAML 后立刻把运行时依赖对象同步一遍,避免必须重启进程才能读到新值。 + server.robot.apply_runtime_config(reload_catalog=True) + else: + # 即便当前没有可用 robot 实例,也尽量把 LLM 路由缓存清掉,避免后续请求短时间内读旧值。 + LLMRegistry.invalidate_cache() - return jsonify({"success": True, "message": "全局配置已保存"}) + return jsonify({"success": True, "message": "全局配置已保存并应用到运行时"}) except Exception as e: logger.error(f"保存系统配置失败: {e}") return jsonify({"success": False, "message": str(e)}), 500 @@ -606,8 +612,12 @@ def update_system_llm_config(): if getattr(server, "robot", None) and getattr(server.robot, "config", None): server.robot.config.reload() + # LLM 目录保存到 MySQL 后,需要主动失效运行时缓存,保证插件下一次调用直接走新目录。 + server.robot.apply_runtime_config(reload_catalog=True) + else: + LLMRegistry.invalidate_cache() - return jsonify({"success": True, "message": "全局 LLM 配置已保存"}) + return jsonify({"success": True, "message": "全局 LLM 配置已保存并应用到运行时"}) except Exception as e: logger.error(f"保存全局 LLM 配置失败: {e}") return jsonify({"success": False, "message": str(e)}), 500 diff --git a/robot.py b/robot.py index 4ed5d59..eb6d404 100644 --- a/robot.py +++ b/robot.py @@ -30,6 +30,7 @@ from utils.robot_cmd.robot_command import GroupBotManager, Feature, PermissionSt from utils.wechat.contact_manager import ContactManager from utils.wechat.member_monitor import ChatroomMemberMonitor from utils.wechat.message_to_db import MessageStorage +from utils.ai.llm_registry import LLMRegistry from wechat_ipad import WechatAPIClient from wechat_ipad.models.message import WxMessage, MessageType @@ -144,6 +145,38 @@ class Robot: GroupBotManager.admin_list = self.config.wx_config.get("admin", []) self.recent_msg_ids = deque(maxlen=20) + def apply_runtime_config(self, reload_catalog: bool = False) -> None: + """把最新全局配置应用到当前运行中的关键对象。 + + 说明: + 1. `self.config.reload()` 只会刷新 Config 实例中的字段,不会自动更新启动时已构造的依赖对象; + 2. 这里集中处理“保存配置后希望立刻生效”的轻量刷新动作,避免为大多数改动走整进程重启; + 3. 该方法刻意不去重建 DB 连接、微信登录态、插件实例,尽量把影响范围控制在可热刷的配置项。 + """ + # 邮件发送器在初始化时会拷贝 SMTP 参数,因此这里需要按最新配置重建一份实例。 + self.email_sender = EmailSender( + smtp_server=self.config.email.get("smtp_server", "smtp.163.com"), + smtp_port=self.config.email.get("smtp_port", 465), + sender_email=self.config.email.get("sender_email", "bovine_liu@163.com"), + sender_password=self.config.email.get("sender_password", "LTS9BhmX9XhS36QS") + ) + + # 管理员列表走 GroupBotManager 的类级缓存;只 reload Config 不会自动回写到这里。 + GroupBotManager.admin_list = self.config.wx_config.get("admin", []) + + # system_context 中保存的是 config 对象引用,reload 后插件读取到的是最新字段。 + # 但 LLMRegistry 自己还有一层短 TTL 缓存,因此保存全局 LLM 配置后需要显式清掉。 + if reload_catalog: + self.llm_catalog_db.bootstrap_from_legacy_llm(self.config.llm) + LLMRegistry.invalidate_cache() + + self.LOG.info( + "运行时配置已应用: " + f"admin_count={len(GroupBotManager.admin_list)}, " + f"email_sender={'ready' if self.email_sender else 'missing'}, " + f"llm_cache_reloaded={reload_catalog}" + ) + def _cleanup_migrated_system_jobs(self): """清理已经迁移到插件层的历史系统任务键。""" migrated_keys = [ diff --git a/utils/ai/llm_registry.py b/utils/ai/llm_registry.py index 55dd223..e59e465 100644 --- a/utils/ai/llm_registry.py +++ b/utils/ai/llm_registry.py @@ -24,6 +24,21 @@ class LLMRegistry: "legacy_llm": {}, } + @classmethod + def invalidate_cache(cls) -> None: + """主动清空运行时缓存。 + + 说明: + 1. 后台修改全局 YAML 或 MySQL 中的 LLM 目录后,旧缓存可能还在 3 秒有效期内; + 2. 对于“保存后立刻生效”的后台体验,主动失效比等待 TTL 自然过期更直接; + 3. 这里只清缓存,不做任何 IO,下一次 resolve/get_catalog 时会自动重新装载最新配置。 + """ + cls._cache = { + "cache_until": 0.0, + "catalog": {}, + "legacy_llm": {}, + } + @classmethod def get_root_config_path(cls) -> Path: return Path(__file__).resolve().parents[2] / "config.yaml"