Altamira Builder commited on
Commit
6ae6a09
·
1 Parent(s): ff80184

minimal FastAPI Docker test v2 - match SpacesExamples pattern

Browse files
Files changed (5) hide show
  1. Dockerfile +6 -4
  2. app.py +11 -6
  3. prestart.sh +0 -2
  4. router.py +0 -94
  5. worker.py +0 -94
Dockerfile CHANGED
@@ -1,9 +1,11 @@
1
- FROM python:3.12-slim
2
 
3
- RUN pip install --no-cache-dir fastapi uvicorn
4
 
5
- COPY app.py .
6
 
7
- EXPOSE 7860
 
 
8
 
9
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
1
+ FROM python:3.9
2
 
3
+ WORKDIR /code
4
 
5
+ COPY ./requirements.txt /code/requirements.txt
6
 
7
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
8
+
9
+ COPY . .
10
 
11
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py CHANGED
@@ -1,8 +1,13 @@
1
- from fastapi import FastAPI
2
- from fastapi.responses import JSONResponse
3
 
4
- app = FastAPI()
 
 
 
 
 
5
 
6
- @app.get("/")
7
- async def root():
8
- return JSONResponse({"status": "ok", "message": "Altamira is alive"})
 
1
+ import http.server
2
+ import json
3
 
4
+ class Handler(http.server.BaseHTTPRequestHandler):
5
+ def do_GET(self):
6
+ self.send_response(200)
7
+ self.send_header("Content-Type", "application/json")
8
+ self.end_headers()
9
+ self.wfile.write(json.dumps({"status": "ok"}).encode())
10
 
11
+ if __name__ == "__main__":
12
+ server = http.server.HTTPServer(("0.0.0.0", 7860), Handler)
13
+ server.serve_forever()
prestart.sh DELETED
@@ -1,2 +0,0 @@
1
- #!/bin/bash
2
- pip install huggingface_hub==0.25.2 --upgrade --quiet
 
 
 
router.py DELETED
@@ -1,94 +0,0 @@
1
- import asyncio
2
- import time
3
- from collections import deque
4
-
5
-
6
- class PredictiveContextFilter:
7
- def __init__(self, capacity: int = 4096, threshold: float = 0.8):
8
- self.capacity = capacity
9
- self.threshold = threshold
10
- self.window = deque(maxlen=100)
11
-
12
- def estimate_tokens(self, text: str) -> int:
13
- return len(text) // 4
14
-
15
- def should_compact(self, text: str) -> bool:
16
- ratio = self.estimate_tokens(text) / self.capacity
17
- return ratio >= self.threshold
18
-
19
- def compact(self, text: str, target_ratio: float = 0.5) -> str:
20
- if not self.should_compact(text):
21
- return text
22
- target_len = int(self.capacity * target_ratio * 4)
23
- if len(text) <= target_len:
24
- return text
25
- half = target_len // 2
26
- return text[:half] + "\n... [compacted] ...\n" + text[-half:]
27
-
28
- async def monitor(self, text: str) -> str:
29
- loop_count = 0
30
- while self.should_compact(text) and loop_count < 3:
31
- text = self.compact(text)
32
- loop_count += 1
33
- return text
34
-
35
-
36
- class CircuitBreakerState:
37
- CLOSED = "closed"
38
- OPEN = "open"
39
- HALF_OPEN = "half_open"
40
-
41
-
42
- class InferenceRouterCircuitBreaker:
43
- def __init__(self, failure_threshold: int = 5, recovery_timeout: float = 30.0):
44
- self.failure_threshold = failure_threshold
45
- self.recovery_timeout = recovery_timeout
46
- self.state = CircuitBreakerState.CLOSED
47
- self.failure_count = 0
48
- self.last_failure_time = 0.0
49
-
50
- def record_success(self):
51
- self.failure_count = 0
52
- if self.state == CircuitBreakerState.HALF_OPEN:
53
- self.state = CircuitBreakerState.CLOSED
54
-
55
- def record_failure(self):
56
- self.failure_count += 1
57
- self.last_failure_time = time.time()
58
- if self.failure_count >= self.failure_threshold:
59
- self.state = CircuitBreakerState.OPEN
60
-
61
- async def call(self, coro_factory, fallback=None):
62
- if self.state == CircuitBreakerState.OPEN:
63
- if time.time() - self.last_failure_time >= self.recovery_timeout:
64
- self.state = CircuitBreakerState.HALF_OPEN
65
- elif fallback is not None:
66
- return fallback
67
- else:
68
- raise RuntimeError("Circuit breaker is OPEN")
69
- try:
70
- result = await coro_factory()
71
- self.record_success()
72
- return result
73
- except Exception as e:
74
- self.record_failure()
75
- if fallback is not None:
76
- return fallback
77
- raise e
78
-
79
-
80
- class ParallelEngine:
81
- def __init__(self, max_concurrency: int = 4):
82
- self.semaphore = asyncio.Semaphore(max_concurrency)
83
- self.filter = PredictiveContextFilter()
84
- self.breaker = InferenceRouterCircuitBreaker()
85
-
86
- async def run_with_filter(self, text: str, coro_factory) -> str:
87
- compacted = await self.filter.monitor(text)
88
- return await self.breaker.call(lambda: coro_factory(compacted))
89
-
90
- async def run_parallel(self, tasks: list):
91
- async def bounded(task):
92
- async with self.semaphore:
93
- return await task
94
- return await asyncio.gather(*(bounded(t) for t in tasks))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
worker.py DELETED
@@ -1,94 +0,0 @@
1
- import asyncio
2
- import json
3
- import shutil
4
- import subprocess
5
- from pathlib import Path
6
-
7
- from fastapi import FastAPI, HTTPException, Request
8
- from huggingface_hub import HfApi
9
-
10
- app = FastAPI(title="Altamira Worker")
11
-
12
- CACHE_DIR = Path("/tmp/altamira-cache")
13
- WORKSPACE_DIR = Path("/tmp/altamira-workspace")
14
- STAGE_DIRS = [CACHE_DIR / "stage_0", CACHE_DIR / "stage_1", CACHE_DIR / "stage_2"]
15
-
16
- api = HfApi()
17
-
18
-
19
- def _ensure_dirs():
20
- for d in [CACHE_DIR, WORKSPACE_DIR] + STAGE_DIRS:
21
- d.mkdir(parents=True, exist_ok=True)
22
-
23
-
24
- def _rotate_stages():
25
- for i in range(len(STAGE_DIRS) - 1, 0, -1):
26
- src = STAGE_DIRS[i - 1]
27
- dst = STAGE_DIRS[i]
28
- if src.exists():
29
- if dst.exists():
30
- shutil.rmtree(dst)
31
- shutil.copytree(src, dst, dirs_exist_ok=True)
32
-
33
-
34
- def _cleanup_stale(keep_last: int = 2):
35
- for d in STAGE_DIRS[:-keep_last] if keep_last < len(STAGE_DIRS) else []:
36
- if d.exists():
37
- shutil.rmtree(d)
38
-
39
-
40
- @app.post("/sync")
41
- async def sync_repo(repo_id: str, local_path: str = "/tmp/altamira-checkout"):
42
- _ensure_dirs()
43
- try:
44
- subprocess.run(
45
- ["git", "clone", f"https://huggingface.co/spaces/{repo_id}", local_path],
46
- capture_output=True, text=True, check=False
47
- )
48
- if not Path(local_path).exists():
49
- subprocess.run(
50
- ["git", "init", local_path],
51
- capture_output=True, check=True,
52
- )
53
- _rotate_stages()
54
- shutil.copytree(local_path, STAGE_DIRS[0], dirs_exist_ok=True)
55
- _cleanup_stale()
56
- return {"status": "synced", "repo": repo_id, "stage": str(STAGE_DIRS[0])}
57
- except Exception as e:
58
- raise HTTPException(status_code=500, detail=str(e))
59
-
60
-
61
- @app.post("/rotate")
62
- async def rotate_cache():
63
- _ensure_dirs()
64
- _rotate_stages()
65
- _cleanup_stale()
66
- return {"status": "rotated", "stages": [str(d) for d in STAGE_DIRS]}
67
-
68
-
69
- @app.post("/webhook/results")
70
- async def receive_results(request: Request):
71
- try:
72
- payload = await request.json()
73
- token = request.query_params.get("token")
74
- if not token:
75
- raise HTTPException(status_code=401, detail="Verification key required")
76
-
77
- # Log diagnostic payload to local file for Orchestrator to pull
78
- log_file = Path("/tmp/altamira-results.json")
79
- with open(log_file, "a") as f:
80
- f.write(json.dumps(payload) + "\n")
81
-
82
- return {"status": "received", "payload_size": len(str(payload))}
83
- except Exception as e:
84
- raise HTTPException(status_code=400, detail=f"Invalid payload: {str(e)}")
85
-
86
-
87
- @app.get("/health")
88
- async def health():
89
- return {"status": "healthy", "worker": "altamira"}
90
-
91
-
92
- if __name__ == "__main__":
93
- import uvicorn
94
- uvicorn.run(app, host="0.0.0.0", port=7860)