"""Central configuration — loaded once at startup from .env.""" from __future__ import annotations from pathlib import Path from typing import List, Tuple from pydantic_settings import BaseSettings, SettingsConfigDict # Project root is three levels up from this file (backend/core/config.py) _PROJECT_ROOT = Path(__file__).parent.parent.parent class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=_PROJECT_ROOT / ".env", env_file_encoding="utf-8", extra="ignore", ) # ── Specialized AI detection APIs ─────────────────────────── hive_api_key: str = "" hive_vlm_secret_key: str = "" # Hive VLM (Bearer token) aiornot_api_key: str = "" reality_defender_api_key: str = "" # Support multiple Sightengine keys for failover (comma-separated) sightengine_api_users: str = "" # e.g., "user1,user2,user3" sightengine_api_secrets: str = "" # e.g., "secret1,secret2,secret3" # Backward compatibility: single key (if provided, converted to list with 1 element) sightengine_api_user: str = "" sightengine_api_secret: str = "" # ── VLM APIs ──────────────────────────────────────────────── anthropic_api_key: str = "" openai_api_key: str = "" google_api_key: str = "" open_route_api_key: str = "" mistral_api_key: str = "" # ── Detector toggles ──────────────────────────────────────── enable_hive: bool = False enable_hive_vlm: bool = False enable_aiornot: bool = False enable_sightengine: bool = True enable_reality_defender: bool = False enable_claude: bool = False enable_gpt4: bool = False enable_gemini: bool = False enable_local_forensics: bool = False enable_hybrid_model: bool = False enable_openrouter_gemma: bool = False enable_openrouter_gpt4: bool = False enable_openrouter_llama: bool = False enable_openrouter_mistral: bool = False enable_mistral: bool = False # Mistral direct API enable_local_finetuned: bool = True # Fine-tuned Swin Transformer (92% AI recall) # ── V3 detector toggles ───────────────────────────────────── enable_frequency_domain: bool = True # FFT spectrum ResNet-18 (Step 2) enable_clip_unifd: bool = True # CLIP ViT-L/14 linear head (Step 3, needs open_clip) enable_dire: bool = True # Diffusion reconstruction error (Step 4 — vae_proxy on CPU, full_dire on GPU) enable_face_specialist: bool = False # F3-Net face forensics (Step 6) enable_siglip2_aigc: bool = True # SigLIP2-base open-deepfake-detection (Nov 2025) enable_npr: bool = True # NPR ResNet-50 upsampling artifact detector (CVPR 2024) use_meta_learner_fusion: bool = False # XGBoost/LR meta-learner (Step 5; False = Bayesian log-odds fallback) # fusion_strategy: "weighted" (Bayesian log-odds) | "voting" (majority vote, each detector 1 vote) fusion_strategy: str = "weighted" # Voting threshold: fraction of detectors that must vote AI to return AI_GENERATED (default 0.5 = majority) voting_threshold: float = 0.5 # ── V1 HybridForensicsModel paths ─────────────────────────── # Path to the trained checkpoint file (hybrid_detector.pt) hybrid_model_checkpoint: str = str( _PROJECT_ROOT.parent / "Deepfake-V1" / "Professional_Integration" / "ml" / "checkpoints" / "hybrid_detector.pt" ) # Path to Professional_Integration/ so `import ml` works hybrid_model_code_root: str = str( _PROJECT_ROOT.parent / "Deepfake-V1" / "Professional_Integration" ) # Model versions: latest and backup hybrid_model_latest_checkpoint: str = str( _PROJECT_ROOT.parent / "Deepfake-V1" / "Professional_Integration" / "ml" / "checkpoints" / "hybrid_detector.pt" ) hybrid_model_backup_checkpoint: str = str( _PROJECT_ROOT.parent / "Deepfake-V1" / "Professional_Integration" / "ml" / "checkpoints" / "hybrid_detector_v1_backup.pt" ) # Default model version to use (latest or backup) hybrid_model_default_version: str = "latest" # ── Fusion weights (sum need not equal 1 — normalised at runtime) ── # Higher = more trusted. Tune after benchmarking. weight_hive: float = 2.5 # purpose-built, high accuracy weight_hive_vlm: float = 2.0 # Hive VLM — strong vision reasoning weight_aiornot: float = 2.0 # diverse training set weight_sightengine: float = 3.0 # primary signal weight_mistral: float = 2.2 # direct Mistral API — strong multimodal weight_claude: float = 2.2 # Anthropic VLM — strong reasoning weight_gpt4: float = 1.5 weight_gemini: float = 0.4 # low weight: Gemini Vision mislabels its own generated images as real weight_gemma_vision: float = 1.3 weight_openrouter_gpt4: float = 2.0 weight_openrouter_llama: float = 1.5 weight_openrouter_mistral: float = 1.4 weight_local_forensics: float = 0.6 # supporting signal only weight_local_finetuned: float = 2.5 # fine-tuned Swin: 92% AI recall on domain data weight_hybrid_model: float = 1.5 # V3 detector weights weight_frequency_domain: float = 1.5 # trained ResNet-18 on FFT spectra weight_clip_unifd: float = 1.2 # UniFD linear head — can FP on compressed images weight_dire: float = 1.5 # DIRE reconstruction error (Step 4) weight_face_specialist: float = 0.5 # unreliable on non-manipulation AI images weight_siglip2_aigc: float = 2.0 # SigLIP2 Nov 2025 — strong on diffusion + AI fashion weight_npr: float = 1.8 # NPR CVPR 2024 — upsampling artifacts, fast CPU # ── Confidence-based gating thresholds ────────────────────── # Used to skip expensive detectors if cheap ones are already confident enable_gating: bool = True gating_confidence_threshold: float = 0.6 # confidence needed to skip Tier 2+ gating_p_fake_margin: float = 0.2 # |p_fake - 0.5| margin for early exit # ── Verdict thresholds ────────────────────────────────────── # Calibrated for accuracy on modern diffusion images (Apr 20): # Lower ai_threshold so fused log-odds actually reaches it; # raise real_threshold so weak REAL signals don't mask AI images. ai_threshold: float = 0.42 # lowered: Sightengine alone can push past this real_threshold: float = 0.28 # real images cluster near 0.02-0.04 so safe to lower # between thresholds → UNCERTAIN # ── HTTP ──────────────────────────────────────────────────── request_timeout: float = 20.0 # seconds per API call # ── MongoDB ────────────────────────────────────────────────── mongodb_url: str = "mongodb://localhost:27017" mongodb_database: str = "deepshield" @property def sightengine_pairs(self) -> List[Tuple[str, str]]: """Parse Sightengine credentials into (user, secret) pairs for failover. Supports both: - Multiple keys: SIGHTENGINE_API_USERS=user1,user2 SIGHTENGINE_API_SECRETS=secret1,secret2 - Single key (backward compat): SIGHTENGINE_API_USER=user SIGHTENGINE_API_SECRET=secret """ # Try multi-key format first if self.sightengine_api_users and self.sightengine_api_secrets: users = [u.strip() for u in self.sightengine_api_users.split(",") if u.strip()] secrets = [s.strip() for s in self.sightengine_api_secrets.split(",") if s.strip()] pairs = list(zip(users, secrets)) if pairs: return pairs # Fall back to single-key format if self.sightengine_api_user and self.sightengine_api_secret: return [(self.sightengine_api_user, self.sightengine_api_secret)] return [] settings = Settings()