import os import configparser from typing import Optional from pathlib import Path class Config: """从 config.ini 读取的应用配置""" _config = configparser.ConfigParser() _config_loaded = False DEFAULT_MODEL_SECTION = "gpt-4o" MAX_RETRIES = 3 TIMEOUT = 30 CONVERSATION_MAX_HISTORY_MESSAGES = 10 CONVERSATION_ENABLE_MULTI_TURN = False CONVERSATION_ENABLE_COMPRESSION = False CONVERSATION_COMPRESSION_THRESHOLD = 8 @classmethod def _get_config_path(cls) -> Path: """获取配置文件路径(支持环境变量覆盖)""" env_path = os.getenv("CONFIG_PATH") if env_path: return Path(env_path) project_root = Path(__file__).parent.parent return project_root / 'config' / 'config.ini' @classmethod def _load_config(cls): """懒加载配置""" if cls._config_loaded: return config_path = cls._get_config_path() if not config_path.exists(): raise FileNotFoundError( f"Configuration file not found at: {config_path}. " "Please copy 'config/config.ini.example' to 'config/config.ini' and fill in your details." ) with open(config_path, "r", encoding="utf-8") as f: cls._config.read_file(f) cls._config_loaded = True @classmethod def reload(cls): """重新加载配置(支持热重载)""" cls._config_loaded = False cls._load_config() _refresh_runtime_constants() @classmethod def get_model_config(cls, section: Optional[str] = None) -> dict: """ 获取指定模型配置段。 若 section 为 None,则使用默认模型配置段。 """ if section is None: section = cls.DEFAULT_MODEL_SECTION if not cls._config.has_section(section): raise ValueError(f"Model section '{section}' not found in config.ini") config = dict(cls._config.items(section)) # 确保必需的键存在 if 'model_name' not in config or 'openai_api_key' not in config: raise ValueError(f"Model section '{section}' must contain 'model_name' and 'openai_api_key'") return { "model": config['model_name'], "api_key": config['openai_api_key'], "base_url": config.get('url') or config.get('base_url') } @classmethod def get_section(cls, section: str) -> dict: """获取指定配置段的键值对(键名会被转为小写)""" if not cls._config.has_section(section): return {} return dict(cls._config.items(section)) @classmethod def validate_config(cls): """校验默认模型配置是否存在且有效""" try: default_config = cls.get_model_config() if not default_config.get("api_key") or 'your_openai_api_key_here' in default_config.get("api_key"): raise ValueError(f"API key for default model '{cls.DEFAULT_MODEL_SECTION}' is missing or a placeholder.") except (ValueError, configparser.Error) as e: raise ValueError(f"Configuration validation failed: {e}") DEFAULT_MODEL_SECTION = Config.DEFAULT_MODEL_SECTION MAX_RETRIES = Config.MAX_RETRIES TIMEOUT = Config.TIMEOUT CONVERSATION_MAX_HISTORY_MESSAGES = Config.CONVERSATION_MAX_HISTORY_MESSAGES CONVERSATION_ENABLE_MULTI_TURN = Config.CONVERSATION_ENABLE_MULTI_TURN CONVERSATION_ENABLE_COMPRESSION = Config.CONVERSATION_ENABLE_COMPRESSION CONVERSATION_COMPRESSION_THRESHOLD = Config.CONVERSATION_COMPRESSION_THRESHOLD def _refresh_runtime_constants() -> None: """同步模块级常量与 Config 类属性,兼容两种访问方式。""" global DEFAULT_MODEL_SECTION global MAX_RETRIES global TIMEOUT global CONVERSATION_MAX_HISTORY_MESSAGES global CONVERSATION_ENABLE_MULTI_TURN global CONVERSATION_ENABLE_COMPRESSION global CONVERSATION_COMPRESSION_THRESHOLD DEFAULT_MODEL_SECTION = Config._config.get('General', 'DEFAULT_MODEL_SECTION', fallback='gpt-4o') MAX_RETRIES = Config._config.getint('General', 'MAX_RETRIES', fallback=3) TIMEOUT = Config._config.getint('General', 'TIMEOUT', fallback=30) CONVERSATION_MAX_HISTORY_MESSAGES = Config._config.getint('conversation', 'max_history_messages', fallback=10) CONVERSATION_ENABLE_MULTI_TURN = Config._config.getboolean('conversation', 'enable_multi_turn', fallback=False) CONVERSATION_ENABLE_COMPRESSION = Config._config.getboolean('conversation', 'enable_memory_compression', fallback=False) CONVERSATION_COMPRESSION_THRESHOLD = Config._config.getint('conversation', 'compression_threshold', fallback=8) Config.DEFAULT_MODEL_SECTION = DEFAULT_MODEL_SECTION Config.MAX_RETRIES = MAX_RETRIES Config.TIMEOUT = TIMEOUT Config.CONVERSATION_MAX_HISTORY_MESSAGES = CONVERSATION_MAX_HISTORY_MESSAGES Config.CONVERSATION_ENABLE_MULTI_TURN = CONVERSATION_ENABLE_MULTI_TURN Config.CONVERSATION_ENABLE_COMPRESSION = CONVERSATION_ENABLE_COMPRESSION Config.CONVERSATION_COMPRESSION_THRESHOLD = CONVERSATION_COMPRESSION_THRESHOLD # 在类定义完成后加载配置 Config._load_config() _refresh_runtime_constants() # 如有需要可在导入时做初始校验, # 但已移到 main.py 以便更可控地执行。 # 如需在导入时校验,可在此调用 Config.validate_config()