Nothing12Man commited on
Commit
5eb188c
Β·
1 Parent(s): 155573a

Fix HF Space runtime by adding persistent FastAPI server

Browse files
.gitignore CHANGED
@@ -1,6 +1,8 @@
1
  node_modules/
2
  .next/
3
- backend/.venv/
 
 
4
  venv/
5
  __pycache__/
6
  *.pyc
 
1
  node_modules/
2
  .next/
3
+ lifeline-ai/node_modules/
4
+ lifeline-ai/.next/
5
+ lifeline-ai/backend/.venv/
6
  venv/
7
  __pycache__/
8
  *.pyc
Dockerfile CHANGED
@@ -26,6 +26,12 @@ COPY requirements.txt .
26
  RUN pip install --no-cache-dir --upgrade pip \
27
  && pip install --no-cache-dir -r requirements.txt
28
 
 
 
 
 
 
 
29
  # ── Copy application source safely ────────────────────────────────────────────
30
  # Copying the full project avoids file-not-found build breaks and is HF-Spaces-friendly.
31
  COPY . .
@@ -40,6 +46,10 @@ ENV OPENAI_API_KEY="EMPTY" \
40
  MODEL_NAME="gpt-4o-mini" \
41
  HF_TOKEN=""
42
 
43
- # ── Default command: run baseline inference across all tasks ──────────────────
44
- # LLM agent can be enabled by passing: --agent llm and setting API env vars.
45
- CMD ["python", "-u", "inference.py", "--difficulty", "all", "--agent", "rules"]
 
 
 
 
 
26
  RUN pip install --no-cache-dir --upgrade pip \
27
  && pip install --no-cache-dir -r requirements.txt
28
 
29
+ # If a backend requirements file exists (lifeline-ai/backend/requirements.txt), install it too
30
+ COPY lifeline-ai/backend/requirements.txt ./lifeline-backend-requirements.txt
31
+ RUN if [ -f ./lifeline-backend-requirements.txt ]; then \
32
+ pip install --no-cache-dir -r ./lifeline-backend-requirements.txt; \
33
+ fi
34
+
35
  # ── Copy application source safely ────────────────────────────────────────────
36
  # Copying the full project avoids file-not-found build breaks and is HF-Spaces-friendly.
37
  COPY . .
 
46
  MODEL_NAME="gpt-4o-mini" \
47
  HF_TOKEN=""
48
 
49
+ # ── Expose the port expected by Hugging Face Spaces and run the backend web server
50
+ EXPOSE 7860
51
+
52
+ # Default command: start the FastAPI backend (lifeline-ai backend) on port 7860.
53
+ # This keeps the container running as a web service compatible with Spaces (sdk: docker).
54
+ # It will cd into the backend folder and run uvicorn. Override at runtime as needed.
55
+ CMD ["sh", "-c", "uvicorn backend.app.main:app --host 0.0.0.0 --port 7860"]
backend/__init__.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ """Backend package for LifeLine AI HTTP API.
2
+
3
+ This package provides a small FastAPI wrapper that exposes the existing
4
+ benchmarking/inference logic via HTTP so the container remains running on
5
+ Hugging Face Spaces (sdk: docker).
6
+ """
7
+
8
+ __all__ = ["app"]
backend/app/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """backend.app package
2
+
3
+ Contains the FastAPI application module (main.py).
4
+ """
5
+
6
+ __all__ = ["main"]
backend/app/main.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import logging
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from fastapi import FastAPI, HTTPException
8
+ from pydantic import BaseModel
9
+
10
+ # Import the existing inference runner so we can reuse run_episode
11
+ try:
12
+ # inference.py lives at project root and exports run_episode
13
+ import inference
14
+ except Exception:
15
+ inference = None
16
+
17
+ app = FastAPI(title="LifeLine AI API", version="1.0.0")
18
+
19
+ # Configure logging for startup visibility
20
+ logger = logging.getLogger("lifeline.backend")
21
+ logging.basicConfig(level=logging.INFO)
22
+
23
+
24
+ class BenchmarkRequest(BaseModel):
25
+ agent: str = "rules" # 'rules' or 'llm'
26
+ difficulty: str = "all" # easy|medium|hard|all
27
+
28
+
29
+ @app.on_event("startup")
30
+ def startup_event() -> None:
31
+ logger.info("LifeLine AI API started successfully")
32
+
33
+
34
+ @app.get("/health")
35
+ def health() -> Dict[str, str]:
36
+ return {"status": "ok", "project": "LifeLine AI"}
37
+
38
+
39
+ @app.post("/run-benchmark")
40
+ def run_benchmark(req: BenchmarkRequest) -> Dict[str, Any]:
41
+ """Run the existing inference benchmark and return structured JSON results.
42
+
43
+ This re-uses the `run_episode` function from `inference.py` so the benchmark
44
+ logic remains in one place and is usable both as CLI and via the HTTP API.
45
+ """
46
+
47
+ if inference is None:
48
+ raise HTTPException(status_code=500, detail="inference module not available")
49
+
50
+ agent = req.agent.lower()
51
+ if agent not in ("rules", "llm"):
52
+ raise HTTPException(status_code=400, detail="agent must be 'rules' or 'llm'")
53
+
54
+ difficulty = req.difficulty.lower()
55
+ if difficulty not in ("easy", "medium", "hard", "all"):
56
+ raise HTTPException(status_code=400, detail="difficulty must be easy|medium|hard|all")
57
+
58
+ # Prepare OpenAI client when requested
59
+ client: Optional[Any] = None
60
+ if agent == "llm":
61
+ try:
62
+ from openai import OpenAI as OpenAIClient # type: ignore
63
+ except Exception as exc: # pragma: no cover - import/runtime error
64
+ raise HTTPException(status_code=500, detail=f"OpenAI client not available: {exc}")
65
+
66
+ api_key = os.getenv("OPENAI_API_KEY", "EMPTY")
67
+ hf_token = os.getenv("HF_TOKEN", "")
68
+ if hf_token and api_key == "EMPTY":
69
+ api_key = hf_token
70
+
71
+ try:
72
+ client = OpenAIClient(api_key=api_key, base_url=os.getenv("API_BASE_URL", "https://api.openai.com/v1"))
73
+ except Exception as exc:
74
+ raise HTTPException(status_code=500, detail=f"Failed to initialize OpenAI client: {exc}")
75
+
76
+ # Determine difficulties list
77
+ difficulties: List[str]
78
+ if difficulty == "all":
79
+ difficulties = inference.ALL_DIFFICULTIES
80
+ else:
81
+ difficulties = [difficulty]
82
+
83
+ results = []
84
+ for diff in difficulties:
85
+ # Each run returns structured dicts as defined by inference.run_episode
86
+ try:
87
+ res = inference.run_episode(client, diff, agent)
88
+ except Exception as exc:
89
+ # Bubble up error details while keeping API stable
90
+ raise HTTPException(status_code=500, detail=f"Benchmark run failed: {exc}")
91
+ results.append(res)
92
+
93
+ avg_score = sum(r["score"] for r in results) / len(results) if results else 0.0
94
+
95
+ return {"average_score": avg_score, "results": results}