Files
2026-03-24 18:07:22 +08:00

142 lines
5.4 KiB
Python

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()