pagepiper/app/config.py
pyr0ball 76aaabd857 feat: rename CF_ORCH_URL to GPU_SERVER_URL for self-hoster clarity
CF_ORCH_URL makes sense internally but isn't self-explanatory for a
local-first user setting up their own GPU rig. Follows the GPU_SERVER_URL
convention Kiwi already established (app/core/config.py there).

- app/config.py: resolves GPU_SERVER_URL -> CF_ORCH_URL (back-compat
  alias) -> https://orch.circuitforge.tech default when CF_LICENSE_KEY
  is present (Paid+ tiers). Written back to os.environ["CF_ORCH_URL"]
  so existing callers (get_llm_config, app/api/chat.py) needed zero
  changes.
- .env.example / .env.cloud.example: document GPU_SERVER_URL as the
  primary name, CF_ORCH_URL noted as a still-honoured legacy alias.
- compose.cloud.yml: the hardcoded coordinator URL now sets
  GPU_SERVER_URL instead of CF_ORCH_URL directly (relies on the same
  config.py normalization).
- docs/reference/environment-variables.md updated.
- 6 new tests covering the resolution priority chain and the
  write-back behavior legacy callers depend on.

Closes: #10
2026-07-10 17:23:27 -07:00

78 lines
2.6 KiB
Python

"""Configuration from environment variables — no file parsing required for basic use."""
from __future__ import annotations
import os
from pathlib import Path
DATA_DIR = Path(os.environ.get("PAGEPIPER_DATA_DIR", "data"))
DATA_DIR.mkdir(parents=True, exist_ok=True)
DB_PATH = str(DATA_DIR / "pagepiper.db")
VEC_DB_PATH = str(DATA_DIR / "pagepiper_vecs.db")
WATCH_DIR = Path(os.environ.get("PAGEPIPER_WATCH_DIR", "books"))
VEC_DIMENSIONS = int(os.environ.get("PAGEPIPER_EMBED_DIMS", "1024"))
LOCAL_USER_ID = "__local__"
CF_LICENSE_KEY = os.environ.get("CF_LICENSE_KEY")
# Priority: GPU_SERVER_URL env var -> CF_ORCH_URL env var (backward compat)
# -> https://orch.circuitforge.tech when CF_LICENSE_KEY is present (Paid+)
# Resolved value is written back to os.environ["CF_ORCH_URL"] so every existing
# caller that reads os.environ.get("CF_ORCH_URL") directly (get_llm_config below,
# app/api/chat.py) sees the right URL without any further changes.
GPU_SERVER_URL = (
os.environ.get("GPU_SERVER_URL")
or os.environ.get("CF_ORCH_URL")
or ("https://orch.circuitforge.tech" if CF_LICENSE_KEY else None)
)
if GPU_SERVER_URL:
os.environ["CF_ORCH_URL"] = GPU_SERVER_URL
def user_data_dir(user_id: str) -> Path:
"""Return (and create) the per-user data directory under DATA_DIR/users/."""
d = DATA_DIR / "users" / user_id
d.mkdir(parents=True, exist_ok=True)
return d
def get_llm_config() -> dict | None:
"""Build LLMRouter config from env vars.
Returns None only when neither PAGEPIPER_OLLAMA_URL nor CF_ORCH_URL is set.
CF_ORCH_URL alone is sufficient — the coordinator resolves the service URL at
allocation time so PAGEPIPER_OLLAMA_URL becomes optional.
"""
url = os.environ.get("PAGEPIPER_OLLAMA_URL", "").strip()
orch_url = os.environ.get("CF_ORCH_URL", "").strip()
if not url and not orch_url:
return None
chat_model = os.environ.get("PAGEPIPER_CHAT_MODEL", "mistral:7b")
_base_url = ""
if url:
_clean = url.rstrip("/")
_base_url = _clean if _clean.endswith("/v1") else _clean + "/v1"
backend: dict = {
"type": "openai_compat",
"base_url": _base_url,
"model": chat_model,
"embedding_model": os.environ.get("PAGEPIPER_EMBED_MODEL", "nomic-embed-text"),
"supports_images": False,
}
if orch_url:
backend["cf_orch"] = {
"service": os.environ.get("PAGEPIPER_ORCH_SERVICE", "ollama"),
"model_candidates": [chat_model],
"ttl_s": 3600,
}
return {
"fallback_order": ["ollama"],
"backends": {"ollama": backend},
}