wrolpi/modules/ai/controller_client.py
Roland 318b835faf
ai: package llama-server for all three deployments, with idle unload
install_llama_cpp.sh builds llama-server from the SHA-pinned v0.3.0
source tarball (single source of truth: on-device upgrade, Pi image,
Live ISO, and the new docker/llm container all call it; version-marker
makes re-runs no-ops).  start_llama_server.sh refuses to start unless
ai.yaml enables AI and the active model file exists — which also covers
unmounted media.  wrolpi-llm.service is installed but deliberately
never enabled at boot: the Controller starts it on demand (registered
in managed_services; the llm compose service follows the samba
pattern), and the API's perpetual idle-unload worker asks the
Controller to stop it after the configured idle minutes.  llama-server
is localhost-only natively, internal-network-only in docker, and never
proxied by Caddy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-13 20:10:16 -06:00

37 lines
1.5 KiB
Python

"""One helper for API -> Controller requests.
Existing call sites disagree on the base URL (wrolpi/common.py uses http://127.0.0.1, the zim
module uses the docker service name); this module resolves it once: the docker service name when
DOCKERIZED, else localhost, overridable with the CONTROLLER_URL environment variable.
"""
import os
from typing import Tuple
from urllib.parse import urlencode
from wrolpi.common import aiohttp_get, aiohttp_post, logger
from wrolpi.vars import DOCKERIZED
logger = logger.getChild(__name__)
CONTROLLER_URL = os.environ.get('CONTROLLER_URL') or ('http://controller' if DOCKERIZED else 'http://127.0.0.1')
async def controller_get(path: str, params: dict = None, timeout: int = 10) -> Tuple[int, dict]:
"""GET a Controller endpoint, returning (status, JSON body). Raises on connection errors."""
url = f'{CONTROLLER_URL}{path}'
if params:
params = {k: v for k, v in params.items() if v is not None}
if params:
url = f'{url}?{urlencode(params)}'
async with aiohttp_get(url, timeout=timeout) as response:
return response.status, await response.json()
async def controller_post(path: str, json_: dict = None, timeout: int = 30) -> Tuple[int, dict]:
"""POST a Controller endpoint, returning (status, JSON body). Raises on connection errors."""
async with aiohttp_post(f'{CONTROLLER_URL}{path}', json_=json_ or {}, timeout=timeout) as response:
try:
body = await response.json()
except Exception:
body = {}
return response.status, body