"""Environment-driven configuration.""" from __future__ import annotations import os from dataclasses import dataclass from pathlib import Path from dotenv import load_dotenv load_dotenv() @dataclass(frozen=True) class Config: local_base_url: str local_model: str mi50_base_url: str # OpenAI-compatible llama.cpp server on the MI50 box mi50_model: str openai_api_key: str cloud_model: str # cloud model for bulk/consolidation work (cheap) chat_model: str # cloud model for live chat (stronger; persona fidelity) embed_backend: str # "cloud" (OpenAI) or "local" (Ollama) embed_model: str # OpenAI embedding model local_embed_model: str # Ollama embedding model embed_base_url: str # Ollama endpoint for embeddings (own box, decoupled from local chat) summary_backend: str # "local" or "cloud" — backend used to compact memory db_path: Path # Proactive reach-out (ntfy push). Empty ntfy_url disables pinging. ntfy_url: str # base url, e.g. "http://10.0.0.41:8090" ntfy_topic: str # topic to publish to, e.g. "lyra" web_url: str # base url of the Lyra web app, for push tap-through links timezone: str # IANA tz for quiet hours / local time ping_salience: float # min thought salience to push (eager = ~0.7) ping_cooldown_min: int # min minutes between pushes (eager = 0) ping_quiet_hours: str # local "start-end" 24h window to stay silent, e.g. "1-9" # External input feed (her #1: react to the world). Comma-separated RSS/Atom URLs. feeds: tuple[str, ...] feed_react_prob: float # chance a would-be new thread reacts to a feed item instead def _csv(name: str, default: str) -> tuple[str, ...]: raw = os.getenv(name, default) return tuple(u.strip() for u in raw.split(",") if u.strip()) def load() -> Config: return Config( local_base_url=os.getenv("LOCAL_BASE_URL", "http://localhost:11434"), local_model=os.getenv("LOCAL_MODEL", "qwen2.5:7b-instruct"), mi50_base_url=os.getenv("MI50_BASE_URL", "http://10.0.0.42:8080/v1"), mi50_model=os.getenv("MI50_MODEL", "local-gpu"), openai_api_key=os.getenv("OPENAI_API_KEY", ""), cloud_model=os.getenv("CLOUD_MODEL", "gpt-4o-mini"), chat_model=os.getenv("CHAT_MODEL", "gpt-4o"), embed_backend=os.getenv("EMBED_BACKEND", "cloud").lower(), embed_model=os.getenv("EMBED_MODEL", "text-embedding-3-small"), local_embed_model=os.getenv("LOCAL_EMBED_MODEL", "nomic-embed-text"), # Embeddings can live on their own always-on box, separate from the local # chat backend. Defaults to LOCAL_BASE_URL so existing setups are unchanged. embed_base_url=os.getenv("EMBED_BASE_URL", os.getenv("LOCAL_BASE_URL", "http://localhost:11434")), summary_backend=os.getenv("SUMMARY_BACKEND", "local").lower(), db_path=Path(os.getenv("LYRA_DB_PATH", "data/lyra.db")), ntfy_url=os.getenv("NTFY_URL", "").rstrip("/"), ntfy_topic=os.getenv("NTFY_TOPIC", "lyra"), web_url=os.getenv("LYRA_WEB_URL", "").rstrip("/"), timezone=os.getenv("LYRA_TIMEZONE", "America/New_York"), ping_salience=float(os.getenv("PING_SALIENCE", "0.7")), ping_cooldown_min=int(os.getenv("PING_COOLDOWN_MIN", "0")), ping_quiet_hours=os.getenv("PING_QUIET_HOURS", "1-9"), feeds=_csv("LYRA_FEEDS", "https://hnrss.org/frontpage,https://www.pokernews.com/rss.php"), feed_react_prob=float(os.getenv("FEED_REACT_PROB", "0.5")), )