-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathapp.py
More file actions
96 lines (72 loc) · 3.34 KB
/
Copy pathapp.py
File metadata and controls
96 lines (72 loc) · 3.34 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
"""
Hugging Face Space entry point — `sdk: gradio`.
Why this file exists
--------------------
Docker Spaces now require a paid plan; Gradio Spaces on ZeroGPU are still free
for personal accounts. So the API is served *through* the Gradio SDK instead of
a Dockerfile. `docs/HOW_TO_DEPLOY_BACKEND.md` describes the old Docker route and
is kept for the day that becomes affordable again.
Nothing about the backend changes. `gr.mount_gradio_app` mounts the Gradio UI
*into* the existing FastAPI app rather than the other way round, so every route
keeps the exact path the frontend already calls:
/api/* /health/* /metrics /ws/stream unchanged
/ui Gradio status page
That matters: `VITE_API_URL` points at the Space origin and nothing in
`frontend/src/api.js` needs a prefix.
Spaces runs this file as a script, the same way the ZeroGPU template's
`demo.launch()` works, so the uvicorn call under __main__ is what binds :7860.
"""
from __future__ import annotations
import logging
import os
import threading
import gradio as gr
# Importing backend.app runs backend/__init__.py first, which loads .env (absent
# here — the Space injects GROQ_API_KEY as a real environment variable) and pins
# the TF/loky settings every entry point needs.
from backend.app import app as helioops
log = logging.getLogger(__name__)
def _prewarm_embedder() -> None:
"""
Pull BAAI/bge-small-en-v1.5 into the HF cache in the background.
The Docker image baked this at build time. There is no build step here, so
the first retrieval would otherwise pay a ~90s download — inside the first
/api/detect a judge runs. Done on a daemon thread rather than at import so
the port binds immediately and the Space reports healthy while the model
downloads.
"""
try:
from backend.embeddings.embedder import embed_query
embed_query("warm")
log.info("embedder prewarmed")
except Exception as exc: # never block serving on a cache warm
log.warning("embedder prewarm skipped: %s", exc)
with gr.Blocks(title="HelioOps API") as demo:
gr.Markdown(
"""
# HelioOps API
Space-weather operations backend — CME detection, quantile impact
prediction, four RAG-grounded industry agents, and a deterministic
verifier that rewrites unsafe values before an operator sees them.
This Space is the **API**, not the console. The operator UI lives on the
frontend deployment and calls this origin.
| Endpoint | What it does |
|---|---|
| `GET /health/ready` | readiness — `knowledge_base: false` means RAG is dead |
| `GET /api/storms` | replayable storms + completed runs |
| `POST /api/detect/{storm_id}` | full pipeline, 65–80s |
| `GET /api/result/{storm_id}` | last result for a storm |
| `WS /ws/stream` | live pipeline events |
| `GET /docs` | OpenAPI |
"""
)
# Gradio mounts INTO the API app, not the reverse — the backend keeps every path.
app = gr.mount_gradio_app(helioops, demo, path="/ui")
if __name__ == "__main__":
import uvicorn
threading.Thread(target=_prewarm_embedder, daemon=True).start()
uvicorn.run(
app,
host="0.0.0.0",
port=int(os.getenv("PORT") or os.getenv("GRADIO_SERVER_PORT") or 7860),
)