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
78 lines
2.6 KiB
Python
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},
|
|
}
|