diff --git a/CloudronManifest.json b/CloudronManifest.json index c96b5be..2036ded 100644 --- a/CloudronManifest.json +++ b/CloudronManifest.json @@ -16,7 +16,13 @@ "addons": { "postgresql": {}, "redis": {}, - "localstorage": {} + "localstorage": {}, + "scheduler": { + "audio_keepalive": { + "schedule": "*/10 * * * *", + "command": "/app/code/audio_keepalive.py" + } + } }, "tags": [ "ai", diff --git a/Dockerfile b/Dockerfile index 2a936ed..d9c39b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,11 @@ COPY config.yaml /app/data/config.yaml COPY patch_budget_message.py /app/code/patch_budget_message.py RUN python3 /app/code/patch_budget_message.py +# Audio keep-alive: scheduled pings keep the Tinfoil TEE audio enclave +# warm so voice mode skips the ~24s cold-start (see audio_keepalive.py header). +COPY audio_keepalive.py /app/code/audio_keepalive.py +RUN chmod +x /app/code/audio_keepalive.py + # Override the base image's ENTRYPOINT (which is `litellm`) so our # startup script runs instead of being passed as an argument to litellm. ENTRYPOINT ["/app/code/start.sh"] diff --git a/audio_keepalive.py b/audio_keepalive.py new file mode 100644 index 0000000..508cafe --- /dev/null +++ b/audio_keepalive.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Audio keep-alive: ping the Tinfoil TEE audio models through localhost LiteLLM +so voice mode doesn't pay the ~24s cold-start after idle. + +Runs INSIDE the gateway container via the Cloudron scheduler addon. Uses the +LITELLM_MASTER_KEY already in the container env. TTS-only (STT pings cost +$0.01/request each; TTS $0.002 — see docs, keep-alive economics). Silent on +success; prints + exits non-zero on failure so scheduler logs capture it. +""" +import json, os, urllib.request, urllib.error, sys + +PORT = os.environ.get("PORT", "4000") +KEY = os.environ.get("LITELLM_MASTER_KEY", "") +if not KEY: + print("keepalive: LITELLM_MASTER_KEY not set", file=sys.stderr) + sys.exit(1) + +fail = 0 + +# TTS warm (voxtral-tts) — $0.002/request (Tinfoil request-priced) +req = urllib.request.Request( + f"http://127.0.0.1:{PORT}/v1/audio/speech", + data=json.dumps({"model": "tinfoil/voxtral-tts", "input": "ok", + "voice": "casual_female"}).encode(), + method="POST", + headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}) +try: + urllib.request.urlopen(req, timeout=30) +except Exception as e: + print(f"keepalive: TTS ping failed: {e}", file=sys.stderr) + fail = 1 + +# STT warm disabled 2026-10-11: $0.01/STT request × 6/hr ≈ $52/mo. Revisit only +# with a cheaper warm strategy. +sys.exit(fail)