"""Centralised config. Reads .env if present. Paths default to ../data which is symlinked to the existing artefacts so we don't duplicate 20+GB. Override via env vars for production. """ from __future__ import annotations import os from pathlib import Path try: from dotenv import load_dotenv load_dotenv(Path(__file__).resolve().parents[1] / ".env", override=False) except ImportError: pass PROJECT_ROOT = Path(__file__).resolve().parents[1] # v2 layout puts artefacts at project root (index/, kcc_adapter/, etc.), not # under a data/ subdir. Default DATA_DIR to PROJECT_ROOT so the existing # v2 file layout is reachable by v3's vendored modules. DATA_DIR = Path(os.environ.get("DATA_DIR", str(PROJECT_ROOT))) LOGS_DIR = PROJECT_ROOT / "logs" LOGS_DIR.mkdir(parents=True, exist_ok=True) # Index artefacts INDEX_DIR = DATA_DIR / "index" FAISS_INDEX_FILE = INDEX_DIR / "faiss.index" METADATA_FILE = INDEX_DIR / "metadata.parquet" BM25_INDEX_FILE = INDEX_DIR / "bm25.db" # Knowledge / training data GOLDEN_SET_FILE = DATA_DIR / "kcc_golden_set.json" DOSE_TABLE_FILE = DATA_DIR / "kcc_dose_table.json" LLAMA_TRAIN_FILE = DATA_DIR / "llama_training_data.jsonl" LLAMA_VAL_FILE = DATA_DIR / "llama_training_val.jsonl" # Models KCC_ADAPTER_DIR = DATA_DIR / "kcc_adapter" PEST_MODEL_DIR = DATA_DIR / "pest_model" MANDI_DIR = DATA_DIR / "mandi_advisor" EARLY_WARNING_DIR = DATA_DIR / "early_warning" BLOCK_FEATURES_FILE = DATA_DIR / "block_features.parquet" # Embedding / retrieval # NOTE: change to "BAAI/bge-m3" after running pipelines/reindex_bge_m3.py. EMBEDDING_MODEL = os.environ.get("EMBEDDING_MODEL", "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2") EMBEDDING_DIM = int(os.environ.get("EMBEDDING_DIM", "384")) EMBED_BATCH_SIZE = int(os.environ.get("EMBED_BATCH_SIZE", "256")) RERANKER_MODEL = os.environ.get("RERANKER_MODEL", "cross-encoder/mmarco-mMiniLMv2-L12-H384-v1") # Upgrade to "BAAI/bge-reranker-v2-m3" once you've benchmarked it on your eval set. TOP_K = 5 RERANKER_TOP_N = 20 MIN_ANSWER_CHARS = 100 LOW_CONF_THRESHOLD = 0.45 # below → trigger HyDE + add "low-confidence" prompt note HYDE_TRIGGER_THRESHOLD = 0.40 # below → run HyDE rewrite # LLM cascade GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "") GROQ_MODEL_PRIMARY = "meta-llama/llama-4-scout-17b-16e-instruct" GROQ_MODEL_FALLBACK = "llama-3.3-70b-versatile" GROQ_MODELS = [GROQ_MODEL_PRIMARY, GROQ_MODEL_FALLBACK, "llama-3.1-8b-instant"] GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "") GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-2.0-flash") GEMINI_FALLBACK_MODELS = [GEMINI_MODEL, "gemini-1.5-flash"] # Local LoRA fine-tune (last-resort offline) USE_LOCAL_LLAMA = os.environ.get("USE_LOCAL_LLAMA", "false").lower() == "true" LOCAL_LLAMA_BASE = "unsloth/llama-3.2-3b-instruct-unsloth-bnb-4bit" # External APIs (free) DATA_GOV_API_KEY = os.environ.get("DATA_GOV_API_KEY", "") OPEN_METEO_BASE = "https://api.open-meteo.com/v1/forecast" # Auth & deployment ENVIRONMENT = os.environ.get("ENVIRONMENT", "development") LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO") ALLOWED_ORIGINS = [o.strip() for o in os.environ.get( "ALLOWED_ORIGINS", "http://localhost:3000,http://localhost:5173").split(",") if o.strip()] JWT_SECRET = os.environ.get("JWT_SECRET", "") JWT_ALGORITHM = "HS256" JWT_EXP_HOURS = 24 B2B_USERS_RAW = os.environ.get("B2B_USERS", "") # "user:hash;user2:hash2" # Hard-fail in production if secrets are weak. if ENVIRONMENT == "production": if not JWT_SECRET or len(JWT_SECRET) < 32: raise RuntimeError( "JWT_SECRET must be set to a >=32 char value in production. " "Generate one with: python -c 'import secrets; print(secrets.token_hex(32))'" ) if not B2B_USERS_RAW: raise RuntimeError( "B2B_USERS must be configured in production " "(format: user:bcrypt_hash;user2:hash). Demo creds are NOT acceptable." ) # Data quality START_YEAR = 2006 QUERY_COL = "QueryText" ANSWER_COL = "KccAns"