""" 配置管理 统一从项目根目录的 .env 文件加载配置 """ import os from dotenv import load_dotenv # 加载项目根目录的 .env 文件 # 路径: MiroFish/.env (相对于 backend/app/config.py) project_root_env = os.path.join(os.path.dirname(__file__), '../../.env') if os.path.exists(project_root_env): load_dotenv(project_root_env, override=True) else: # 如果根目录没有 .env,尝试加载环境变量(用于生产环境) load_dotenv(override=True) def _resolve_llm_config() -> tuple[str, str, str | None]: """ 解析LLM配置。 优先级: 1. 如果设置了 LLM_PROVIDER,使用提供商预设填充 base_url 和 model(但可被显式值覆盖) 2. 否则使用 LLM_BASE_URL / LLM_MODEL_NAME(兼容原有行为) Returns: (base_url, model_name, provider_name_or_none) """ from .providers import get_provider provider_name = os.environ.get('LLM_PROVIDER', '').strip() explicit_base_url = os.environ.get('LLM_BASE_URL', '').strip() explicit_model = os.environ.get('LLM_MODEL_NAME', '').strip() if provider_name: preset = get_provider(provider_name) if preset is None: provider_names = ["openai", "deepseek", "xiaomi_mimo", "alibaba_dashscope", "minimax"] available = ", ".join(provider_names) raise ValueError( f"未知的 LLM_PROVIDER: '{provider_name}'. " f"可用值: {available}" ) # 显式值优先于预设默认值 base_url = explicit_base_url or preset.base_url model = explicit_model or preset.default_model return base_url, model, provider_name else: # 兼容原有行为:无 LLM_PROVIDER 时直接使用显式值 base_url = explicit_base_url or 'https://api.openai.com/v1' model = explicit_model or 'gpt-4o-mini' return base_url, model, None # 在模块加载时解析配置(避免重复计算) _llm_base_url, _llm_model_name, _llm_provider = _resolve_llm_config() class Config: """Flask配置类""" # Flask配置 SECRET_KEY = os.environ.get('SECRET_KEY', 'mirofish-secret-key') DEBUG = os.environ.get('FLASK_DEBUG', 'True').lower() == 'true' # JSON配置 - 禁用ASCII转义,让中文直接显示(而不是 \uXXXX 格式) JSON_AS_ASCII = False # LLM配置(统一使用OpenAI格式) LLM_API_KEY = os.environ.get('LLM_API_KEY') LLM_PROVIDER = _llm_provider # e.g. "deepseek" or None LLM_BASE_URL = _llm_base_url LLM_MODEL_NAME = _llm_model_name # Zep配置 ZEP_API_KEY = os.environ.get('ZEP_API_KEY') # 文件上传配置 MAX_CONTENT_LENGTH = 50 * 1024 * 1024 # 50MB UPLOAD_FOLDER = os.path.join(os.path.dirname(__file__), '../uploads') ALLOWED_EXTENSIONS = {'pdf', 'md', 'txt', 'markdown'} # 文本处理配置 DEFAULT_CHUNK_SIZE = 500 # 默认切块大小 DEFAULT_CHUNK_OVERLAP = 50 # 默认重叠大小 # OASIS模拟配置 OASIS_DEFAULT_MAX_ROUNDS = int(os.environ.get('OASIS_DEFAULT_MAX_ROUNDS', '10')) OASIS_SIMULATION_DATA_DIR = os.path.join(os.path.dirname(__file__), '../uploads/simulations') # OASIS平台可用动作配置 OASIS_TWITTER_ACTIONS = [ 'CREATE_POST', 'LIKE_POST', 'REPOST', 'FOLLOW', 'DO_NOTHING', 'QUOTE_POST' ] OASIS_REDDIT_ACTIONS = [ 'LIKE_POST', 'DISLIKE_POST', 'CREATE_POST', 'CREATE_COMMENT', 'LIKE_COMMENT', 'DISLIKE_COMMENT', 'SEARCH_POSTS', 'SEARCH_USER', 'TREND', 'REFRESH', 'DO_NOTHING', 'FOLLOW', 'MUTE' ] # Report Agent配置 REPORT_AGENT_MAX_TOOL_CALLS = int(os.environ.get('REPORT_AGENT_MAX_TOOL_CALLS', '5')) REPORT_AGENT_MAX_REFLECTION_ROUNDS = int(os.environ.get('REPORT_AGENT_MAX_REFLECTION_ROUNDS', '2')) REPORT_AGENT_TEMPERATURE = float(os.environ.get('REPORT_AGENT_TEMPERATURE', '0.5')) @classmethod def validate(cls) -> list[str]: """验证必要配置""" errors: list[str] = [] if not cls.LLM_API_KEY: errors.append("LLM_API_KEY 未配置") if not cls.ZEP_API_KEY: errors.append("ZEP_API_KEY 未配置") return errors @classmethod def get_active_provider_info(cls) -> dict: """返回当前活跃的LLM提供商信息(用于日志/API展示)""" from .providers import get_provider info = { "provider": cls.LLM_PROVIDER or "custom", "base_url": cls.LLM_BASE_URL, "model": cls.LLM_MODEL_NAME, } if cls.LLM_PROVIDER: preset = get_provider(cls.LLM_PROVIDER) if preset: info["display_name"] = preset.display_name info["notes"] = preset.notes return info