""" 配置管理模块 """ import os from pathlib import Path from pydantic_settings import BaseSettings from dotenv import load_dotenv # 加载环境变量 # 🔥 确保从 backend 目录加载 .env 文件(无论从哪个目录启动服务) backend_dir = Path(__file__).parent # 支持通过环境变量 ENV_FILE 指定 env 文件路径(生产环境使用) env_file_name = os.getenv("ENV_FILE", ".env") env_file = backend_dir / env_file_name if env_file.exists(): load_dotenv(env_file) print(f"✅ 已加载 .env 文件: {env_file}") else: # 如果指定的 env 文件不存在,尝试默认 .env default_env_file = backend_dir / ".env" if default_env_file.exists(): load_dotenv(default_env_file) print(f"✅ 已加载默认 .env 文件: {default_env_file}") else: # 如果 backend/.env 不存在,尝试从当前目录加载(兼容旧方式) load_dotenv() if os.getenv("TTS_SPEECH_RATE"): print(f"⚠️ 从当前目录加载环境变量,TTS_SPEECH_RATE={os.getenv('TTS_SPEECH_RATE')}") else: print(f"⚠️ 未找到 .env 文件: {env_file},使用默认配置") class Settings(BaseSettings): """应用配置""" # 应用基础配置 APP_NAME: str = "Lyrria Voice Assistant" APP_VERSION: str = "1.0.0" DEBUG: bool = False # 服务器配置 HOST: str = os.getenv("HOST", "0.0.0.0") PORT: int = int(os.getenv("PORT", "8000")) host: str = os.getenv("HOST", "0.0.0.0") # 别名,兼容小写 port: int = int(os.getenv("PORT", "8000")) # 别名,兼容小写 # WebSocket配置 WS_HEARTBEAT_INTERVAL: int = 30 # 心跳间隔(秒) WS_TIMEOUT: int = 60 # 超时时间(秒) # 能量石配置 ENERGY_STONE_TTL_SECONDS: int = int(os.getenv("ENERGY_STONE_TTL_SECONDS", "20")) CONVERSATION_STONE_INTERVAL_SECONDS: int = int(os.getenv("CONVERSATION_STONE_INTERVAL_SECONDS", "30")) MISSION_TYPE_CACHE_SECONDS: int = int(os.getenv("MISSION_TYPE_CACHE_SECONDS", "60")) # DashScope API配置(阿里云) dashscope_api_key: str = os.getenv("DASHSCOPE_API_KEY", "") dashscope_api_key_intl: str = os.getenv("DASHSCOPE_API_KEY_INTL", "") # 国际版单独的key(可选) use_dashscope_intl: bool = os.getenv("USE_DASHSCOPE_INTL", "false").lower() == "true" # 是否使用国际版 @property def active_dashscope_api_key(self) -> str: """获取当前激活的API Key(根据国际版/国内版自动选择)""" if self.use_dashscope_intl and self.dashscope_api_key_intl: return self.dashscope_api_key_intl return self.dashscope_api_key # LLM配置 llm_model: str = os.getenv("LLM_MODEL", "qwen-flash") LLM_BASE_URL: str = os.getenv("LLM_BASE_URL", "") # STT配置 # ⚠️ 重要:language参数必须设置! # 支持的值: # - 'zh' (中文) # - 'en' (英文) # - 'ja' (日文) # - 'ko' (韩文) # - 'auto' (自动检测 - 实验性,需要测试) # 项目正式使用时应设为'en',测试阶段用'zh'或'auto' STT_MODEL: str = os.getenv("STT_MODEL", "qwen3-asr-flash-realtime") STT_WS_URL: str = os.getenv("STT_WS_URL", "") STT_LANGUAGE: str = os.getenv("STT_LANGUAGE", "zh") # TTS配置 tts_model: str = os.getenv("TTS_MODEL", "qwen3-tts-flash") TTS_API_URL: str = os.getenv("TTS_API_URL", "") tts_voice: str = os.getenv("TTS_VOICE", "Cherry") # 女声(支持:Cherry, Stella) tts_speech_rate: float = float(os.getenv("TTS_SPEECH_RATE", "1.1")) # 语速:1.1 = 10%加速(优化延迟) # TTS重试配置(处理API速率限制) TTS_MAX_RETRIES: int = int(os.getenv("TTS_MAX_RETRIES", "3")) # 最大重试次数 TTS_RETRY_DELAY: float = float(os.getenv("TTS_RETRY_DELAY", "5.0")) # 初始重试延迟(秒)- 增加到5秒 TTS_RETRY_BACKOFF: float = float(os.getenv("TTS_RETRY_BACKOFF", "2.0")) # 指数退避系数 # TTS速率控制(预防性措施,避免触发API限制) TTS_MIN_INTERVAL: float = float(os.getenv("TTS_MIN_INTERVAL", "0.8")) # 两次TTS调用的最小间隔(秒) # AI助手提示词配置 LEARNER_PROFICIENCY: str = os.getenv("LEARNER_PROFICIENCY", "A1-A2") AGE_TAG: str = os.getenv("AGE_TAG", "Adult") TOPIC_TAG: str = os.getenv("TOPIC_TAG", "Daily Conversation") EXPERIMENT_TAG: str = os.getenv("EXPERIMENT_TAG", "control") # control 或 experiment USE_SIMPLIFIED_PROMPT: bool = os.getenv("USE_SIMPLIFIED_PROMPT", "false").lower() == "true" # 🔥 使用简化提示词(减少延迟) # 音频配置 AUDIO_SAMPLE_RATE: int = 16000 AUDIO_BIT_RATE: int = 16 AUDIO_CHANNELS: int = 1 # MySQL配置 MYSQL_HOST: str = os.getenv("MYSQL_HOST", "47.99.164.66") MYSQL_PORT: int = int(os.getenv("MYSQL_PORT", "3306")) MYSQL_USER: str = os.getenv("MYSQL_USER", "root") MYSQL_PASSWORD: str = os.getenv("MYSQL_PASSWORD", "") MYSQL_DATABASE: str = os.getenv("MYSQL_DATABASE", "Lyrria") MYSQL_POOL_SIZE: int = int(os.getenv("MYSQL_POOL_SIZE", "10")) MYSQL_MAX_OVERFLOW: int = int(os.getenv("MYSQL_MAX_OVERFLOW", "20")) MYSQL_CONNECT_TIMEOUT: int = int(os.getenv("MYSQL_CONNECT_TIMEOUT", "10")) MYSQL_READ_TIMEOUT: int = int(os.getenv("MYSQL_READ_TIMEOUT", "10")) MYSQL_POOL_RECYCLE: int = int(os.getenv("MYSQL_POOL_RECYCLE", "7200")) # 🔥 增加到2小时,保持长连接 MYSQL_HEALTHCHECK_INTERVAL: int = int(os.getenv("MYSQL_HEALTHCHECK_INTERVAL", "600")) # 🔥 增加到10分钟,减少检查频率 # Redis配置 REDIS_HOST: str = os.getenv("REDIS_HOST", "47.99.164.66") REDIS_PORT: int = int(os.getenv("REDIS_PORT", "16379")) REDIS_DB: int = int(os.getenv("REDIS_DB", "12")) REDIS_PASSWORD: str = os.getenv("REDIS_PASSWORD", "") REDIS_SSL: bool = os.getenv("REDIS_SSL", "false").lower() == "true" REDIS_SESSION_TTL: int = int(os.getenv("REDIS_SESSION_TTL", "86400")) REDIS_MAX_CONNECTIONS: int = int(os.getenv("REDIS_MAX_CONNECTIONS", "50")) REDIS_HEALTHCHECK_INTERVAL: int = int(os.getenv("REDIS_HEALTHCHECK_INTERVAL", "300")) # 🔥 Redis健康检查间隔(秒),5分钟检查一次 # HTTP客户端配置(用于DashScope API) HTTPX_MAX_KEEPALIVE_CONNECTIONS: int = int(os.getenv("HTTPX_MAX_KEEPALIVE_CONNECTIONS", "20")) # 🔥 keepalive连接数 HTTPX_MAX_CONNECTIONS: int = int(os.getenv("HTTPX_MAX_CONNECTIONS", "50")) # 🔥 最大连接数 HTTPX_KEEPALIVE_EXPIRY: float = float(os.getenv("HTTPX_KEEPALIVE_EXPIRY", "600.0")) # 🔥 keepalive过期时间(秒),10分钟,减少长时间未使用后的连接重建 # LLM连接健康检查配置 LLM_HEALTHCHECK_INTERVAL: int = int(os.getenv("LLM_HEALTHCHECK_INTERVAL", "300")) # 🔥 LLM连接健康检查间隔(秒),5分钟检查一次 # TTS连接健康检查配置 TTS_HEALTHCHECK_INTERVAL: int = int(os.getenv("TTS_HEALTHCHECK_INTERVAL", "300")) # 🔥 TTS连接健康检查间隔(秒),5分钟检查一次 # CORS配置 # 支持localhost和任意IP地址访问(端口3000为Vite默认HTTPS端口) CORS_ORIGINS: list = [ "http://localhost:3000", "https://localhost:3000", "http://127.0.0.1:3000", "https://127.0.0.1:3000", "http://localhost:5173", "https://localhost:5173", "http://127.0.0.1:5173", "https://127.0.0.1:5173", # 支持局域网IP访问 "http://192.168.2.112:3000", "https://192.168.2.112:3000", "http://192.168.2.112:5173", "https://192.168.2.112:5173", "http://192.168.43.35:3000", "https://192.168.43.35:3000", "http://192.168.43.35:5173", "https://192.168.43.35:5173", ] # 是否允许所有来源(开发环境可设为True,生产环境务必设为False) CORS_ALLOW_ALL: bool = os.getenv("CORS_ALLOW_ALL", "true").lower() == "true" # 日志配置 LOG_LEVEL: str = "INFO" LOG_DIR: str = "logs" LOG_ROTATION: str = "500 MB" LOG_RETENTION: str = "10 days" # SSL配置(可选,生产环境建议由nginx统一管理SSL) SSL_KEYFILE: str = os.getenv("SSL_KEYFILE", "") SSL_CERTFILE: str = os.getenv("SSL_CERTFILE", "") # OAuth配置 - 微信开放平台(用于登录) WECHAT_OPEN_APP_ID: str = os.getenv("WECHAT_OPEN_APP_ID", os.getenv("WECHAT_APP_ID", "")) # 开放平台AppID,兼容旧配置 WECHAT_OPEN_APP_SECRET: str = os.getenv("WECHAT_OPEN_APP_SECRET", os.getenv("WECHAT_APP_SECRET", "")) # 开放平台AppSecret,兼容旧配置 WECHAT_REDIRECT_URI: str = os.getenv("WECHAT_REDIRECT_URI", "") # 微信公众平台配置(用于JSSDK分享) WECHAT_MP_APP_ID: str = os.getenv("WECHAT_MP_APP_ID", os.getenv("WECHAT_APP_ID", "")) # 公众平台AppID,兼容旧配置 WECHAT_MP_APP_SECRET: str = os.getenv("WECHAT_MP_APP_SECRET", os.getenv("WECHAT_APP_SECRET", "")) # 公众平台AppSecret,兼容旧配置 # 微信JS-SDK配置(用于分享功能) WECHAT_JSAPI_TICKET: str = os.getenv("WECHAT_JSAPI_TICKET", "") # jsapi_ticket(需要从微信服务器获取) WECHAT_JSAPI_TICKET_CACHE_KEY: str = "wechat_jsapi_ticket" # Redis缓存key WECHAT_JSAPI_TICKET_CACHE_TTL: int = int(os.getenv("WECHAT_JSAPI_TICKET_CACHE_TTL", "7200")) # 缓存时间(秒),默认2小时 # 微信公众平台配置(用于公众号消息推送) WECHAT_MP_TOKEN: str = os.getenv("WECHAT_MP_TOKEN", "") # 公众号服务器配置Token # OAuth配置 - QQ开放平台 QQ_APP_ID: str = os.getenv("QQ_APP_ID", "") QQ_APP_KEY: str = os.getenv("QQ_APP_KEY", "") QQ_REDIRECT_URI: str = os.getenv("QQ_REDIRECT_URI", "") QQ_APP_NAME: str = os.getenv("QQ_APP_NAME", "Lyrria") # QQ应用名称(用于分享功能) # 前端URL(用于OAuth回调后的前端跳转) FRONTEND_URL: str = os.getenv("FRONTEND_URL", "http://localhost:5173") # API安全配置 API_CLIENT_KEY: str = os.getenv("API_CLIENT_KEY", "") # 前端API Key,用于验证请求来源 API_KEY_ENABLED: bool = os.getenv("API_KEY_ENABLED", "true").lower() == "true" # 是否启用API Key验证 class Config: env_file = ".env" case_sensitive = True extra = "ignore" # 忽略额外的环境变量(如旧的连接池配置) # 创建全局配置实例 settings = Settings() # 🔍 调试:打印 TTS 语速配置(确认环境变量是否正确加载) import logging logger = logging.getLogger(__name__) env_value = os.getenv("TTS_SPEECH_RATE", "未设置") logger.info(f"🔍 TTS配置检查 - 环境变量 TTS_SPEECH_RATE: {env_value}, 最终配置值 tts_speech_rate: {settings.tts_speech_rate}") # 确保日志目录存在 Path(settings.LOG_DIR).mkdir(parents=True, exist_ok=True) # DashScope API endpoint 配置 def get_dashscope_domain() -> str: """获取 DashScope API 域名""" return "dashscope-intl.aliyuncs.com" if settings.use_dashscope_intl else "dashscope.aliyuncs.com" def get_dashscope_ws_url() -> str: """获取 DashScope WebSocket URL(用于STT)""" if settings.STT_WS_URL: return settings.STT_WS_URL domain = get_dashscope_domain() return f"wss://{domain}/api-ws/v1/realtime" def get_dashscope_http_base_url() -> str: """获取 DashScope HTTP base URL(用于LLM)""" if settings.LLM_BASE_URL: return settings.LLM_BASE_URL.rstrip("/") domain = get_dashscope_domain() return f"https://{domain}/compatible-mode/v1" def get_dashscope_tts_api_url() -> str: """获取 DashScope HTTP API URL(用于TTS)""" if settings.TTS_API_URL: return settings.TTS_API_URL.rstrip("/") domain = get_dashscope_domain() return f"https://{domain}/api/v1"