import os
import yaml
from enum import Enum
from typing import Optional
from dotenv import load_dotenv
from pydantic import BaseModel, Field, SecretStr
# Load environment variables from .env file
load_dotenv()
# --- 1. Define the Configuration Schema ---
class EmbeddingProvider(str, Enum):
"""An enum for supported embedding providers."""
GOOGLE_GEMINI = "google_gemini"
MOCK = "mock"
class TTSProvider(str, Enum):
"""An enum for supported Text-to-Speech (TTS) providers."""
GOOGLE_GEMINI = "google_gemini"
GCLOUD_TTS = "gcloud_tts" # NEW: Add Google Cloud TTS as a supported provider
class STTProvider(str, Enum):
"""An enum for supported Speech-to-Text (STT) providers."""
GOOGLE_GEMINI = "google_gemini"
OPENAI = "openai"
class ApplicationSettings(BaseModel):
project_name: str = "Cortex Hub"
version: str = "1.0.0"
log_level: str = "INFO"
class DatabaseSettings(BaseModel):
mode: str = "sqlite"
url: Optional[str] = None
local_path: str = "data/ai_hub.db"
class LLMProviderSettings(BaseModel):
deepseek_model_name: str = "deepseek-chat"
gemini_model_name: str = "gemini-1.5-flash-latest"
class EmbeddingProviderSettings(BaseModel):
provider: EmbeddingProvider = Field(default=EmbeddingProvider.GOOGLE_GEMINI)
model_name: str = "models/text-embedding-004"
api_key: Optional[SecretStr] = None
class TTSProviderSettings(BaseModel):
provider: TTSProvider = Field(default=TTSProvider.GOOGLE_GEMINI)
# The default values are kept as originally requested
voice_name: str = "Kore"
model_name: str = "gemini-2.5-flash-preview-tts"
api_key: Optional[SecretStr] = None
class STTProviderSettings(BaseModel):
provider: STTProvider = Field(default=STTProvider.GOOGLE_GEMINI)
model_name: str = "gemini-2.5-flash"
api_key: Optional[SecretStr] = None
class VectorStoreSettings(BaseModel):
index_path: str = "data/faiss_index.bin"
embedding_dimension: int = 768
class AppConfig(BaseModel):
"""Top-level Pydantic model for application configuration."""
application: ApplicationSettings = Field(default_factory=ApplicationSettings)
database: DatabaseSettings = Field(default_factory=DatabaseSettings)
llm_providers: LLMProviderSettings = Field(default_factory=LLMProviderSettings)
vector_store: VectorStoreSettings = Field(default_factory=VectorStoreSettings)
embedding_provider: EmbeddingProviderSettings = Field(default_factory=EmbeddingProviderSettings)
tts_provider: TTSProviderSettings = Field(default_factory=TTSProviderSettings)
stt_provider: STTProviderSettings = Field(default_factory=STTProviderSettings)
# --- 2. Create the Final Settings Object ---
class Settings:
"""
Holds all application settings, validated and structured by Pydantic.
Priority Order: Environment Variables > YAML File > Pydantic Defaults
"""
def __init__(self):
config_path = os.getenv("CONFIG_PATH", "config.yaml")
yaml_data = {}
if os.path.exists(config_path):
print(f"✅ Loading configuration from {config_path}")
with open(config_path, 'r') as f:
yaml_data = yaml.safe_load(f) or {}
else:
print(f"⚠️ '{config_path}' not found. Using defaults and environment variables.")
config_from_pydantic = AppConfig.parse_obj(yaml_data)
def get_from_yaml(keys):
d = yaml_data
for key in keys:
d = d.get(key) if isinstance(d, dict) else None
return d
self.PROJECT_NAME: str = os.getenv("PROJECT_NAME") or \
get_from_yaml(["application", "project_name"]) or \
config_from_pydantic.application.project_name
self.VERSION: str = config_from_pydantic.application.version
self.LOG_LEVEL: str = os.getenv("LOG_LEVEL") or \
get_from_yaml(["application", "log_level"]) or \
config_from_pydantic.application.log_level
# --- Database Settings ---
self.DB_MODE: str = os.getenv("DB_MODE") or \
get_from_yaml(["database", "mode"]) or \
config_from_pydantic.database.mode
local_db_path = os.getenv("LOCAL_DB_PATH") or \
get_from_yaml(["database", "local_path"]) or \
config_from_pydantic.database.local_path
external_db_url = os.getenv("DATABASE_URL") or \
get_from_yaml(["database", "url"]) or \
config_from_pydantic.database.url
if self.DB_MODE == "sqlite":
normalized_path = local_db_path.lstrip("./")
self.DATABASE_URL: str = f"sqlite:///./{normalized_path}" if normalized_path else "sqlite:///./data/ai_hub.db"
else:
self.DATABASE_URL: str = external_db_url or "sqlite:///./data/ai_hub.db"
# --- API Keys & Models ---
self.DEEPSEEK_API_KEY: Optional[str] = os.getenv("DEEPSEEK_API_KEY")
self.GEMINI_API_KEY: Optional[str] = os.getenv("GEMINI_API_KEY")
self.OPENAI_API_KEY: Optional[str] = os.getenv("OPENAI_API_KEY")
self.DEEPSEEK_MODEL_NAME: str = os.getenv("DEEPSEEK_MODEL_NAME") or \
get_from_yaml(["llm_providers", "deepseek_model_name"]) or \
config_from_pydantic.llm_providers.deepseek_model_name
self.GEMINI_MODEL_NAME: str = os.getenv("GEMINI_MODEL_NAME") or \
get_from_yaml(["llm_providers", "gemini_model_name"]) or \
config_from_pydantic.llm_providers.gemini_model_name
# --- Vector Store Settings ---
self.FAISS_INDEX_PATH: str = os.getenv("FAISS_INDEX_PATH") or \
get_from_yaml(["vector_store", "index_path"]) or \
config_from_pydantic.vector_store.index_path
dimension_str = os.getenv("EMBEDDING_DIMENSION") or \
get_from_yaml(["vector_store", "embedding_dimension"]) or \
config_from_pydantic.vector_store.embedding_dimension
self.EMBEDDING_DIMENSION: int = int(dimension_str)
# --- Embedding Provider Settings ---
embedding_provider_env = os.getenv("EMBEDDING_PROVIDER")
if embedding_provider_env:
embedding_provider_env = embedding_provider_env.lower()
self.EMBEDDING_PROVIDER: EmbeddingProvider = EmbeddingProvider(embedding_provider_env or \
get_from_yaml(["embedding_provider", "provider"]) or \
config_from_pydantic.embedding_provider.provider)
self.EMBEDDING_MODEL_NAME: str = os.getenv("EMBEDDING_MODEL_NAME") or \
get_from_yaml(["embedding_provider", "model_name"]) or \
config_from_pydantic.embedding_provider.model_name
self.EMBEDDING_API_KEY: Optional[str] = os.getenv("EMBEDDING_API_KEY") or \
get_from_yaml(["embedding_provider", "api_key"]) or \
self.GEMINI_API_KEY
# --- TTS Provider Settings ---
tts_provider_env = os.getenv("TTS_PROVIDER")
if tts_provider_env:
tts_provider_env = tts_provider_env.lower()
self.TTS_PROVIDER: TTSProvider = TTSProvider(tts_provider_env or \
get_from_yaml(["tts_provider", "provider"]) or \
config_from_pydantic.tts_provider.provider)
self.TTS_VOICE_NAME: str = os.getenv("TTS_VOICE_NAME") or \
get_from_yaml(["tts_provider", "voice_name"]) or \
config_from_pydantic.tts_provider.voice_name
self.TTS_MODEL_NAME: str = os.getenv("TTS_MODEL_NAME") or \
get_from_yaml(["tts_provider", "model_name"]) or \
config_from_pydantic.tts_provider.model_name
# API Key logic for TTS
tts_api_key_env = os.getenv("TTS_API_KEY") or get_from_yaml(["tts_provider", "api_key"])
if tts_api_key_env:
self.TTS_API_KEY: Optional[str] = tts_api_key_env
else:
# If no specific TTS key is set, use the Gemini key as a fallback
self.TTS_API_KEY: Optional[str] = self.GEMINI_API_KEY
# --- STT Provider Settings ---
stt_provider_env = os.getenv("STT_PROVIDER")
if stt_provider_env:
stt_provider_env = stt_provider_env.lower()
self.STT_PROVIDER: STTProvider = STTProvider(stt_provider_env or \
get_from_yaml(["stt_provider", "provider"]) or \
config_from_pydantic.stt_provider.provider)
self.STT_MODEL_NAME: str = os.getenv("STT_MODEL_NAME") or \
get_from_yaml(["stt_provider", "model_name"]) or \
config_from_pydantic.stt_provider.model_name
# Logic for STT_API_KEY: Prioritize a dedicated STT_API_KEY.
explicit_stt_api_key = os.getenv("STT_API_KEY") or get_from_yaml(["stt_provider", "api_key"])
if explicit_stt_api_key:
self.STT_API_KEY: Optional[str] = explicit_stt_api_key
elif self.STT_PROVIDER == STTProvider.OPENAI:
self.STT_API_KEY: Optional[str] = self.OPENAI_API_KEY
else:
# Fallback for Google Gemini or other providers
self.STT_API_KEY: Optional[str] = self.GEMINI_API_KEY
# Instantiate the single settings object for the application
settings = Settings()