Spaces:
Running
Running
| """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) | |