| # app/config.py - Application configuration (Production Hardened) | |
| from pydantic_settings import BaseSettings, SettingsConfigDict | |
| from typing import Optional | |
| from functools import lru_cache | |
| from dotenv import load_dotenv | |
| import os | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # FIX 1: HF-SAFE DOTENV LOADING | |
| # On HuggingFace, secrets are injected at runtime. Don't override them. | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| if os.getenv("HF_SPACE_ID") is None: | |
| load_dotenv(override=True) # Local dev โ .env | |
| else: | |
| load_dotenv(override=False) # HF prod โ Secrets only | |
| class Settings(BaseSettings): | |
| """Application settings loaded from environment variables.""" | |
| # Application | |
| APP_NAME: str = "Scam Honeypot API" | |
| VERSION: str = "2.5.0" | |
| DEBUG: bool = False | |
| GUVI_API_KEY: str = "" # Must be set via Environment Variable (HF Secrets) | |
| GUVI_CALLBACK_URL: str = "https://hackathon.guvi.in/api/updateHoneyPotFinalResult" | |
| # SOC Hardening (SIEM Integration) | |
| SYSLOG_ENABLED: bool = False | |
| SYSLOG_HOST: str = "localhost" | |
| SYSLOG_PORT: int = 514 | |
| # LLM Configuration | |
| LLM_PROVIDER: str = "groq" | |
| OPENAI_API_KEY: Optional[str] = None | |
| ANTHROPIC_API_KEY: Optional[str] = None | |
| GROQ_API_KEY: Optional[str] = None | |
| OPENROUTER_API_KEY: Optional[str] = None | |
| # Local HF (Offline / Free-Tier) Inference | |
| # FIX: Enable by default as crash-proof fallback when Groq fails | |
| USE_LOCAL_HF_MODEL: bool = True # Auto-fallback when API fails | |
| HF_LOCAL_MODEL_NAME: str = "facebook/opt-125m" # Tiny model, runs on CPU | |
| HF_LOCAL_MAX_TOKENS: int = 80 # Short responses like real SMS/chat | |
| HF_LOCAL_DEVICE: str = "cpu" # Explicit so HF Spaces & local dev behave consistently | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # FIX 2: EXPLICIT MODEL DEFAULTS (No None = No Surprises) | |
| # Aligned with Groq limits and capability-aware fallback chains | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| GPT_MODEL: str = "gpt-4-turbo-preview" | |
| CLAUDE_MODEL: str = "claude-3-sonnet-20240229" | |
| GROQ_MODEL: str = "llama-3.3-70b-versatile" | |
| # Per-task model routing (Production Grade) | |
| MAX_RETRIES: int = 2 | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # FIX: HARD LLM CALL LIMITS (Prevent rate limit exhaustion) | |
| # OPTIMIZED for GUVI's 30s timeout & Groq free tier rate limits | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| MAX_LLM_CALLS_PER_REQUEST: int = 6 # Reduced from 10 - fits within 20s timeout | |
| MAX_LLM_CALLS_PER_SESSION: int = 20 # Reduced from 30 - prevents key exhaustion | |
| # Per-task model routing (Production Grade) | |
| # FIX: llama-3.3-70b fails JSON_SCHEMA mode with 400. Use qwen3-32b for structured. | |
| GROQ_FAST_MODEL: str = "llama-3.1-8b-instant" # Blazing fast (~300 tokens/sec) | |
| GROQ_SMART_MODEL: str = "qwen/qwen3-32b" # Native reasoning, 60 RPM | |
| GROQ_NATURAL_MODEL: str = "moonshotai/kimi-k2-instruct-0905" | |
| GROQ_STRUCTURED_MODEL: str = "qwen/qwen3-32b" # FIX: Supports JSON_SCHEMA properly | |
| GROQ_SAFETY_MODEL: str = "meta-llama/llama-guard-4-12b" | |
| GROQ_SAFEGUARD_MODEL: str = "openai/gpt-oss-safeguard-20b" | |
| OPENROUTER_MODEL: str = "meta-llama/llama-3.1-70b-instruct" | |
| # LLM parameters | |
| LLM_TEMPERATURE: float = 0.7 | |
| LLM_MAX_TOKENS: int = 500 | |
| # Conversation | |
| MAX_CONVERSATION_LENGTH: int = 50 | |
| CONVERSATION_TTL_HOURS: int = 24 | |
| # Rate Limiting | |
| RATE_LIMIT_PER_MINUTE: int = 60 | |
| # Feature Flags | |
| ENABLE_LLM_DETECTION: bool = True | |
| ENABLE_LLM_RESPONSES: bool = True | |
| ENABLE_THREAT_INTELLIGENCE: bool = True | |
| ENABLE_LAW_ENFORCEMENT_API: bool = False # Disabled for hackathon | |
| ENABLE_ENGAGEMENT_DELAY: bool = False | |
| # Forensic Clinic (Compound Systems) | |
| ENABLE_MATH_FORENSICS: bool = False # ๐งฎ Claim Verifier (Compound-Mini) | |
| ENABLE_VISUAL_EVIDENCE: bool = False # ๐ Chart Lab (Compound) | |
| # Database (SQLite default, PostgreSQL/Supabase via env) | |
| DATABASE_URL: str = "sqlite+aiosqlite:///./data/honeypot.db" | |
| # Compliance | |
| SANDBOX_MODE: bool = False | |
| ANONYMIZE_LOGS: bool = True | |
| SYNTHETIC_DATA_ONLY: bool = False | |
| model_config = SettingsConfigDict( | |
| env_file=".env", | |
| env_file_encoding="utf-8", | |
| case_sensitive=True, | |
| extra="allow" | |
| ) | |
| def get_settings() -> Settings: | |
| return Settings() | |
| settings = get_settings() | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # FIX 3 & 4: STARTUP VALIDATION (Fail Fast) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def validate_production_config(): | |
| """Validates critical config at startup. Raises RuntimeError if invalid.""" | |
| errors = [] | |
| # FIX 3: GUVI_API_KEY must be set for scoring | |
| if not settings.GUVI_API_KEY: | |
| errors.append("GUVI_API_KEY missing โ scoring impossible") | |
| # FIX 4: Exactly ONE *external* LLM provider key must be set | |
| # EXCEPTION: When USE_LOCAL_HF_MODEL=True we allow zero external keys | |
| active_keys = [ | |
| ("GROQ_API_KEY", settings.GROQ_API_KEY), | |
| ("OPENAI_API_KEY", settings.OPENAI_API_KEY), | |
| ("ANTHROPIC_API_KEY", settings.ANTHROPIC_API_KEY), | |
| ("OPENROUTER_API_KEY", settings.OPENROUTER_API_KEY), | |
| ] | |
| set_keys = [(name, key) for name, key in active_keys if key] | |
| if len(set_keys) == 0 and not settings.USE_LOCAL_HF_MODEL: | |
| errors.append("No LLM API key set โ system cannot function (set USE_LOCAL_HF_MODEL=True to enable offline mode)") | |
| elif len(set_keys) > 1: | |
| key_names = [name for name, _ in set_keys] | |
| errors.append(f"Multiple LLM API keys set ({', '.join(key_names)}) โ please use exactly one") | |
| # In production, fail hard | |
| if errors and not settings.DEBUG: | |
| raise RuntimeError("๐จ CONFIG VALIDATION FAILED:\n - " + "\n - ".join(errors)) | |
| elif errors: | |
| # In debug mode, just warn | |
| import warnings | |
| for err in errors: | |
| warnings.warn(f"โ ๏ธ CONFIG WARNING: {err}") | |
| # Run validation at import time (fail fast) | |
| validate_production_config() | |