IDKHowToCodeFr commited on
Commit
2cd0755
·
1 Parent(s): 3b4cb78

feat: mount heartflow backend at /heartflow with prefetch and websocket support

Browse files
Files changed (5) hide show
  1. Dockerfile +3 -3
  2. registry.py +1 -0
  3. requirements.in +1 -0
  4. requirements.txt +40 -4
  5. tests/test_host.py +50 -1
Dockerfile CHANGED
@@ -12,9 +12,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
12
  && rm -rf /var/lib/apt/lists/*
13
 
14
  RUN useradd -m -u 1000 user && \
15
- mkdir -p /home/user/hf-cache /tmp/p2 && \
16
- chown -R user:user /home/user/hf-cache /tmp/p2 && \
17
- chmod 777 /tmp/p2
18
 
19
  USER user
20
  ENV HOME=/home/user \
 
12
  && rm -rf /var/lib/apt/lists/*
13
 
14
  RUN useradd -m -u 1000 user && \
15
+ mkdir -p /home/user/hf-cache /tmp/p1 /tmp/p2 && \
16
+ chown -R user:user /home/user/hf-cache /tmp/p1 /tmp/p2 && \
17
+ chmod 777 /tmp/p1 /tmp/p2
18
 
19
  USER user
20
  ENV HOME=/home/user \
registry.py CHANGED
@@ -1,3 +1,4 @@
1
  PROJECT_REGISTRY = {
2
  "/semantic": "semantic_backend.router:router",
 
3
  }
 
1
  PROJECT_REGISTRY = {
2
  "/semantic": "semantic_backend.router:router",
3
+ "/heartflow": "heartflow_backend.router:router",
4
  }
requirements.in CHANGED
@@ -3,3 +3,4 @@ uvicorn[standard]
3
  pydantic
4
  starlette
5
  git+https://github.com/rachit-mangawa/Semantic-Comment-Analyze.git@v1.3#subdirectory=backend
 
 
3
  pydantic
4
  starlette
5
  git+https://github.com/rachit-mangawa/Semantic-Comment-Analyze.git@v1.3#subdirectory=backend
6
+ git+https://github.com/rachit-mangawa/HEARTFLOW_OS.git@v1.0#subdirectory=backend
requirements.txt CHANGED
@@ -3,6 +3,8 @@
3
  --index-url https://pypi.org/simple
4
  --extra-index-url https://download.pytorch.org/whl/cpu
5
 
 
 
6
  annotated-doc==0.0.5
7
  # via
8
  # fastapi
@@ -19,10 +21,13 @@ click==8.5.0
19
  # huggingface-hub
20
  # uvicorn
21
  cloudpickle==3.1.2
22
- # via joblib
 
 
23
  fastapi==0.162.0
24
  # via
25
  # -r requirements.in
 
26
  # semantic-backend
27
  filelock==3.32.3
28
  # via
@@ -36,6 +41,8 @@ h11==0.16.0
36
  # via
37
  # httpcore2
38
  # uvicorn
 
 
39
  hf-xet==1.7.0
40
  # via huggingface-hub
41
  httpcore2==2.3.0
@@ -57,8 +64,13 @@ jinja2==3.1.6
57
  # via torch
58
  joblib==1.6.0
59
  # via
 
60
  # scikit-learn
61
  # semantic-backend
 
 
 
 
62
  markdown-it-py==4.2.0
63
  # via rich
64
  markupsafe==3.0.3
@@ -71,26 +83,36 @@ narwhals==2.27.1
71
  # via scikit-learn
72
  networkx==3.6.1
73
  # via torch
 
 
74
  numpy==2.4.6
75
  # via
 
 
76
  # pandas
77
  # scikit-learn
78
  # scipy
79
  # semantic-backend
80
  # sentence-transformers
 
81
  # transformers
82
  opentelemetry-api==1.45.1
83
  # via fastapi
84
  packaging==24.1
85
  # via
86
  # huggingface-hub
 
87
  # transformers
88
  pandas==3.1.0
89
- # via semantic-backend
 
 
 
90
  pydantic==2.14.1
91
  # via
92
  # -r requirements.in
93
  # fastapi
 
94
  pydantic-core==2.50.1
95
  # via pydantic
96
  pygments==2.21.0
@@ -100,7 +122,9 @@ python-dateutil==3.0.0
100
  python-dotenv==1.2.4
101
  # via uvicorn
102
  python-multipart==0.0.32
103
- # via semantic-backend
 
 
104
  pyyaml==6.0.3
105
  # via
106
  # huggingface-hub
@@ -114,22 +138,30 @@ safetensors==0.8.0
114
  # via transformers
115
  scikit-learn==1.9.1
116
  # via
 
117
  # semantic-backend
118
  # sentence-transformers
 
119
  scipy==1.17.1
120
  # via
 
121
  # scikit-learn
122
  # sentence-transformers
 
123
  semantic-backend @ git+https://github.com/rachit-mangawa/Semantic-Comment-Analyze.git@eb73c60a2d94fafa9d224d75d1ae8602a0fe372f#subdirectory=backend
124
  # via -r requirements.in
125
  sentence-transformers==6.2.0
126
  # via semantic-backend
127
  setuptools==78.1.0
128
  # via torch
 
 
129
  shellingham==1.5.4
130
  # via typer
131
  six==1.17.0
132
  # via python-dateutil
 
 
133
  starlette==1.10.0
134
  # via
135
  # -r requirements.in
@@ -148,6 +180,7 @@ tqdm==4.66.5
148
  # via
149
  # huggingface-hub
150
  # sentence-transformers
 
151
  # transformers
152
  transformers==5.20.2
153
  # via
@@ -168,6 +201,7 @@ typing-extensions==4.16.0
168
  # pydantic
169
  # pydantic-core
170
  # sentence-transformers
 
171
  # starlette
172
  # torch
173
  # typing-inspection
@@ -184,4 +218,6 @@ uvloop==0.23.0
184
  watchfiles==1.3.0
185
  # via uvicorn
186
  websockets==17.3
187
- # via uvicorn
 
 
 
3
  --index-url https://pypi.org/simple
4
  --extra-index-url https://download.pytorch.org/whl/cpu
5
 
6
+ aiosqlite==0.22.1
7
+ # via heartflow-backend
8
  annotated-doc==0.0.5
9
  # via
10
  # fastapi
 
21
  # huggingface-hub
22
  # uvicorn
23
  cloudpickle==3.1.2
24
+ # via
25
+ # joblib
26
+ # shap
27
  fastapi==0.162.0
28
  # via
29
  # -r requirements.in
30
+ # heartflow-backend
31
  # semantic-backend
32
  filelock==3.32.3
33
  # via
 
41
  # via
42
  # httpcore2
43
  # uvicorn
44
+ heartflow-backend @ git+https://github.com/rachit-mangawa/HEARTFLOW_OS.git@c81f1ffadf10f23df2767498230f54b14d39084f#subdirectory=backend
45
+ # via -r requirements.in
46
  hf-xet==1.7.0
47
  # via huggingface-hub
48
  httpcore2==2.3.0
 
64
  # via torch
65
  joblib==1.6.0
66
  # via
67
+ # heartflow-backend
68
  # scikit-learn
69
  # semantic-backend
70
+ llvmlite==0.50.0
71
+ # via
72
+ # numba
73
+ # shap
74
  markdown-it-py==4.2.0
75
  # via rich
76
  markupsafe==3.0.3
 
83
  # via scikit-learn
84
  networkx==3.6.1
85
  # via torch
86
+ numba==0.68.0
87
+ # via shap
88
  numpy==2.4.6
89
  # via
90
+ # heartflow-backend
91
+ # numba
92
  # pandas
93
  # scikit-learn
94
  # scipy
95
  # semantic-backend
96
  # sentence-transformers
97
+ # shap
98
  # transformers
99
  opentelemetry-api==1.45.1
100
  # via fastapi
101
  packaging==24.1
102
  # via
103
  # huggingface-hub
104
+ # shap
105
  # transformers
106
  pandas==3.1.0
107
+ # via
108
+ # heartflow-backend
109
+ # semantic-backend
110
+ # shap
111
  pydantic==2.14.1
112
  # via
113
  # -r requirements.in
114
  # fastapi
115
+ # heartflow-backend
116
  pydantic-core==2.50.1
117
  # via pydantic
118
  pygments==2.21.0
 
122
  python-dotenv==1.2.4
123
  # via uvicorn
124
  python-multipart==0.0.32
125
+ # via
126
+ # heartflow-backend
127
+ # semantic-backend
128
  pyyaml==6.0.3
129
  # via
130
  # huggingface-hub
 
138
  # via transformers
139
  scikit-learn==1.9.1
140
  # via
141
+ # heartflow-backend
142
  # semantic-backend
143
  # sentence-transformers
144
+ # shap
145
  scipy==1.17.1
146
  # via
147
+ # heartflow-backend
148
  # scikit-learn
149
  # sentence-transformers
150
+ # shap
151
  semantic-backend @ git+https://github.com/rachit-mangawa/Semantic-Comment-Analyze.git@eb73c60a2d94fafa9d224d75d1ae8602a0fe372f#subdirectory=backend
152
  # via -r requirements.in
153
  sentence-transformers==6.2.0
154
  # via semantic-backend
155
  setuptools==78.1.0
156
  # via torch
157
+ shap==0.51.0
158
+ # via heartflow-backend
159
  shellingham==1.5.4
160
  # via typer
161
  six==1.17.0
162
  # via python-dateutil
163
+ slicer==0.0.8
164
+ # via shap
165
  starlette==1.10.0
166
  # via
167
  # -r requirements.in
 
180
  # via
181
  # huggingface-hub
182
  # sentence-transformers
183
+ # shap
184
  # transformers
185
  transformers==5.20.2
186
  # via
 
201
  # pydantic
202
  # pydantic-core
203
  # sentence-transformers
204
+ # shap
205
  # starlette
206
  # torch
207
  # typing-inspection
 
218
  watchfiles==1.3.0
219
  # via uvicorn
220
  websockets==17.3
221
+ # via
222
+ # heartflow-backend
223
+ # uvicorn
tests/test_host.py CHANGED
@@ -284,8 +284,9 @@ def test_real_registry():
284
  import importlib
285
  import registry
286
  importlib.reload(registry)
287
- assert list(registry.PROJECT_REGISTRY.keys()) == ["/semantic"]
288
  assert registry.PROJECT_REGISTRY["/semantic"] == "semantic_backend.router:router"
 
289
 
290
  if "main" in sys.modules:
291
  del sys.modules["main"]
@@ -297,8 +298,10 @@ def test_real_registry():
297
  data = res.json()
298
  assert data["status"] == "healthy"
299
  assert "/semantic" in data["loaded"]
 
300
  assert not data["failed"]
301
  assert data["warm_status"]["/semantic"] == "warmed"
 
302
  assert data["total_warmup_seconds"] >= 0
303
 
304
  res_root = client.get("/")
@@ -315,6 +318,13 @@ def test_real_registry():
315
  assert sem_data["models_loaded"]["classifier_head"] is True
316
  assert sem_data["models_loaded"]["sentiment"] is True
317
 
 
 
 
 
 
 
 
318
 
319
  def test_status_page_and_content_negotiation():
320
  if "main" in sys.modules:
@@ -351,3 +361,42 @@ def test_status_page_and_content_negotiation():
351
  assert "application/json" in res_api.headers.get("content-type", "")
352
  assert res_api.json()["status"] in ["healthy", "degraded", "unhealthy"]
353
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  import importlib
285
  import registry
286
  importlib.reload(registry)
287
+ assert set(registry.PROJECT_REGISTRY.keys()) == {"/semantic", "/heartflow"}
288
  assert registry.PROJECT_REGISTRY["/semantic"] == "semantic_backend.router:router"
289
+ assert registry.PROJECT_REGISTRY["/heartflow"] == "heartflow_backend.router:router"
290
 
291
  if "main" in sys.modules:
292
  del sys.modules["main"]
 
298
  data = res.json()
299
  assert data["status"] == "healthy"
300
  assert "/semantic" in data["loaded"]
301
+ assert "/heartflow" in data["loaded"]
302
  assert not data["failed"]
303
  assert data["warm_status"]["/semantic"] == "warmed"
304
+ assert data["warm_status"]["/heartflow"] == "warmed"
305
  assert data["total_warmup_seconds"] >= 0
306
 
307
  res_root = client.get("/")
 
318
  assert sem_data["models_loaded"]["classifier_head"] is True
319
  assert sem_data["models_loaded"]["sentiment"] is True
320
 
321
+ # Check Heartflow /health endpoint reports models_loaded
322
+ res_hf = client.get("/heartflow/health")
323
+ assert res_hf.status_code == 200
324
+ hf_data = res_hf.json()
325
+ assert hf_data["status"] == "Healthy"
326
+ assert hf_data["models_loaded"] is True
327
+
328
 
329
  def test_status_page_and_content_negotiation():
330
  if "main" in sys.modules:
 
361
  assert "application/json" in res_api.headers.get("content-type", "")
362
  assert res_api.json()["status"] in ["healthy", "degraded", "unhealthy"]
363
 
364
+
365
+
366
+ def test_host_websocket_through_middleware(monkeypatch):
367
+ from fastapi import WebSocket
368
+
369
+ ws_pkg = types.ModuleType("mock_ws_pkg")
370
+ ws_router_mod = types.ModuleType("mock_ws_pkg.router")
371
+ r = APIRouter()
372
+
373
+ @r.websocket("/ws")
374
+ async def dummy_ws(websocket: WebSocket):
375
+ await websocket.accept()
376
+ for i in range(3):
377
+ await websocket.send_text(f"frame-{i}")
378
+ await websocket.close()
379
+
380
+ ws_router_mod.router = r
381
+ ws_pkg.router = ws_router_mod
382
+ ws_pkg.warmup = lambda: True
383
+ sys.modules["mock_ws_pkg"] = ws_pkg
384
+ sys.modules["mock_ws_pkg.router"] = ws_router_mod
385
+
386
+ monkeypatch.setenv("DISABLE_PROJECTS", "")
387
+ monkeypatch.setenv("MAX_BODY_SIZE_BYTES", "1024")
388
+ monkeypatch.setenv("REQUEST_TIMEOUT_SECONDS", "5.0")
389
+
390
+ import registry
391
+ registry.PROJECT_REGISTRY = {
392
+ "/mockws": "mock_ws_pkg.router:router",
393
+ }
394
+
395
+ if "main" in sys.modules:
396
+ del sys.modules["main"]
397
+
398
+ import main
399
+ with TestClient(main.app) as client:
400
+ with client.websocket_connect("/mockws/ws") as ws:
401
+ frames = [ws.receive_text() for _ in range(3)]
402
+ assert frames == ["frame-0", "frame-1", "frame-2"]