Skip to content

Commit 6d88699

Browse files
committed
Fix HF Spaces timeout - use background initialization
1 parent 2b6c8ba commit 6d88699

2 files changed

Lines changed: 85 additions & 16 deletions

File tree

Dockerfile

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@ FROM python:3.11-slim
22

33
WORKDIR /app
44

5-
COPY pyproject.toml ./
6-
COPY requirements.txt* ./
5+
COPY requirements.txt ./
76

8-
RUN pip install --no-cache-dir uv && \
9-
uv pip install --system --no-cache -r pyproject.toml
7+
RUN pip install --no-cache-dir -r requirements.txt
108

119
COPY . .
1210

13-
EXPOSE 8000
11+
EXPOSE 7860
1412

1513
ENV PYTHONUNBUFFERED=1
1614

17-
CMD ["python", "app.py"]
15+
# Health check for HF Spaces - this endpoint returns immediately
16+
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
17+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:7860/health').read()"
18+
19+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--startup-timeout", "3600"]

app.py

Lines changed: 77 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,104 @@
11
from fastapi import FastAPI, Request, Form
2-
from fastapi.responses import HTMLResponse
2+
from fastapi.responses import HTMLResponse, JSONResponse
33
from fastapi.staticfiles import StaticFiles
44
from fastapi.templating import Jinja2Templates
55
from project.pipeline.agents import AgentWorkflow
66
from project.logger.logging import get_logger
77
import uvicorn
8+
import asyncio
9+
from contextlib import asynccontextmanager
810

911
logger = get_logger(__name__)
1012

11-
app = FastAPI(title="Learn with Transformers")
13+
# Global initialization state
14+
agent = None
15+
initialization_complete = False
16+
initialization_error = None
17+
18+
19+
async def initialize_rag_pipeline():
20+
"""Background task to initialize RAG pipeline"""
21+
global agent, initialization_complete, initialization_error
22+
try:
23+
logger.info("Initializing RAG pipeline in background...")
24+
agent = AgentWorkflow()
25+
agent.setup(use_attention_paper=True)
26+
initialization_complete = True
27+
logger.info("RAG pipeline ready")
28+
except Exception as e:
29+
initialization_error = str(e)
30+
logger.error(f"Initialization failed: {e}")
31+
initialization_complete = True
32+
33+
34+
@asynccontextmanager
35+
async def lifespan(app: FastAPI):
36+
"""App lifecycle manager - returns immediately, initializes in background"""
37+
# Startup
38+
logger.info("App starting up - launching background initialization")
39+
asyncio.create_task(initialize_rag_pipeline())
40+
yield
41+
# Shutdown
42+
logger.info("App shutting down")
43+
44+
45+
app = FastAPI(title="Learn with Transformers", lifespan=lifespan)
1246

1347
app.mount("/static", StaticFiles(directory="static"), name="static")
1448
templates = Jinja2Templates(directory="templates")
1549

16-
agent = None
1750

51+
@app.get("/health")
52+
async def health():
53+
"""Liveness probe - returns immediately (HF Spaces requirement)"""
54+
return JSONResponse({"status": "alive"}, status_code=200)
1855

19-
@app.on_event("startup")
20-
async def startup_event():
21-
global agent
22-
logger.info("Initializing RAG pipeline...")
23-
agent = AgentWorkflow()
24-
agent.setup(use_attention_paper=True)
25-
logger.info("RAG pipeline ready")
56+
57+
@app.get("/ready")
58+
async def readiness():
59+
"""Readiness probe - checks if initialization complete"""
60+
if initialization_error:
61+
return JSONResponse(
62+
{"status": "failed", "error": initialization_error},
63+
status_code=503
64+
)
65+
if not initialization_complete:
66+
return JSONResponse(
67+
{"status": "initializing"},
68+
status_code=503
69+
)
70+
return JSONResponse({"status": "ready"}, status_code=200)
2671

2772

2873
@app.get("/", response_class=HTMLResponse)
2974
async def home(request: Request):
75+
if not initialization_complete:
76+
return templates.TemplateResponse(
77+
"index.html",
78+
{"request": request, "message": "System initializing... Please wait"}
79+
)
80+
if initialization_error:
81+
return templates.TemplateResponse(
82+
"index.html",
83+
{"request": request, "error": f"Initialization failed: {initialization_error}"}
84+
)
3085
return templates.TemplateResponse("index.html", {"request": request})
3186

3287

3388
@app.post("/search", response_class=HTMLResponse)
3489
async def search(request: Request, query: str = Form(...)):
90+
if initialization_error:
91+
return templates.TemplateResponse(
92+
"index.html",
93+
{"request": request, "error": f"System error: {initialization_error}"}
94+
)
95+
96+
if not initialization_complete:
97+
return templates.TemplateResponse(
98+
"index.html",
99+
{"request": request, "error": "System still initializing. Please try again in a moment"}
100+
)
101+
35102
if not query.strip():
36103
return templates.TemplateResponse(
37104
"index.html",

0 commit comments

Comments
 (0)