"""Central configuration, loaded from environment / .env.""" from __future__ import annotations import os from dataclasses import dataclass from dotenv import load_dotenv load_dotenv() def _int(name: str, default: int) -> int: try: return int(os.getenv(name, str(default))) except ValueError: return default @dataclass(frozen=True) class Config: # LLM llm_provider: str = os.getenv("LLM_PROVIDER", "anthropic").lower() ollama_model: str = os.getenv("OLLAMA_MODEL", "qwen2.5:7b-instruct") ollama_host: str = os.getenv("OLLAMA_HOST", "http://localhost:11434") anthropic_api_key: str = os.getenv("ANTHROPIC_API_KEY", "") anthropic_model: str = os.getenv("ANTHROPIC_MODEL", "claude-haiku-4-5-20251001") # Embeddings embed_model: str = os.getenv("EMBED_MODEL", "sentence-transformers/all-MiniLM-L6-v2") # Retrieval / chunking top_k: int = _int("TOP_K", 6) chunk_target_words: int = _int("CHUNK_TARGET_WORDS", 180) chunk_overlap_words: int = _int("CHUNK_OVERLAP_WORDS", 30) # Storage chroma_dir: str = os.getenv("CHROMA_DIR", "data/chroma") transcript_dir: str = os.getenv("TRANSCRIPT_DIR", "data/transcripts") collection_name: str = os.getenv("COLLECTION_NAME", "youtube_kb") # Durable decision store. When DATABASE_URL is set (Neon Postgres on the deployed Space), the # audit log persists there so it survives container restarts/rebuilds; otherwise it falls back # to the local JSONL file at `audit_log_path` (fine for local dev and the offline tests). database_url: str = os.getenv("DATABASE_URL", "") # --- Triage copilot (T&S) --- policy_path: str = os.getenv("POLICY_PATH", "data/policy.md") campaigns_dir: str = os.getenv("CAMPAIGNS_DIR", "data/campaigns") sanctions_path: str = os.getenv("SANCTIONS_PATH", "data/sanctions.json") audit_log_path: str = os.getenv("AUDIT_LOG_PATH", "data/audit_log.jsonl") policy_collection: str = os.getenv("POLICY_COLLECTION", "policy_rules") cases_collection: str = os.getenv("CASES_COLLECTION", "past_cases") # Public-demo lockdown (set PUBLIC_DEMO=1 on the deployed Space). When on, the billed # /api/triage endpoint refuses to bypass its per-campaign cache and serves Anthropic only, so # total spend is bounded by construction to the 18 fixed campaigns once each (then cache hits). public_demo: bool = os.getenv("PUBLIC_DEMO", "").strip().lower() in ("1", "true", "yes", "on") CONFIG = Config()