""" Configuration centrale de PolyglotRAG. Toutes les valeurs peuvent être surchargées par des variables d'environnement (fichier .env en local, "Secrets"/"Variables" sur un Hugging Face Space). Le projet n'a besoin que d'UN seul token : HF_TOKEN (droits "Make calls to Inference Providers" + lecture/écriture sur le dataset privé). """ from __future__ import annotations import os from dataclasses import dataclass, field try: from dotenv import load_dotenv load_dotenv() except ImportError: # pragma: no cover - python-dotenv est optionnel pass SUPPORTED_LANGUAGES: dict[str, dict[str, str]] = { "en": {"name": "English", "dir": "ltr"}, "fr": {"name": "Français", "dir": "ltr"}, "ru": {"name": "Русский", "dir": "ltr"}, "es": {"name": "Español", "dir": "ltr"}, "ar": {"name": "العربية", "dir": "rtl"}, "pt": {"name": "Português", "dir": "ltr"}, } def _bool_env(name: str, default: bool) -> bool: val = os.getenv(name) if val is None: return default return val.strip().lower() in {"1", "true", "yes", "on"} @dataclass class Settings: # --- Authentification (un seul token pour tout) ----------------------- hf_token: str = field(default_factory=lambda: os.getenv("HF_TOKEN", "")) # --- Modèle génératif --------------------------------------------------- # Servi via le routeur "Inference Providers" de Hugging Face. # ":fastest" laisse HF choisir automatiquement le meilleur provider # (aujourd'hui Novita) sans jamais changer de code applicatif. llm_model: str = field( default_factory=lambda: os.getenv( "LLM_MODEL", "deepseek-ai/DeepSeek-V4.1-Flash:fastest" ) ) llm_temperature: float = field( default_factory=lambda: float(os.getenv("LLM_TEMPERATURE", "0.2")) ) llm_max_tokens: int = field( default_factory=lambda: int(os.getenv("LLM_MAX_TOKENS", "1024")) ) # --- Modèle d'embeddings -------------------------------------------- # BGE-M3 = qualité maximale, multilingue + cross-lingue natif (défaut). # intfloat/multilingual-e5-base = alternative légère (variable d'env). embedding_model: str = field( default_factory=lambda: os.getenv("EMBEDDING_MODEL", "BAAI/bge-m3") ) embedding_dim: int = field( default_factory=lambda: int(os.getenv("EMBEDDING_DIM", "1024")) ) use_query_passage_prefix: bool = field( default_factory=lambda: _bool_env("USE_QUERY_PASSAGE_PREFIX", False) ) # --- Stockage vectoriel (dataset privé Hugging Face) -------------------- hf_dataset_repo: str = field( default_factory=lambda: os.getenv("HF_DATASET_REPO", "") ) local_index_dir: str = field( default_factory=lambda: os.getenv("LOCAL_INDEX_DIR", "data/index") ) # --- Chunking ----------------------------------------------------------- chunk_size_tokens: int = field( default_factory=lambda: int(os.getenv("CHUNK_SIZE_TOKENS", "350")) ) chunk_overlap_tokens: int = field( default_factory=lambda: int(os.getenv("CHUNK_OVERLAP_TOKENS", "60")) ) # --- Recherche hybride ---------------------------------------------- top_k_retrieve: int = field( default_factory=lambda: int(os.getenv("TOP_K_RETRIEVE", "15")) ) # Volontairement bas par défaut : moins de passages envoyés au LLM -> # latence et coût réduits, contexte plus dense. Augmentez si votre # corpus a besoin de plus de passages pour couvrir une même réponse. top_k_final: int = field( default_factory=lambda: int(os.getenv("TOP_K_FINAL", "2")) ) same_language_bonus: float = field( default_factory=lambda: float(os.getenv("SAME_LANGUAGE_BONUS", "0.04")) ) min_confidence_score: float = field( default_factory=lambda: float(os.getenv("MIN_CONFIDENCE_SCORE", "0.28")) ) # --- Index vectoriel : recherche exacte ou approximative (ANN) --------- # "hnsw" (par défaut) = FAISS IndexHNSWFlat, approximatif mais bien plus # rapide sur un grand corpus, sans étape d'entraînement (contrairement à # IVF). "flat" = recherche exacte (IndexFlatIP), utile pour un tout petit # corpus de test ou pour comparer la qualité du retrieval. ann_index_type: str = field( default_factory=lambda: os.getenv("ANN_INDEX_TYPE", "hnsw") ) ann_hnsw_m: int = field( default_factory=lambda: int(os.getenv("ANN_HNSW_M", "32")) ) ann_ef_search: int = field( default_factory=lambda: int(os.getenv("ANN_EF_SEARCH", "64")) ) # --- Cache (réponses + embeddings de requêtes) -------------------------- enable_cache: bool = field( default_factory=lambda: _bool_env("ENABLE_CACHE", True) ) cache_ttl_seconds: int = field( default_factory=lambda: int(os.getenv("CACHE_TTL_SECONDS", "600")) ) cache_max_size: int = field( default_factory=lambda: int(os.getenv("CACHE_MAX_SIZE", "512")) ) # --- Journalisation / métriques --------------------------------------- metrics_db_path: str = field( default_factory=lambda: os.getenv("METRICS_DB_PATH", "data/metrics.sqlite3") ) # --- Admin --------------------------------------------------------------- admin_password: str = field( default_factory=lambda: os.getenv("ADMIN_PASSWORD", "") ) def validate(self) -> list[str]: errors = [] if not self.hf_token: errors.append( "HF_TOKEN manquant : créez un token sur " "https://huggingface.co/settings/tokens avec le scope " "'Make calls to Inference Providers' (+ 'Write' si vous " "poussez un dataset)." ) return errors settings = Settings()