from functools import lru_cache from urllib.parse import quote_plus from pathlib import Path from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict ENV_FILE = Path(__file__).resolve().parent / ".env" class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=str(ENV_FILE), env_file_encoding="utf-8", extra="ignore", ) app_name: str = Field(default="banban-server", validation_alias="APP_NAME") app_version: str = Field(default="0.1.0", validation_alias="APP_VERSION") app_description: str = Field( default="Backend service for TalkingQ device", validation_alias="APP_DESCRIPTION", ) server_host: str = Field(default="0.0.0.0", validation_alias="SERVER_HOST") server_port: int = Field(default=8080, validation_alias="SERVER_PORT") asr_provider: str = Field(default="Aliyun", validation_alias="ASR_PROVIDER") llm_provider: str = Field(default="Volcano", validation_alias="LLM_PROVIDER") tts_provider: str = Field(default="MiniMax", validation_alias="TTS_PROVIDER") volcano_api_key: str = Field(default="", validation_alias="VOLCANO_API_KEY") volcano_base_url: str = Field(default="", validation_alias="VOLCANO_BASE_URL") volcano_model_id: str = Field(default="ep-20250226121739-jkd24", validation_alias="VOLCANO_MODEL_ID") volcano_app_id: str = Field(default="7872932045", validation_alias="VOLCANO_APP_ID") volcano_access_token: str = Field(default="", validation_alias="VOLCANO_ACCESS_TOKEN") minimax_api_key: str = Field(default="", validation_alias="MINIMAX_API_KEY") minimax_group_id: str = Field(default="", validation_alias="MINIMAX_GROUP_ID") minimax_base_url: str = Field( default="https://api.minimax.chat/v1/t2a_v2", validation_alias="MINIMAX_BASE_URL", ) aliyun_api_key: str = Field(default="", validation_alias="ALIYUN_API_KEY") aliyun_vocabulary_id: str = Field(default="", validation_alias="ALIYUN_VOCABULARY_ID") assets_dir: str = Field(default="assets", validation_alias="ASSETS_DIR") session_timeout: int = Field(default=600, validation_alias="SESSION_TIMEOUT") conversation_history_timeout: int = Field(default=1800, validation_alias="CONVERSATION_HISTORY_TIMEOUT") max_conversation_history: int = Field(default=5, validation_alias="MAX_CONVERSATION_HISTORY") cleanup_interval: int = Field(default=300, validation_alias="CLEANUP_INTERVAL") llm_first_token_timeout: int = Field(default=5, validation_alias="LLM_FIRST_TOKEN_TIMEOUT") tts_request_timeout: int = Field(default=5, validation_alias="TTS_REQUEST_TIMEOUT") selected_role_key: str = Field(default="", validation_alias="SELECTED_ROLE_KEY") db_host: str = Field(default="mysql", validation_alias="DB_HOST") db_port: int = Field(default=3306, validation_alias="DB_PORT") db_user: str = Field(default="talkingq", validation_alias="DB_USER") db_password: str = Field(default="", validation_alias="DB_PASSWORD") db_name: str = Field(default="talkingq", validation_alias="DB_NAME") db_echo: bool = Field(default=False, validation_alias="DB_ECHO") talkingq_mqtt_broker: str = Field(default="broker.emqx.io", validation_alias="TALKINGQ_MQTT_BROKER") talkingq_mqtt_port: int = Field(default=1884, validation_alias="TALKINGQ_MQTT_PORT") talkingq_mqtt_username: str = Field(default="", validation_alias="TALKINGQ_MQTT_USERNAME") talkingq_mqtt_password: str = Field(default="", validation_alias="TALKINGQ_MQTT_PASSWORD") talkingq_mqtt_device_prefix: str = Field(default="TalkingQ", validation_alias="TALKINGQ_MQTT_DEVICE_PREFIX") talkingq_mqtt_qos: int = Field(default=1, validation_alias="TALKINGQ_MQTT_QOS") talkingq_mqtt_keepalive: int = Field(default=60, validation_alias="TALKINGQ_MQTT_KEEPALIVE") talkingq_mqtt_nfc_notice_interval: int = Field(default=600, validation_alias="TALKINGQ_MQTT_NFC_NOTICE_INTERVAL") admin_api_key: str = Field(default="", validation_alias="ADMIN_API_KEY") client_api_key: str = Field(default="", validation_alias="CLIENT_API_KEY") wechat_app_id: str = Field(default="", validation_alias="WECHAT_APP_ID") wechat_app_secret: str = Field(default="", validation_alias="WECHAT_APP_SECRET") wechat_api_base_url: str = Field( default="https://api.weixin.qq.com", validation_alias="WECHAT_API_BASE_URL", ) wechat_http_timeout_seconds: float = Field( default=5.0, validation_alias="WECHAT_HTTP_TIMEOUT_SECONDS", ) cos_secret_id: str = Field(default="", validation_alias="COS_SECRET_ID") cos_secret_key: str = Field(default="", validation_alias="COS_SECRET_KEY") cos_region: str = Field(default="", validation_alias="COS_REGION") cos_bucket_message: str = Field(default="", validation_alias="COS_BUCKET_MESSAGE") cos_bucket_ava: str = Field(default="", validation_alias="COS_BUCKET_AVA") cos_public_base_url: str = Field(default="", validation_alias="COS_PUBLIC_BASE_URL") cos_message_prefix: str = Field(default="messages/audio/", validation_alias="COS_MESSAGE_PREFIX") cos_avatar_prefix: str = Field(default="avatars/", validation_alias="COS_AVATAR_PREFIX") cos_avatar_url_expire_seconds: int = Field( default=86400, validation_alias="COS_AVATAR_URL_EXPIRE_SECONDS", ) cos_avatar_max_bytes: int = Field( default=2 * 1024 * 1024, validation_alias="COS_AVATAR_MAX_BYTES", ) jwt_secret: str = Field( default="dev_only_change_jwt_secret", validation_alias="JWT_SECRET", ) jwt_algorithm: str = Field(default="HS256", validation_alias="JWT_ALGORITHM") jwt_access_token_expire_minutes: int = Field( default=60, validation_alias="JWT_ACCESS_TOKEN_EXPIRE_MINUTES", ) log_level: str = Field(default="INFO", validation_alias="LOG_LEVEL") log_json: bool = Field(default=False, validation_alias="LOG_JSON") slow_sql_ms: int = Field(default=200, validation_alias="SLOW_SQL_MS") @property def mysql_dsn(self) -> str: user = quote_plus(self.db_user) password = quote_plus(self.db_password) auth = f"{user}:{password}" if password else user return ( f"mysql+pymysql://{auth}@{self.db_host}:{self.db_port}/" f"{self.db_name}?charset=utf8mb4" ) @lru_cache(maxsize=1) def get_settings() -> Settings: return Settings() settings = get_settings()