From f6a48031a07138c59ae338bb0facdbbae70a8bc9 Mon Sep 17 00:00:00 2001 From: jinye_huang Date: Fri, 11 Jul 2025 15:29:30 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E8=8E=B7=E5=8F=96=E6=95=B0=E6=8D=AE=E7=9A=84=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/__pycache__/dependencies.cpython-312.pyc | Bin 1594 -> 2022 bytes api/dependencies.py | 17 +- .../prompt_builder.cpython-312.pyc | Bin 7271 -> 9905 bytes .../prompt_service.cpython-312.pyc | Bin 28523 -> 29264 bytes api/services/prompt_builder.py | 92 ++++++- api/services/prompt_service.py | 228 +++++++++--------- config/database.json | 8 + .../__pycache__/manager.cpython-312.pyc | Bin 9863 -> 12412 bytes .../config/__pycache__/models.cpython-312.pyc | Bin 9415 -> 9601 bytes core/config/manager.py | 143 ++++++++--- core/config/models.py | 2 + 11 files changed, 330 insertions(+), 160 deletions(-) create mode 100644 config/database.json diff --git a/api/__pycache__/dependencies.cpython-312.pyc b/api/__pycache__/dependencies.cpython-312.pyc index 17cdd93734d4cdfcaf2e847b028deca41514eb3b..bd44757265d07ee7b70c2a3b5e563ff9522abcab 100644 GIT binary patch literal 2022 zcmb_d-D@0G6u+}OyEFT}O*RQh+S4`~=3@o$Om4FCl;9!Wr%c*uM>6nY>OUk|Gi#xl|_OO1T)sZq=P(Q*6eQ z@>pwD^=7yfXJL=ZXM8CiV6W(xU9zVe_K^b;Hv%*8g23Y%JifsT0ngXq`7ECQDoKT< zfb5xZNx`dZDgu}T90D8#>;oJIe5Bw_MDks*wSV;0@vXZnyFV>$|5&ase!X43RbN^5 z-v<#%FeX2hH56SF)r8Azo0SbAt!p#NtdJ2kaaN{g=z_JLu$N%KjgF7b%9;T%G?h29 zd7~klG{Y^nyr>AUiDsB#-O_Y+P2=ItU;$uo_x}a5Obo^mtTuqi7%qo5=W$ST#w2{_ zUTx?=fLX}{iEu9v4E8*k^EGuH~e`vP^}HF3A+V#xzQzAsb3YHd!&N2vUDZWNn4x+`kQ6GX4V@Nf7$t5^f7|492A#pvD2FE4 zG-e>JQ#onhk{&^hzgbQcx`|NCLxQ#U@zT5|D3Yz|gIm~^IlRLO82==PsID7B(^R}{ z-PV9Qxw@a6(>=BpcXy85lyT|6Xk*LGu7xz#qnCCvs9=?PQE*~4@VM= zzLBTep{mDoG zhF$f)u369Y;Rlt*K^ZHFoT@w!H>WrlUL>Q zbP!bVbj0CA*1RcC%%&ixgTCv3S&$ z1uKXrPtv0YLC>DN^q^1z1;P4rj7HFt_}(U4(1YN>d-J`YnQz{E^J(m5zVoI~$P!qu z@2=h25QKa{BVB9+1`Pna#3lx@sm(mAV~Z-=rGy1paT z!e2GJyGe@sKnSaA}g@TX;hP`nguY>L3PPd zuDmbGNm3mg$LpUA5nlb}{)>+f9xh!ydiC`4_C4U60;iloRUtP<4z_IalO)h+<>g zE1>$EQuv)UACkGgN+wQriwC)}u6{5+)h&J#Cup^Qj?v2Y@_@kBhZE(= R?Is$NXjC38f5$*l+i!)I(YF8q diff --git a/api/dependencies.py b/api/dependencies.py index 7d14fdf..854c355 100644 --- a/api/dependencies.py +++ b/api/dependencies.py @@ -5,22 +5,23 @@ API依赖注入模块 """ +from typing import Optional from core.config import get_config_manager, ConfigManager from core.ai import AIAgent from utils.file_io import OutputManager # 全局依赖 -config_manager = None -ai_agent = None -output_manager = None +config_manager: Optional[ConfigManager] = None +ai_agent: Optional[AIAgent] = None +output_manager: Optional[OutputManager] = None def initialize_dependencies(): """初始化全局依赖""" global config_manager, ai_agent, output_manager - # 初始化配置 + # 初始化配置 - 使用服务器模式 config_manager = get_config_manager() - config_manager.load_from_directory("config") + config_manager.load_from_directory("config", server_mode=True) # 初始化输出管理器 from datetime import datetime @@ -34,12 +35,18 @@ def initialize_dependencies(): def get_config() -> ConfigManager: """获取配置管理器""" + if config_manager is None: + raise RuntimeError("配置管理器未初始化") return config_manager def get_ai_agent() -> AIAgent: """获取AI代理""" + if ai_agent is None: + raise RuntimeError("AI代理未初始化") return ai_agent def get_output_manager() -> OutputManager: """获取输出管理器""" + if output_manager is None: + raise RuntimeError("输出管理器未初始化") return output_manager \ No newline at end of file diff --git a/api/services/__pycache__/prompt_builder.cpython-312.pyc b/api/services/__pycache__/prompt_builder.cpython-312.pyc index a8788c40f8219f8cbe816360a80f7d4241c60446..901ba5e6f58a707d66327a0b0f7f47af6d88657a 100644 GIT binary patch literal 9905 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 delta 2609 zcmc&#Uu+!38Q;0xy}SK?XP>?2Tc6MNB{t{87pK8Zh|?0s#Enw0prDi(iW_^^#?IZ@ zn%$FdyQf4bs`3B`Ymi!%AoZb@92r%ic|qcV3m_C!sk*DF4i;!pTdAT?PF<<$Lq)&Y zy}QIw-#gNN^Ud%3GduJBnYSMM+cEXGiXt*FKD)g3yqDTgljyaL;i+d4Vm)O9r%^AZG!2~rQ86*<_Pg>Uy9+0PDeE?VIU;ybg*2?Oen*0Ee> zj%xb@4#<+T>@UxH(!y%Vu}aR|l2w|?@mK{p)_{28lI6^Sba8nJ3lvaWssWf|J$q-sSYs_CfE;Dx4E( ztz6dSTGvwHNnFaJFxklHWG0i4!mZq>L_ne|tQl(SMnUU<6m4q=RD*mj$>hV#0ofJX z0?9Y!?=hAA4NA#Q&XuYI@*J~&q;=4x*2=CE=Zp-!@?j!CpyJQm`JHS!nrl~^1n0Kqh4aS908 z&?e`zR7o?f;)Rkqx42xeN*9V2-bYbsb$-QKaOQkuKSks2j28+#;Ace5?1K1h=!(>K^ikJZy-jr4RqJza~YJ~1*4 zBVRZ24dYbZICb+@Y!V@z{IvUEXo!WNkBvF)*tVm>x~whTjV@ezv$|hIE=vaFE{!$s z#+&moodtx%bwg>#`$gnPT8Ln)YR%vQY|Qbax@ue4aH0dIvgEFMR8jj{6_ALsHS2@SK{P}{sj3f83#iR z4DTefa~Q?R8$Dksp5T`f zt?9Bsu0{Grz0LsH6DHgTzvION$vqJxKS`ZJgXE*sFzO+9QcoW_0ebjbKyqqO?H$yR zGnW?}Pds;_P_|BEyoxa`e@v4O^6h0~6dfY(7^&$o3LFNKO9c7XcIU~#-pwxa4XSvA zk_Ul!VcS_RST;^mBuk9mo?)7mLBjF&+Nxtivw#WH)Wi=_GDXR8@=R|Ub&>VnljqL? zVE?I=Z^_<Csw# zs`|a{^cg=fha1L$x^bX(@ZoC5w(-c#uX?8?x25@e!3&MHCo(G@r13aJ){RMWE7na?-4Bq*<44J_(*mhvzpv_GJ(EM2{$@@if6gAc zBms-NDWR{E$J!3Y^!_k?gYkrqEteepI9)yiS(jv4s+rI*Gh53{l+~GvN zjDAtsVdz{f?)vkc-il9mY1rmKi8f*Uz`0wZEn6cJ70flVVBBw z7SRM4Jh{Wb^meB|RlBP8?WyVuw=%mF-T4tZ2rAs>lQ*)PPhVGe7+6*l)rH$fs?Xo* z^OyIz{wGxljsSnp)54x3*Y-aa_mr};T(I-OGhkzpa6<#9>HT;DJVTGJE-hIYKTFr) zLa^|UKM%;=EAgTyitx%5tZ(CsAfO3sKTMW}(jzCnU>^8_8UIp9UDX>xsxG8{c5GYN ScRBp8@Wl`s`3FO(@94iigl-@J diff --git a/api/services/__pycache__/prompt_service.cpython-312.pyc b/api/services/__pycache__/prompt_service.cpython-312.pyc index cf75200dfff59a755f3a167c7a2d4c98bee74f87..10051f942adf108822a3182bb7e841b94e18c666 100644 GIT binary patch delta 7909 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 delta 5822 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 GenerateContentConfig: + """确保内容生成配置已加载""" + # 按需加载内容生成配置 + if not self.config_manager.load_task_config('content_gen'): + logger.warning("未找到内容生成配置,将使用默认配置") + + return self.config_manager.get_config('content_gen', GenerateContentConfig) + + def _ensure_topic_config(self) -> GenerateTopicConfig: + """确保选题生成配置已加载""" + # 按需加载选题生成配置 + if not self.config_manager.load_task_config('topic_gen'): + logger.warning("未找到选题生成配置,将使用默认配置") + + return self.config_manager.get_config('topic_gen', GenerateTopicConfig) + + def _ensure_poster_config(self) -> PosterConfig: + """确保海报生成配置已加载""" + # 按需加载海报生成配置 + if not self.config_manager.load_task_config('poster_gen'): + logger.warning("未找到海报生成配置,将使用默认配置") + + return self.config_manager.get_config('poster_gen', PosterConfig) def build_content_prompt(self, topic: Dict[str, Any], step: str = "content") -> Tuple[str, str]: """ @@ -43,9 +66,12 @@ class PromptBuilderService: Returns: 系统提示词和用户提示词的元组 """ + # 获取内容生成配置 + content_config = self._ensure_content_config() + # 加载系统提示词和用户提示词模板 - system_prompt_path = self.content_config.content_system_prompt - user_prompt_path = self.content_config.content_user_prompt + system_prompt_path = content_config.content_system_prompt + user_prompt_path = content_config.content_user_prompt # 创建提示词模板 template = PromptTemplate(system_prompt_path, user_prompt_path) @@ -83,6 +109,47 @@ class PromptBuilderService: return system_prompt, user_prompt + def build_poster_prompt(self, topic: Dict[str, Any], content: Dict[str, Any]) -> Tuple[str, str]: + """ + 构建海报生成提示词 + + Args: + topic: 选题信息 + content: 生成的内容 + + Returns: + 系统提示词和用户提示词的元组 + """ + # 获取海报生成配置 + poster_config = self._ensure_poster_config() + + # 从配置中获取海报提示词模板路径 + system_prompt_path = poster_config.poster_system_prompt + user_prompt_path = poster_config.poster_user_prompt + + if not system_prompt_path or not user_prompt_path: + raise ValueError("海报提示词模板路径不完整") + + # 创建提示词模板 + template = PromptTemplate(system_prompt_path, user_prompt_path) + + # 获取景区信息 + object_name = topic.get("object", "") + object_content = self.prompt_service.get_scenic_spot_info(object_name) + + # 构建系统提示词 + system_prompt = template.get_system_prompt() + + # 构建用户提示词 + user_prompt = template.build_user_prompt( + content=content.get("content", ""), + title=content.get("title", ""), + object_name=object_name, + object_content=object_content + ) + + return system_prompt, user_prompt + def build_topic_prompt(self, num_topics: int, month: str) -> Tuple[str, str]: """ 构建选题生成提示词 @@ -94,13 +161,11 @@ class PromptBuilderService: Returns: 系统提示词和用户提示词的元组 """ - # 从配置中获取选题提示词模板路径 - topic_config = self.config_manager.get_config('topic_gen', dict) - if not topic_config: - raise ValueError("未找到选题生成配置") + # 获取选题生成配置 + topic_config = self._ensure_topic_config() - system_prompt_path = topic_config.get("topic_system_prompt", "") - user_prompt_path = topic_config.get("topic_user_prompt", "") + system_prompt_path = topic_config.topic_system_prompt + user_prompt_path = topic_config.topic_user_prompt if not system_prompt_path or not user_prompt_path: raise ValueError("选题提示词模板路径不完整") @@ -155,9 +220,12 @@ class PromptBuilderService: Returns: 系统提示词和用户提示词的元组 """ + # 获取内容生成配置 + content_config = self._ensure_content_config() + # 从配置中获取审核提示词模板路径 - system_prompt_path = self.content_config.judger_system_prompt - user_prompt_path = self.content_config.judger_user_prompt + system_prompt_path = content_config.judger_system_prompt + user_prompt_path = content_config.judger_user_prompt # 创建提示词模板 template = PromptTemplate(system_prompt_path, user_prompt_path) diff --git a/api/services/prompt_service.py b/api/services/prompt_service.py index 076fd57..de8fe01 100644 --- a/api/services/prompt_service.py +++ b/api/services/prompt_service.py @@ -10,6 +10,9 @@ import logging import json import os import re +import sys +import traceback +import time from typing import Dict, Any, Optional, List, cast from pathlib import Path import mysql.connector @@ -33,121 +36,130 @@ class PromptService: config_manager: 配置管理器 """ self.config_manager = config_manager - self.resource_config: ResourceConfig = config_manager.get_config('resource', ResourceConfig) + + # 按需加载resource配置 + if 'resource' not in self.config_manager._loaded_configs: + self.config_manager.load_task_config('resource') + + self.resource_config = self.config_manager.get_config('resource', ResourceConfig) + + # ResourceLoader是静态类,不需要实例化 + self.resource_dirs = self.resource_config.resource_dirs # 初始化数据库连接池 - self._init_db_pool() + self.db_pool = self._init_db_pool() - def _init_db_pool(self): - """初始化数据库连接池""" - try: - # 尝试直接从配置文件加载数据库配置 - config_dir = Path("config") - db_config_path = config_dir / "database.json" - - if not db_config_path.exists(): - logger.warning(f"数据库配置文件不存在: {db_config_path}") - self.db_pool = None - return - - # 加载配置文件 - with open(db_config_path, 'r', encoding='utf-8') as f: - db_config = json.load(f) - - # 处理环境变量 - processed_config = {} - for key, value in db_config.items(): - if isinstance(value, str) and "${" in value: - # 匹配 ${ENV_VAR:-default} 格式 - pattern = r'\${([^:-]+)(?::-([^}]+))?}' - match = re.match(pattern, value) - if match: - env_var, default = match.groups() - processed_value = os.environ.get(env_var, default) - # 尝试转换为数字 - if key == "port": - try: - processed_value = int(processed_value) - except (ValueError, TypeError): - processed_value = 3306 - processed_config[key] = processed_value - else: - processed_config[key] = value - - # 创建连接池 - self.db_pool = pooling.MySQLConnectionPool( - pool_name="prompt_pool", - pool_size=5, - host=processed_config.get("host", "localhost"), - user=processed_config.get("user", "root"), - password=processed_config.get("password", ""), - database=processed_config.get("database", "travel_content"), - port=processed_config.get("port", 3306), - charset=processed_config.get("charset", "utf8mb4") - ) - logger.info(f"数据库连接池初始化成功,连接到 {processed_config.get('host')}:{processed_config.get('port')}") - except Exception as e: - logger.error(f"初始化数据库连接池失败: {e}") - self.db_pool = None + # 创建必要的目录结构 + self._create_resource_directories() - def get_style_content(self, style_name: str) -> str: - """ - 获取内容风格提示词 + def _create_resource_directories(self): + """创建必要的资源目录""" + try: + dirs_to_create = ['styles', 'attractions', 'products', 'audiences'] + for dir_name in dirs_to_create: + dir_path = Path(dir_name) + if not dir_path.exists(): + dir_path.mkdir(parents=True, exist_ok=True) + logger.info(f"创建目录: {dir_path}") + except Exception as e: + logger.error(f"创建资源目录失败: {e}") + + def _process_env_vars(self, config: Dict[str, Any]) -> Dict[str, Any]: + """处理配置中的环境变量""" + processed = {} + for key, value in config.items(): + if isinstance(value, str) and "${" in value: + # 匹配 ${ENV_VAR:-default} 格式 + pattern = r'\${([^:-]+)(?::-([^}]+))?}' + match = re.match(pattern, value) + if match: + env_var, default = match.groups() + processed_value = os.environ.get(env_var, default or "") + # 尝试转换为数字 + if key == "port": + try: + processed_value = int(processed_value) + except (ValueError, TypeError): + processed_value = 3306 + processed[key] = processed_value + else: + processed[key] = value + else: + processed[key] = value + return processed + + def _init_db_pool(self): + """初始化数据库连接池,尝试多种连接方式""" + # 获取数据库配置 + raw_db_config = self.config_manager.get_raw_config('database') - Args: - style_name: 风格名称 - - Returns: - 风格提示词内容 - """ - # 优先从数据库获取 + # 处理环境变量 + db_config = self._process_env_vars(raw_db_config) + + # 连接尝试配置 + connection_attempts = [ + {"desc": "使用配置文件中的设置", "config": db_config}, + {"desc": "使用明确的密码", "config": {**db_config, "password": "password"}}, + {"desc": "使用空密码", "config": {**db_config, "password": ""}}, + {"desc": "使用auth_plugin", "config": {**db_config, "auth_plugin": "mysql_native_password"}} + ] + + # 尝试不同的连接方式 + for attempt in connection_attempts: + try: + # 打印连接信息(不包含密码) + connection_info = {k: v for k, v in attempt["config"].items() if k != 'password'} + logger.info(f"尝试连接数据库 ({attempt['desc']}): {connection_info}") + + # 创建连接池 + pool = pooling.MySQLConnectionPool( + pool_name=f"prompt_service_pool_{int(time.time())}", + pool_size=5, + **attempt["config"] + ) + + # 测试连接 + with pool.get_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT 1") + cursor.fetchall() + + logger.info(f"数据库连接池初始化成功 ({attempt['desc']})") + return pool + except Exception as e: + error_details = traceback.format_exc() + logger.error(f"数据库连接尝试 ({attempt['desc']}) 失败: {e}\n{error_details}") + + logger.warning("所有数据库连接尝试均失败,将使用文件系统作为数据源") + return None + + def get_style_content(self, style_name: str) -> str: + """获取风格提示词内容""" + # 尝试从数据库获取 if self.db_pool: try: - conn = self.db_pool.get_connection() - cursor = conn.cursor(dictionary=True) - cursor.execute( - "SELECT description FROM contentStyle WHERE styleName = %s", - (style_name,) - ) - result = cursor.fetchone() - cursor.close() - conn.close() - - if result: - logger.info(f"从数据库获取风格提示词: {style_name}") - return result["description"] + with self.db_pool.get_connection() as conn: + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT description FROM contentStyle WHERE styleName = %s AND isDelete = 0", + (style_name,) + ) + result = cursor.fetchone() + if result: + return result['description'] except Exception as e: logger.error(f"从数据库获取风格提示词失败: {e}") - # 回退到文件系统 - try: - style_paths = self.resource_config.style.paths - for path_str in style_paths: - try: - if style_name in path_str: - full_path = self._get_full_path(path_str) - if full_path.exists(): - logger.info(f"从文件系统获取风格提示词: {style_name}") - return full_path.read_text('utf-8') - except Exception as e: - logger.error(f"读取风格文件失败 {path_str}: {e}") - - # 如果没有精确匹配,尝试模糊匹配 - for path_str in style_paths: - try: - full_path = self._get_full_path(path_str) - if full_path.exists() and full_path.is_file(): - content = full_path.read_text('utf-8') - if style_name.lower() in full_path.stem.lower(): - logger.info(f"通过模糊匹配找到风格提示词: {style_name} -> {full_path.name}") - return content - except Exception as e: - logger.error(f"读取风格文件失败 {path_str}: {e}") - except Exception as e: - logger.error(f"获取风格提示词失败: {e}") - - logger.warning(f"未找到风格提示词: {style_name},将使用默认值") - return "通用风格" + # 从文件系统获取 + logger.info(f"从文件系统获取风格提示词: {style_name}") + for dir_path in self.resource_dirs: + style_file = ResourceLoader.find_file(os.path.join(dir_path, "styles"), style_name) + if style_file: + content = ResourceLoader.load_text_file(style_file) + if content: + return content + + return f"未找到风格 '{style_name}' 的提示词" def get_audience_content(self, audience_name: str) -> str: """ @@ -224,7 +236,7 @@ class PromptService: conn = self.db_pool.get_connection() cursor = conn.cursor(dictionary=True) cursor.execute( - "SELECT description FROM scenicSpot WHERE spotName = %s", + "SELECT description FROM scenicSpot WHERE name = %s", (spot_name,) ) result = cursor.fetchone() @@ -271,7 +283,7 @@ class PromptService: conn = self.db_pool.get_connection() cursor = conn.cursor(dictionary=True) cursor.execute( - "SELECT description FROM product WHERE productName = %s", + "SELECT description FROM product WHERE name = %s", (product_name,) ) result = cursor.fetchone() @@ -453,7 +465,7 @@ class PromptService: try: conn = self.db_pool.get_connection() cursor = conn.cursor(dictionary=True) - cursor.execute("SELECT spotName as name, description FROM scenicSpot") + cursor.execute("SELECT name as name, description FROM scenicSpot") results = cursor.fetchall() cursor.close() conn.close() diff --git a/config/database.json b/config/database.json new file mode 100644 index 0000000..67f7c88 --- /dev/null +++ b/config/database.json @@ -0,0 +1,8 @@ +{ + "host": "localhost", + "user": "root", + "password": "password", + "database": "travel_content", + "port": 3306, + "charset": "utf8mb4" +} \ No newline at end of file diff --git a/core/config/__pycache__/manager.cpython-312.pyc b/core/config/__pycache__/manager.cpython-312.pyc index f76d67958060dc0b271b9bc8069c1c075f280971..d913be138dc74d6a6a0570bef009f987aa534e09 100644 GIT binary patch delta 5808 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 delta 3708 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 GYz6>6&|^OU delta 175 zcmZqlKJLkPnwOW00SKfk@-w!IZR9&F$5=M`l3Y1UCATKe=1locMrJ=vzRB-JL?`$2 zu}I(yi{vyD}s5HUrD+7=Mn*{(i C4KX7C diff --git a/core/config/manager.py b/core/config/manager.py index f73a7e0..463554f 100644 --- a/core/config/manager.py +++ b/core/config/manager.py @@ -9,7 +9,7 @@ import json import os import logging from pathlib import Path -from typing import Dict, Type, TypeVar, Optional, Any, cast +from typing import Dict, Type, TypeVar, Optional, Any, cast, List, Set from core.config.models import ( BaseConfig, AIModelConfig, SystemConfig, GenerateTopicConfig, ResourceConfig, @@ -27,23 +27,30 @@ class ConfigManager: 负责加载、管理和访问所有配置 """ + # 服务端必要的全局配置 + SERVER_CONFIGS = {'system', 'ai_model', 'database'} + + # 单次生成任务的配置 + TASK_CONFIGS = {'topic_gen', 'content_gen', 'poster_gen', 'resource'} + def __init__(self): self._configs: Dict[str, BaseConfig] = {} + self._raw_configs: Dict[str, Dict[str, Any]] = {} # 存储原始配置数据 self.config_dir: Optional[Path] = None self.config_objects = { 'ai_model': AIModelConfig(), 'system': SystemConfig(), - 'topic_gen': GenerateTopicConfig(), - 'content_gen': GenerateContentConfig(), 'resource': ResourceConfig() } + self._loaded_configs: Set[str] = set() - def load_from_directory(self, config_dir: str): + def load_from_directory(self, config_dir: str, server_mode: bool = False): """ 从目录加载配置 Args: config_dir: 配置文件目录 + server_mode: 是否为服务器模式,如果是则只加载必要的全局配置 """ self.config_dir = Path(config_dir) if not self.config_dir.is_dir(): @@ -54,15 +61,17 @@ class ConfigManager: self._register_configs() # 动态加载目录中的所有.json文件 - self._load_all_configs_from_dir() + self._load_all_configs_from_dir(server_mode) def _register_configs(self): """注册所有配置""" self.register_config('ai_model', AIModelConfig) + self.register_config('system', SystemConfig) + self.register_config('resource', ResourceConfig) + + # 这些配置在服务器模式下不会自动加载,但仍然需要注册类型 self.register_config('poster', PosterConfig) self.register_config('content', ContentConfig) - self.register_config('resource', ResourceConfig) - self.register_config('system', SystemConfig) self.register_config('topic_gen', GenerateTopicConfig) self.register_config('content_gen', GenerateContentConfig) @@ -113,49 +122,106 @@ class ConfigManager: return cast(T, config) - def _load_all_configs_from_dir(self): - """动态加载目录中的所有.json文件""" - try: - # 1. 优先加载旧的主配置文件以实现向后兼容 - main_config_path = self.config_dir / 'poster_gen_config.json' - if main_config_path.exists(): - self._load_main_config(main_config_path) - else: - logger.warning(f"旧的主配置文件不存在: {main_config_path}") + def get_raw_config(self, name: str) -> Dict[str, Any]: + """ + 获取原始配置数据 + + Args: + name: 配置名称 - # 2. 遍历并加载目录中所有其他的 .json 文件 + Returns: + 原始配置数据字典 + """ + if name in self._raw_configs: + return self._raw_configs[name] + + # 如果没有原始配置,但有对象配置,则转换为字典 + if name in self._configs: + return self._configs[name].to_dict() + + # 尝试从文件加载 + if self.config_dir: + config_path = self.config_dir / f"{name}.json" + if config_path.exists(): + try: + with open(config_path, 'r', encoding='utf-8') as f: + raw_config = json.load(f) + self._raw_configs[name] = raw_config + return raw_config + except Exception as e: + logger.error(f"加载原始配置 '{name}' 失败: {e}") + + # 返回空字典 + return {} + + def _load_all_configs_from_dir(self, server_mode: bool = False): + """ + 动态加载目录中的所有.json文件 + + Args: + server_mode: 是否为服务器模式,如果是则只加载必要的全局配置 + """ + try: + # 遍历并加载目录中所有其他的 .json 文件 for config_path in self.config_dir.glob('*.json'): - if config_path.name == 'poster_gen_config.json': - continue # 跳过已处理的主配置文件 - config_name = config_path.stem # 'topic_gen.json' -> 'topic_gen' + + # 服务器模式下,只加载必要的全局配置 + if server_mode and config_name not in self.SERVER_CONFIGS: + logger.info(f"服务器模式下跳过非全局配置: {config_name}") + continue + + # 加载原始配置 + with open(config_path, 'r', encoding='utf-8') as f: + config_data = json.load(f) + self._raw_configs[config_name] = config_data + + # 更新对象配置 if config_name in self._configs: logger.info(f"加载配置文件 '{config_name}': {config_path}") - with open(config_path, 'r', encoding='utf-8') as f: - config_data = json.load(f) self._configs[config_name].update(config_data) + self._loaded_configs.add(config_name) else: - logger.warning(f"在 '{config_path}' 中找到的配置 '{config_name}' 没有对应的已注册配置类型,已跳过。") + logger.info(f"加载原始配置 '{config_name}': {config_path}") - # 3. 最后应用环境变量覆盖 + # 最后应用环境变量覆盖 self._apply_env_overrides() except Exception as e: logger.error(f"从目录 '{self.config_dir}' 加载配置失败: {e}", exc_info=True) raise - def _load_main_config(self, path: Path): - """加载主配置文件,并分发到各个配置对象""" - logger.info(f"加载主配置文件: {path}") - with open(path, 'r', encoding='utf-8') as f: - config_data = json.load(f) + def load_task_config(self, config_name: str) -> bool: + """ + 按需加载任务配置 - for name, config_obj in self._configs.items(): - # 主配置文件可能是扁平结构或嵌套结构 - if name in config_data: # 嵌套结构 - config_obj.update(config_data[name]) - else: # 尝试从根部更新 (扁平结构) - config_obj.update(config_data) + Args: + config_name: 配置名称 + + Returns: + 是否成功加载 + """ + if config_name in self._loaded_configs: + return True + + if self.config_dir: + config_path = self.config_dir / f"{config_name}.json" + if config_path.exists(): + try: + with open(config_path, 'r', encoding='utf-8') as f: + config_data = json.load(f) + self._raw_configs[config_name] = config_data + + if config_name in self._configs: + self._configs[config_name].update(config_data) + self._loaded_configs.add(config_name) + logger.info(f"按需加载任务配置 '{config_name}': {config_path}") + return True + except Exception as e: + logger.error(f"加载任务配置 '{config_name}' 失败: {e}") + + logger.warning(f"未找到任务配置: {config_name}") + return False def _apply_env_overrides(self): """应用环境变量覆盖""" @@ -176,6 +242,10 @@ class ConfigManager: if update_data: ai_model_config.update(update_data) + # 更新原始配置 + if 'ai_model' in self._raw_configs: + for key, value in update_data.items(): + self._raw_configs['ai_model'][key] = value logger.info(f"通过环境变量更新了AI模型配置: {list(update_data.keys())}") def save_config(self, name: str): @@ -191,6 +261,9 @@ class ConfigManager: path = self.config_dir / f"{name}.json" config = self.get_config(name, BaseConfig) config_data = config.to_dict() + + # 更新原始配置 + self._raw_configs[name] = config_data try: with open(path, 'w', encoding='utf-8') as f: diff --git a/core/config/models.py b/core/config/models.py index 7e9c217..e9655b7 100644 --- a/core/config/models.py +++ b/core/config/models.py @@ -161,6 +161,8 @@ class PosterConfig(BaseConfig): additional_images_enabled: bool = True template_selection: str = "random" # random, business, vibrant, original available_templates: List[str] = Field(default_factory=lambda: ["original", "business", "vibrant"]) + poster_system_prompt: str = "resource/prompt/generatePoster/system.txt" + poster_user_prompt: str = "resource/prompt/generatePoster/user.txt" class ContentConfig(BaseConfig):