Marwan-Tamer commited on
Commit
ca2008b
·
1 Parent(s): d98e3cc

Prepare Hugging Face Spaces deployment

Browse files
.dockerignore ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .git
2
+ .env
3
+ .venv
4
+ .hf_cache
5
+ __pycache__
6
+ *.pyc
7
+ *.pyo
8
+ *.log
9
+
10
+ data
11
+ checkpoints
12
+ runs
13
+ docs
14
+
15
+ src/models/saved_lang_model.pkl
16
+ src/models/saved_emotion_model
17
+ src/models/saved_*
18
+
19
+ notebooks/.ipynb_checkpoints
20
+ notebooks/data
21
+ notebooks/reports
.env.example CHANGED
@@ -1,5 +1,9 @@
1
  GROQ_API_KEY=your_groq_api_key_here
2
 
 
 
 
 
3
  QDRANT_URL=https://your-cluster-url.qdrant.tech
4
  QDRANT_API_KEY=your_qdrant_api_key_here
5
  QDRANT_COLLECTION=mental_health_rag
 
1
  GROQ_API_KEY=your_groq_api_key_here
2
 
3
+ LANGUAGE_MODEL_REPO_ID=your_hf_username/language-detector-model
4
+ LANGUAGE_MODEL_FILENAME=saved_lang_model.pkl
5
+ EMOTION_MODEL_ID=your_hf_username/emotion-detector-model
6
+
7
  QDRANT_URL=https://your-cluster-url.qdrant.tech
8
  QDRANT_API_KEY=your_qdrant_api_key_here
9
  QDRANT_COLLECTION=mental_health_rag
DEPLOYMENT.md ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces Deployment
2
+
3
+ This project is prepared for deployment as a Hugging Face Docker Space.
4
+
5
+ ## 1. Login Locally
6
+
7
+ Run:
8
+
9
+ ```powershell
10
+ .\.venv\Scripts\hf.exe auth login
11
+ ```
12
+
13
+ Use a Hugging Face token with write access.
14
+
15
+ ## 2. Upload Model Artifacts
16
+
17
+ Create two Hugging Face model repositories:
18
+
19
+ ```text
20
+ your_username/mental-health-language-detector
21
+ your_username/mental-health-emotion-detector
22
+ ```
23
+
24
+ Upload:
25
+
26
+ - `src/models/saved_lang_model.pkl` to the language model repository.
27
+ - All files inside `src/models/saved_emotion_model/` to the emotion model repository.
28
+
29
+ The emotion model folder should contain:
30
+
31
+ ```text
32
+ config.json
33
+ model.safetensors
34
+ tokenizer.json
35
+ tokenizer_config.json
36
+ ```
37
+
38
+ ## 3. Create The Space
39
+
40
+ Create a Hugging Face Space:
41
+
42
+ ```text
43
+ SDK: Docker
44
+ Visibility: Public or Private
45
+ App port: 7860
46
+ ```
47
+
48
+ ## 4. Add Space Secrets
49
+
50
+ In the Space settings, add:
51
+
52
+ ```text
53
+ GROQ_API_KEY
54
+ QDRANT_URL
55
+ QDRANT_API_KEY
56
+ QDRANT_COLLECTION
57
+ LANGUAGE_MODEL_REPO_ID
58
+ LANGUAGE_MODEL_FILENAME
59
+ EMOTION_MODEL_ID
60
+ ```
61
+
62
+ Recommended values:
63
+
64
+ ```text
65
+ LANGUAGE_MODEL_FILENAME=saved_lang_model.pkl
66
+ QDRANT_COLLECTION=mental_health_rag
67
+ EMBEDDING_MODEL_NAME=intfloat/multilingual-e5-base
68
+ EMBEDDING_BATCH_SIZE=2
69
+ TORCH_NUM_THREADS=1
70
+ ```
71
+
72
+ ## 5. Push To The Space Repo
73
+
74
+ After the Space is created, add it as a Git remote:
75
+
76
+ ```powershell
77
+ git remote add space https://huggingface.co/spaces/your_username/your_space_name
78
+ git push space main
79
+ ```
80
+
81
+ The Dockerfile starts the production app with:
82
+
83
+ ```text
84
+ uvicorn src.api_app:app --host 0.0.0.0 --port 7860
85
+ ```
86
+
87
+ Open the Space URL after the build finishes.
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ ENV PYTHONDONTWRITEBYTECODE=1
4
+ ENV PYTHONUNBUFFERED=1
5
+ ENV PIP_NO_CACHE_DIR=1
6
+ ENV TOKENIZERS_PARALLELISM=false
7
+ ENV TORCH_NUM_THREADS=1
8
+
9
+ WORKDIR /app
10
+
11
+ RUN apt-get update \
12
+ && apt-get install -y --no-install-recommends build-essential \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ COPY requirements.txt .
16
+ RUN pip install --upgrade pip \
17
+ && pip install -r requirements.txt
18
+
19
+ COPY . .
20
+
21
+ EXPOSE 7860
22
+
23
+ CMD ["uvicorn", "src.api_app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -164,6 +164,9 @@ Create a local `.env` file from `.env.example`:
164
 
165
  ```text
166
  GROQ_API_KEY=your_groq_api_key_here
 
 
 
167
  QDRANT_URL=https://your-cluster-url.qdrant.tech
168
  QDRANT_API_KEY=your_qdrant_api_key_here
169
  QDRANT_COLLECTION=mental_health_rag
@@ -221,11 +224,37 @@ These reports make the project easier to review, debug, and present.
221
 
222
  ## Deployment Notes
223
 
224
- The current app runs locally through FastAPI and can be prepared for Hugging Face Spaces. For deployment:
225
 
226
- - Store API keys as platform secrets, not in code.
227
- - Keep trained model artifacts outside Git or upload them to a model host.
228
- - Rebuild or connect to the Qdrant collection during setup.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  - Keep the production UI at `/` and the developer UI at `/developer`.
230
 
231
  ## Safety Note
 
164
 
165
  ```text
166
  GROQ_API_KEY=your_groq_api_key_here
167
+ LANGUAGE_MODEL_REPO_ID=your_hf_username/language-detector-model
168
+ LANGUAGE_MODEL_FILENAME=saved_lang_model.pkl
169
+ EMOTION_MODEL_ID=your_hf_username/emotion-detector-model
170
  QDRANT_URL=https://your-cluster-url.qdrant.tech
171
  QDRANT_API_KEY=your_qdrant_api_key_here
172
  QDRANT_COLLECTION=mental_health_rag
 
224
 
225
  ## Deployment Notes
226
 
227
+ The current app runs locally through FastAPI and is prepared for a Hugging Face Docker Space.
228
 
229
+ See `DEPLOYMENT.md` for the full Hugging Face Spaces checklist.
230
+
231
+ Recommended Hugging Face Space setup:
232
+
233
+ 1. Create a new Space with `Docker` as the SDK.
234
+ 2. Push this repository content to the Space repository.
235
+ 3. Add the required secrets in the Space settings:
236
+
237
+ ```text
238
+ GROQ_API_KEY
239
+ QDRANT_URL
240
+ QDRANT_API_KEY
241
+ QDRANT_COLLECTION
242
+ LANGUAGE_MODEL_REPO_ID
243
+ LANGUAGE_MODEL_FILENAME
244
+ EMOTION_MODEL_ID
245
+ ```
246
+
247
+ The Dockerfile runs:
248
+
249
+ ```text
250
+ uvicorn src.api_app:app --host 0.0.0.0 --port 7860
251
+ ```
252
+
253
+ Model artifact policy:
254
+
255
+ - Upload `saved_lang_model.pkl` to a Hugging Face model repository and set `LANGUAGE_MODEL_REPO_ID`.
256
+ - Upload the trained `saved_emotion_model/` files to another Hugging Face model repository and set `EMOTION_MODEL_ID`.
257
+ - Keep API keys, model secrets, local data, and local caches outside Git.
258
  - Keep the production UI at `/` and the developer UI at `/developer`.
259
 
260
  ## Safety Note
requirements.txt CHANGED
@@ -3,6 +3,7 @@ scikit-learn==1.9.0
3
  joblib==1.5.3
4
  gradio==6.18.0
5
  datasets==5.0.0
 
6
  transformers
7
  torch
8
  accelerate
 
3
  joblib==1.5.3
4
  gradio==6.18.0
5
  datasets==5.0.0
6
+ huggingface_hub
7
  transformers
8
  torch
9
  accelerate
src/models/emotion_classifier.py CHANGED
@@ -10,6 +10,7 @@ from typing import Any
10
 
11
  PROJECT_ROOT = Path(__file__).resolve().parents[2]
12
  DEFAULT_MODEL_DIR = PROJECT_ROOT / "src" / "models" / "saved_emotion_model"
 
13
 
14
 
15
  def _load_transformer_stack() -> tuple[Any, Any, Any]:
@@ -33,29 +34,38 @@ class EmotionClassifier:
33
  model_dir: str | Path | None = None,
34
  ) -> None:
35
  self.model_dir = Path(model_dir or os.getenv("EMOTION_MODEL_DIR", DEFAULT_MODEL_DIR))
36
- self.active_model_source = str(self.model_dir)
 
37
  self.torch = None
38
  self.tokenizer = None
39
  self.model = None
40
  self.id2label: dict[int, str] = {}
41
 
42
  def load_model(self) -> None:
43
- if not self.model_dir.exists():
 
44
  raise FileNotFoundError(
45
- f"Emotion model not found at {self.model_dir}. "
46
- "Train it first with notebooks/module_2_emotion_training.ipynb."
47
  )
48
 
49
  torch, model_cls, tokenizer_cls = _load_transformer_stack()
50
  self.torch = torch
51
- self.tokenizer = tokenizer_cls.from_pretrained(self.model_dir)
52
- self.model = model_cls.from_pretrained(self.model_dir)
53
  self.model.eval()
54
- self.active_model_source = str(self.model_dir)
55
 
56
  config_labels = self.model.config.id2label
57
  self.id2label = {int(key): value for key, value in config_labels.items()}
58
 
 
 
 
 
 
 
 
59
  def _score_text(self, text: str) -> dict[str, Any]:
60
  if self.model is None or self.tokenizer is None or self.torch is None:
61
  self.load_model()
 
10
 
11
  PROJECT_ROOT = Path(__file__).resolve().parents[2]
12
  DEFAULT_MODEL_DIR = PROJECT_ROOT / "src" / "models" / "saved_emotion_model"
13
+ DEFAULT_HF_MODEL_ID = ""
14
 
15
 
16
  def _load_transformer_stack() -> tuple[Any, Any, Any]:
 
34
  model_dir: str | Path | None = None,
35
  ) -> None:
36
  self.model_dir = Path(model_dir or os.getenv("EMOTION_MODEL_DIR", DEFAULT_MODEL_DIR))
37
+ self.model_id = os.getenv("EMOTION_MODEL_ID", DEFAULT_HF_MODEL_ID).strip()
38
+ self.active_model_source = str(self.model_dir if self.model_dir.exists() else self.model_id)
39
  self.torch = None
40
  self.tokenizer = None
41
  self.model = None
42
  self.id2label: dict[int, str] = {}
43
 
44
  def load_model(self) -> None:
45
+ model_source = self._resolve_model_source()
46
+ if not model_source:
47
  raise FileNotFoundError(
48
+ "Emotion model is not available. Train Module 2 locally, or set "
49
+ "EMOTION_MODEL_ID to a Hugging Face model repository."
50
  )
51
 
52
  torch, model_cls, tokenizer_cls = _load_transformer_stack()
53
  self.torch = torch
54
+ self.tokenizer = tokenizer_cls.from_pretrained(model_source)
55
+ self.model = model_cls.from_pretrained(model_source)
56
  self.model.eval()
57
+ self.active_model_source = str(model_source)
58
 
59
  config_labels = self.model.config.id2label
60
  self.id2label = {int(key): value for key, value in config_labels.items()}
61
 
62
+ def _resolve_model_source(self) -> str | Path | None:
63
+ if self.model_dir.exists():
64
+ return self.model_dir
65
+ if self.model_id:
66
+ return self.model_id
67
+ return None
68
+
69
  def _score_text(self, text: str) -> dict[str, Any]:
70
  if self.model is None or self.tokenizer is None or self.torch is None:
71
  self.load_model()
src/models/language_classifier.py CHANGED
@@ -1,6 +1,7 @@
1
  from __future__ import annotations
2
  import argparse
3
  import json
 
4
  import sys
5
  from pathlib import Path
6
  from typing import Any
@@ -17,6 +18,7 @@ DATA_DIR = PROJECT_ROOT / "data"
17
  MODEL_DIR = PROJECT_ROOT / "src" / "models"
18
  REPORTS_DIR = PROJECT_ROOT / "reports" / "module_1_language_detection"
19
  DEFAULT_MODEL_PATH = MODEL_DIR / "saved_lang_model.pkl"
 
20
 
21
  LANGUAGE_NAMES = {
22
  "ar": "Arabic",
@@ -47,10 +49,10 @@ class LanguageDetector:
47
 
48
  def __init__(
49
  self,
50
- model_path: str | Path = DEFAULT_MODEL_PATH,
51
  confidence_threshold: float = 0.65,
52
  ) -> None:
53
- self.model_path = Path(model_path)
54
  self.confidence_threshold = confidence_threshold
55
  self.pipeline = self._build_pipeline()
56
 
@@ -191,11 +193,29 @@ class LanguageDetector:
191
  print(f"Saved evaluation reports to {REPORTS_DIR}")
192
 
193
  def load_model(self) -> None:
194
- if not self.model_path.exists():
 
195
  raise FileNotFoundError(
196
- f"Model not found at {self.model_path}. Run training first."
 
197
  )
198
- self.pipeline = joblib.load(self.model_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
  def predict(self, text: str) -> str:
201
  return self.predict_with_confidence(text)["language_code"]
@@ -249,7 +269,7 @@ if __name__ == "__main__":
249
  sample_texts = [
250
  "I feel anxious and need someone to talk to.",
251
  "أنا أشعر بالقلق وأحتاج إلى المساعدة.",
252
- "Je me sens stresse aujourd'hui.",
253
  ]
254
 
255
  print("\nSample predictions:")
 
1
  from __future__ import annotations
2
  import argparse
3
  import json
4
+ import os
5
  import sys
6
  from pathlib import Path
7
  from typing import Any
 
18
  MODEL_DIR = PROJECT_ROOT / "src" / "models"
19
  REPORTS_DIR = PROJECT_ROOT / "reports" / "module_1_language_detection"
20
  DEFAULT_MODEL_PATH = MODEL_DIR / "saved_lang_model.pkl"
21
+ DEFAULT_HF_MODEL_FILENAME = "saved_lang_model.pkl"
22
 
23
  LANGUAGE_NAMES = {
24
  "ar": "Arabic",
 
49
 
50
  def __init__(
51
  self,
52
+ model_path: str | Path | None = None,
53
  confidence_threshold: float = 0.65,
54
  ) -> None:
55
+ self.model_path = Path(model_path or os.getenv("LANGUAGE_MODEL_PATH", DEFAULT_MODEL_PATH))
56
  self.confidence_threshold = confidence_threshold
57
  self.pipeline = self._build_pipeline()
58
 
 
193
  print(f"Saved evaluation reports to {REPORTS_DIR}")
194
 
195
  def load_model(self) -> None:
196
+ model_path = self._resolve_model_path()
197
+ if not model_path.exists():
198
  raise FileNotFoundError(
199
+ "Language model is not available. Train Module 1 locally, or set "
200
+ "LANGUAGE_MODEL_REPO_ID to a Hugging Face model repository."
201
  )
202
+ self.pipeline = joblib.load(model_path)
203
+
204
+ def _resolve_model_path(self) -> Path:
205
+ if self.model_path.exists():
206
+ return self.model_path
207
+
208
+ repo_id = os.getenv("LANGUAGE_MODEL_REPO_ID")
209
+ if not repo_id:
210
+ return self.model_path
211
+
212
+ filename = os.getenv("LANGUAGE_MODEL_FILENAME", DEFAULT_HF_MODEL_FILENAME)
213
+ try:
214
+ from huggingface_hub import hf_hub_download
215
+ except ImportError as exc:
216
+ raise ImportError("Install huggingface_hub to load the language model from Hugging Face Hub.") from exc
217
+
218
+ return Path(hf_hub_download(repo_id=repo_id, filename=filename))
219
 
220
  def predict(self, text: str) -> str:
221
  return self.predict_with_confidence(text)["language_code"]
 
269
  sample_texts = [
270
  "I feel anxious and need someone to talk to.",
271
  "أنا أشعر بالقلق وأحتاج إلى المساعدة.",
272
+ "Je me sens stressé aujourd'hui.",
273
  ]
274
 
275
  print("\nSample predictions:")