"""Central configuration for LifeOS. All environment-driven knobs live here so the rest of the code never reaches into os.environ directly. Values are read once at import. A local .env file (if present, gitignored) is loaded first — no external dependency, just a tiny KEY=VALUE parser — so secrets and machine-specific paths stay out of the code. Copy .env.example to .env to override any of these. """ import os ROOT = os.path.dirname(os.path.abspath(__file__)) def _load_dotenv(path: str) -> None: """Minimal .env loader: KEY=VALUE lines, # comments, optional quotes. Does not overwrite variables already set in the real environment.""" if not os.path.exists(path): return with open(path, "r", encoding="utf-8") as f: for raw in f: line = raw.strip() if not line or line.startswith("#") or "=" not in line: continue key, val = line.split("=", 1) key = key.strip() val = val.strip().strip('"').strip("'") os.environ.setdefault(key, val) _load_dotenv(os.path.join(ROOT, ".env")) def _flag(name: str, default: bool = False) -> bool: return os.environ.get(name, "1" if default else "0").lower() in ("1", "true", "yes", "on") def _int(name: str, default: int) -> int: try: return int(os.environ.get(name, default)) except (TypeError, ValueError): return default # --- runtime mode ------------------------------------------------------ # Demo mode seeds a sample persona + long-term notes so the app looks alive # for screenshots/demos. Real installs leave this off and start blank. DEMO = _flag("LIFEOS_DEMO", default=False) # --- inference --------------------------------------------------------- # GPU offload layers for llama.cpp: -1 = all, 0 = CPU only. Needs a CUDA/Metal # build of llama-cpp-python; the plain CPU wheel ignores it. GPU_LAYERS = _int("LIFEOS_GPU_LAYERS", -1) # The vision model is occasional (one shot per food photo) and would otherwise # fight the always-resident text model for VRAM — on a small card loading both # on the GPU OOMs. Default it to CPU so it never disrupts the interactive chat # path; set LIFEOS_VLM_GPU_LAYERS=-1 on a large-VRAM card for faster photos. VLM_GPU_LAYERS = _int("LIFEOS_VLM_GPU_LAYERS", 0) # Text reasoning model (Nemotron-3-Nano-4B). MODEL_REPO = os.environ.get("LIFEOS_MODEL_REPO", "nvidia/NVIDIA-Nemotron-3-Nano-4B-GGUF") MODEL_FILE = os.environ.get("LIFEOS_MODEL_FILE", "NVIDIA-Nemotron3-Nano-4B-Q4_K_M.gguf") # Vision model for food-photo recognition (Qwen2.5-VL-3B). VLM_REPO = os.environ.get("LIFEOS_VLM_REPO", "ggml-org/Qwen2.5-VL-3B-Instruct-GGUF") VLM_FILE = os.environ.get("LIFEOS_VLM_FILE", "Qwen2.5-VL-3B-Instruct-Q4_K_M.gguf") VLM_MMPROJ_FILE = os.environ.get("LIFEOS_VLM_MMPROJ_FILE", "mmproj-Qwen2.5-VL-3B-Instruct-f16.gguf") # Longest-side pixel cap for photos sent to the VLM. Full-res photos decode # ~1000 image tokens on the CPU path (~30s); 768px is ~4x faster with no loss # in food-recognition quality. Raise it if you offload the VLM to a big GPU. VLM_MAX_IMAGE_SIDE = _int("LIFEOS_VLM_MAX_IMAGE_SIDE", 768) # Embedding model for long-term RAG (nomic-embed-text-v1.5). EMBED_REPO = os.environ.get("LIFEOS_EMBED_REPO", "nomic-ai/nomic-embed-text-v1.5-GGUF") EMBED_FILE = os.environ.get("LIFEOS_EMBED_FILE", "nomic-embed-text-v1.5.Q8_0.gguf") # Use the GPU for OCR (EasyOCR) when a CUDA torch build is present. OCR_GPU = _flag("LIFEOS_OCR_GPU", default=True) # --- server ------------------------------------------------------------ # Bind host: defaults to 0.0.0.0 on Hugging Face Spaces, else Gradio's loopback. HOST = os.environ.get("LIFEOS_HOST") or ("0.0.0.0" if os.environ.get("SPACE_ID") else None) PORT = _int("LIFEOS_PORT", 7860)