-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathstart_app.py
More file actions
111 lines (89 loc) · 3.96 KB
/
Copy pathstart_app.py
File metadata and controls
111 lines (89 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
"""Databricks Apps entrypoint (pure Python — no bash, no Node).
Starts the stdlib web server (web_server.py) immediately so the app port
binds within seconds, then boots the LiveKit agent worker behind it in a
private venv at /tmp/agent-venv. The platform's shared Python environment
collides with the agent's pinned dependencies, so it is never touched:
deps live in agent-requirements.txt (a name the platform auto-installer
ignores).
"""
import os
import shutil
import signal
import subprocess
import sys
import threading
import time
from pathlib import Path
ROOT = Path(__file__).parent
os.chdir(ROOT)
# Apps route traffic to port 8000.
os.environ.setdefault("DATABRICKS_APP_PORT", "8000")
os.environ["PYTHONUNBUFFERED"] = "1"
# The agent authenticates with the PAT injected from the app's secret
# resources; the runtime also injects OAuth client creds for the app's
# service principal, and the Databricks SDK refuses ambiguous auth.
os.environ.pop("DATABRICKS_CLIENT_ID", None)
os.environ.pop("DATABRICKS_CLIENT_SECRET", None)
# The runtime may inject DATABRICKS_HOST without a scheme; the agent builds
# URLs from it (AI gateway base, OTLP endpoint).
_host = os.environ.get("DATABRICKS_HOST", "")
if _host and not _host.startswith("http"):
os.environ["DATABRICKS_HOST"] = f"https://{_host}"
def log(msg: str) -> None:
print(f"[boot] {msg}", flush=True)
log(f"python {sys.version.split()[0]} at {sys.executable}")
web = subprocess.Popen([sys.executable, "web_server.py"])
log(f"web server pid {web.pid} binding 0.0.0.0:{os.environ['DATABRICKS_APP_PORT']}")
agent_proc: subprocess.Popen | None = None
def _step(desc: str, cmd: list[str]) -> None:
print(f"[agent-boot] {desc} @ {time.strftime('%H:%M:%S')}", flush=True)
res = subprocess.run(cmd, capture_output=True, text=True)
tail = "\n".join((res.stdout + "\n" + res.stderr).strip().splitlines()[-10:])
if res.returncode != 0:
print(f"[agent-boot] step failed (exit {res.returncode}):\n{tail}", flush=True)
raise RuntimeError(f"{desc} failed")
if tail:
print(tail, flush=True)
def boot_agent() -> None:
global agent_proc
venv_py = "/tmp/agent-venv/bin/python"
try:
_step("creating isolated venv", [sys.executable, "-m", "venv", "/tmp/agent-venv"])
if shutil.which("uv"):
_step(
"installing agent deps with uv",
["uv", "pip", "install", "--python", venv_py, "-r", "agent-requirements.txt"],
)
else:
_step(
"installing agent deps with pip (the long step)",
[venv_py, "-m", "pip", "install", "--no-cache-dir", "--prefer-binary",
"--progress-bar", "off", "-r", "agent-requirements.txt"],
)
os.environ.setdefault("HF_HOME", "/tmp/hf")
_step("downloading model files", [venv_py, "src/agent.py", "download-files"])
print(f"[agent-boot] starting worker @ {time.strftime('%H:%M:%S')}", flush=True)
agent_proc = subprocess.Popen([venv_py, "src/agent.py", "start"])
except Exception as exc: # never take the web tier down with us
print(f"[agent-boot] FAILED: {exc}", flush=True)
threading.Thread(target=boot_agent, daemon=True).start()
def shutdown(signum, _frame) -> None:
# The platform allows 15 seconds after SIGTERM; the LiveKit worker
# prefers a slow graceful drain, so stop both children fast and firmly.
log(f"signal {signum} received; stopping worker and web server")
for proc in (agent_proc, web):
if proc is not None and proc.poll() is None:
proc.terminate()
deadline = time.time() + 5
for proc in (agent_proc, web):
if proc is None:
continue
while proc.poll() is None and time.time() < deadline:
time.sleep(0.2)
if proc.poll() is None:
proc.kill()
sys.exit(0)
signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)
# The app lives and dies with the web server.
sys.exit(web.wait())