MohammedMediani commited on
Commit
a61a8ff
·
1 Parent(s): bc84fc0

Professionalize API: Clean code, HF Model Hub integration, new README

Browse files
Dockerfile CHANGED
@@ -16,9 +16,8 @@ RUN pip install --no-cache-dir -r requirements.txt
16
  # Cela inclut main.py et le dossier de votre modèle (ex: "marbert-darija-nlu-aicc")
17
  COPY . .
18
 
19
- # Étape 6: Exposer le port que votre API utilise
20
- EXPOSE 8000
21
 
22
- # Étape 7: La commande pour lancer l'API quand le container démarre
23
- # Uvicorn est lancé avec host="0.0.0.0" pour être accessible de l'extérieur du container
24
- CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
 
16
  # Cela inclut main.py et le dossier de votre modèle (ex: "marbert-darija-nlu-aicc")
17
  COPY . .
18
 
19
+ # Step 6: Expose the port used by the API (Hugging Face Spaces defaults to 7860)
20
+ EXPOSE 7860
21
 
22
+ # Step 7: Launch the API
23
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
 
README.md CHANGED
@@ -4,77 +4,61 @@ emoji: 🚀
4
  colorFrom: indigo
5
  colorTo: blue
6
  sdk: docker
7
- app_port: 8000
8
  ---
9
 
10
- # API de Classification d'Intention en Darija pour AICC
11
 
12
- Ce projet a été développé dans le cadre d'un Projet de Fin d'Études visant à intégrer le dialecte marocain "Darija" dans la solution **AICC (Artificial Intelligence Contact Center)** de Huawei.
 
 
 
13
 
14
- L'API utilise un modèle **MARBERTv2**, un Transformer pré-entraîné pour l'arabe et ses dialectes, qui a été fine-tuné sur un corpus personnalisé pour classifier les intentions des utilisateurs s'exprimant en Darija.
15
 
16
- [![Hugging Face Spaces](https://img.shields.io/badge/🤗%20Spaces-Live%20Demo%20API-yellow)](https://mediani-darija-aicc-api.hf.space/docs)
17
 
18
- ---
19
-
20
- ## 🚀 API Déployée et Documentation Interactive
21
-
22
- L'API est en ligne et pleinement fonctionnelle. Vous pouvez la tester en direct grâce à l'interface Swagger UI générée automatiquement.
23
-
24
- **➡️ [Tester l'API interactivement ici](https://mediani-darija-aicc-api.hf.space/docs)**
25
-
26
- ---
27
 
28
- ## 🔧 Comment Utiliser l'API
29
 
30
- L'API expose un endpoint principal `/predict` qui accepte les requêtes `POST` pour la classification d'intention.
31
 
32
- ### Exemple de Requête avec `curl`
33
-
34
- Voici comment interroger l'API depuis un terminal :
35
-
36
- ```bash
37
- curl -X 'POST' \
38
- 'https://mediani-darija-aicc-api.hf.space/predict' \
39
- -H 'accept: application/json' \
40
- -H 'Content-Type: application/json' \
41
- -d '{"text": "Salam, la connexion 4G naqsa 3ndi bzaf"}'
42
  ```
43
 
44
- ### Exemple de Réponse Attendue
45
-
46
- L'API retournera un objet JSON avec l'intention (intent) prédite par le modèle et son score de confiance (confidence).
47
-
48
  ```json
49
  {
50
- "intent": "declarer_panne",
51
- "confidence": 0.9954321098
52
  }
53
  ```
54
 
55
- ---
56
 
57
- ## 📋 Liste des Intentions Reconnues
58
-
59
- Le modèle a été entraîné pour reconnaître et classifier les 9 intentions suivantes, qui sont les plus courantes dans un contexte de service client :
 
 
 
60
 
61
- - **consulter_solde**: Demandes concernant le solde, la recharge ou les données restantes.
62
- - **reclamer_facture**: Réclamations concernant une facture (montant élevé, erreur...).
63
- - **declarer_panne**: Signalement d'un problème technique (panne réseau, connexion lente...).
64
- - **info_forfait**: Demandes d'informations sur les produits, offres et abonnements.
65
- - **recuperer_mot_de_passe**: Demandes liées à la réinitialisation d'un mot de passe ou d'un code.
66
- - **salutations**: Salutations et début de conversation.
67
- - **remerciements**: Expressions de gratitude.
68
- - **demander_agent_humain**: Demande explicite de parler à un conseiller humain.
69
- - **hors_scope**: Toute demande hors du périmètre du service client.
70
 
71
- ## 🛠️ Stack Technique & Cycle de Vie du Projet
 
 
 
72
 
73
- Ce projet a été réalisé en suivant un cycle de vie complet, du prototypage au déploiement :
74
 
75
- - **Modèle**: UBC-NLP/MARBERTv2 fine-tuné avec la bibliothèque transformers de Hugging Face.
76
- - **Corpus**: Un corpus personnalisé a été assemblé en combinant la collecte de données (Twitter, YouTube), la génération par IA, et l'annotation manuelle avec Doccano.
77
- - **Framework API**: FastAPI, pour sa rapidité et sa génération automatique de documentation.
78
- - **Conteneurisation**: Docker, pour garantir la portabilité et la reproductibilité de l'environnement.
79
- - **Versionnement**: Git & Git LFS pour gérer les gros fichiers de modèle (plus de 100 Mo).
80
- - **Déploiement**: L'API est hébergée sur Hugging Face Spaces, fournissant une solution CI/CD (intégration et déploiement continus) à partir d'un dépôt Git.
 
4
  colorFrom: indigo
5
  colorTo: blue
6
  sdk: docker
7
+ app_port: 7860
8
  ---
9
 
10
+ # Darija NLU API 🚀
11
 
12
+ [![API Status](https://img.shields.io/website?url=https%3A%2F%2Fmohammedmediani-darija-aicc-api.hf.space%2Fhealth&label=API%20Status)](https://mohammedmediani-darija-aicc-api.hf.space/docs)
13
+ [![Python](https://img.shields.io/badge/Python-3.9+-3776AB?logo=python&logoColor=white)](https://python.org)
14
+ [![FastAPI](https://img.shields.io/badge/FastAPI-0.68+-009688?logo=fastapi&logoColor=white)](https://fastapi.tiangolo.com)
15
+ [![Model](https://img.shields.io/badge/🤗%20Model-MARBERTv2-orange)](https://huggingface.co/mediani/marbert-fine-tuned-darija-aicc)
16
 
17
+ A professional REST API for **Natural Language Understanding (NLU)** in Moroccan Arabic (Darija). Designed to power intelligent contact centers and automated support systems.
18
 
19
+ ## 🔗 Ecosystem
20
 
21
+ | Project | Description | Link |
22
+ |---------|-------------|------|
23
+ | 📺 **Demo App** | Interactive Streamlit UI | [Go to Space](https://huggingface.co/spaces/mediani/darija-nlu-demo) |
24
+ | 🧠 **Model** | Fine-tuned MARBERTv2 | [Go to Model](https://huggingface.co/mediani/marbert-fine-tuned-darija-aicc) |
25
+ | 💻 **Source Code** | GitHub Repository | [Go to GitHub](https://github.com/mohammedmediani/aicc-nlu-api) |
 
 
 
 
26
 
27
+ ## 🚀 Quick Usage
28
 
29
+ ### Endpoint: `/predict` (POST)
30
 
31
+ **Request:**
32
+ ```json
33
+ {
34
+ "text": "bghit n3raf solde dyali"
35
+ }
 
 
 
 
 
36
  ```
37
 
38
+ **Response:**
 
 
 
39
  ```json
40
  {
41
+ "intent": "consulter_solde",
42
+ "confidence": 0.985
43
  }
44
  ```
45
 
46
+ ### Try it with cURL
47
 
48
+ ```bash
49
+ curl -X 'POST' \
50
+ 'https://mohammedmediani-darija-aicc-api.hf.space/predict' \
51
+ -H 'Content-Type: application/json' \
52
+ -d '{"text": "llah ykhalik bghit nchof factura"}'
53
+ ```
54
 
55
+ ## 📋 Features
 
 
 
 
 
 
 
 
56
 
57
+ - **9 Intent Categories**: From balance checks (`consulter_solde`) to technical support (`declarer_panne`).
58
+ - **High Performance**: Fine-tuned MARBERTv2 achieving >92% F1-score.
59
+ - **Production Ready**: Built with FastAPI, utilizing async capabilities and robust error handling.
60
+ - **Code-Switching**: Handles mixed Darija/French input natively.
61
 
62
+ ## 📄 License
63
 
64
+ Apache 2.0
 
 
 
 
 
main.py CHANGED
@@ -1,98 +1,108 @@
1
- # main.py
 
 
 
2
 
3
- import torch
4
- from fastapi import FastAPI, HTTPException
5
- from pydantic import BaseModel
6
- from transformers import pipeline, AutoModelForSequenceClassification, AutoTokenizer
7
  from typing import Dict, Any
8
 
 
 
 
 
9
  # --- Configuration ---
10
- # Chemin vers votre modèle fine-tuné. Assurez-vous que ce dossier est correct.
11
- MODEL_PATH = "./mon_modele_darija_final"
12
 
13
- # --- Chargement du modèle (partie critique) ---
14
- # Cette partie est exécutée une seule fois, au démarrage du serveur.
15
- # C'est une bonne pratique pour éviter de recharger le modèle à chaque requête.
16
- try:
17
- print("Chargement du tokenizer et du modèle MARBERT fine-tuné...")
18
-
19
- # On spécifie le device (GPU si disponible, sinon CPU)
20
- device = 0 if torch.cuda.is_available() else -1
21
-
22
- # Création du pipeline de classification de texte de Hugging Face.
23
- # C'est la manière la plus simple d'utiliser un modèle pour l'inférence.
24
- nlu_pipeline = pipeline(
25
- "text-classification",
26
- model=MODEL_PATH,
27
- tokenizer=MODEL_PATH,
28
- device=device # Utilise le GPU si disponible
29
- )
30
- print("Modèle chargé avec succès !")
31
 
32
- except Exception as e:
33
- # Si le modèle ne peut pas être chargé, on lève une erreur claire.
34
- print(f"Erreur critique lors du chargement du modèle: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  nlu_pipeline = None
36
 
37
- # --- Définition de l'application FastAPI ---
38
  app = FastAPI(
39
- title="API de NLU pour Darija Marocaine",
40
- description="Une API pour classifier l'intention d'un texte en Darija, basée sur MARBERT.",
41
- version="1.0.0"
 
 
 
42
  )
43
 
44
- # --- Définition des modèles de données (Pydantic) ---
45
- # C'est pour la validation automatique des requêtes.
46
 
47
  class TextInput(BaseModel):
48
- """Modèle pour le corps de la requête de prédiction."""
49
- text: str # Le champ doit s'appeler 'text'
50
- # Exemple de requête JSON attendue: {"text": "3afak bghit nchouf lfactura"}
51
 
52
  class PredictionResponse(BaseModel):
53
- """Modèle pour la réponse de l'API."""
54
- intent: str
55
- confidence: float
56
 
57
- # --- Définition des routes de l'API ---
58
 
59
- @app.get("/", tags=["Général"])
60
  def read_root() -> Dict[str, str]:
61
- """Route principale qui retourne un message de bienvenue."""
62
- return {"message": "Bienvenue sur l'API de NLU Darija. Utilisez le endpoint /predict pour faire une prédiction."}
63
-
64
 
65
- @app.get("/health", tags=["Général"])
66
  def health_check() -> Dict[str, str]:
67
- """Route de 'health check' pour vérifier si le service est en ligne et le modèle chargé."""
68
  if nlu_pipeline is None:
69
- raise HTTPException(status_code=500, detail="Erreur: Le modèle NLP n'a pas pu être chargé.")
70
  return {"status": "ok", "model_status": "loaded"}
71
 
72
-
73
- @app.post("/predict", response_model=PredictionResponse, tags=["Prédiction"])
74
- def predict_intent(request: TextInput) -> PredictionResponse:
75
  """
76
- Endpoint principal pour la prédiction d'intention.
77
- Prend un texte en entrée et retourne l'intention prédite et son score de confiance.
78
  """
79
  if nlu_pipeline is None:
80
- raise HTTPException(status_code=503, detail="Le service est indisponible car le modèle n'est pas chargé.")
81
-
82
- if not request.text or not request.text.strip():
83
- raise HTTPException(status_code=400, detail="Le champ 'text' ne peut pas être vide.")
84
 
85
  try:
86
- # Utilisation du pipeline pour faire la prédiction
 
87
  prediction = nlu_pipeline(request.text, top_k=1)[0]
88
 
89
- # Le pipeline retourne un dictionnaire avec 'label' et 'score'
90
- # On renomme pour correspondre à notre modèle de réponse
91
- intent = prediction['label']
92
- confidence = prediction['score']
93
-
94
- return PredictionResponse(intent=intent, confidence=confidence)
95
-
96
  except Exception as e:
97
- # Gestion d'erreurs inattendues pendant la prédiction
98
- raise HTTPException(status_code=500, detail=f"Une erreur interne est survenue: {str(e)}")
 
 
 
 
 
 
1
+ """
2
+ Darija NLU API - Professional REST API for Moroccan Arabic Sentiment/Intent Classification.
3
+ Powered by MARBERTv2 fine-tuned on Darija.
4
+ """
5
 
6
+ import os
7
+ from contextlib import asynccontextmanager
 
 
8
  from typing import Dict, Any
9
 
10
+ from fastapi import FastAPI, HTTPException
11
+ from pydantic import BaseModel, Field
12
+ from transformers import pipeline
13
+
14
  # --- Configuration ---
15
+ MODEL_ID = "mediani/marbert-fine-tuned-darija-aicc"
 
16
 
17
+ # Global pipeline variable
18
+ nlu_pipeline = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ @asynccontextmanager
21
+ async def lifespan(app: FastAPI):
22
+ """
23
+ Lifespan context manager for loading the model on startup.
24
+ This ensures the model is loaded only once.
25
+ """
26
+ global nlu_pipeline
27
+ try:
28
+ print(f"Loading model from HuggingFace Hub: {MODEL_ID}...")
29
+ # device=0 uses GPU if available, -1 uses CPU
30
+ # We rely on transformers to auto-detect the best available device if not specified,
31
+ # but explicit integer is often safer for pipelines.
32
+ import torch
33
+ device = 0 if torch.cuda.is_available() else -1
34
+
35
+ nlu_pipeline = pipeline(
36
+ "text-classification",
37
+ model=MODEL_ID,
38
+ tokenizer=MODEL_ID,
39
+ device=device
40
+ )
41
+ print("Model loaded successfully!")
42
+ except Exception as e:
43
+ print(f"CRITICAL: Failed to load model: {e}")
44
+ nlu_pipeline = None
45
+ yield
46
+ # Cleanup if necessary
47
  nlu_pipeline = None
48
 
49
+ # --- FastAPI App Definition ---
50
  app = FastAPI(
51
+ title="Darija NLU API",
52
+ description="Professional API for intent classification in Moroccan Darija (Arabic Dialect).",
53
+ version="1.0.0",
54
+ lifespan=lifespan,
55
+ docs_url="/docs",
56
+ redoc_url="/redoc"
57
  )
58
 
59
+ # --- Data Models ---
 
60
 
61
  class TextInput(BaseModel):
62
+ """Request model for text classification."""
63
+ text: str = Field(..., description="The text in Darija to analyze", min_length=1, example="3afak bghit nchouf solde")
 
64
 
65
  class PredictionResponse(BaseModel):
66
+ """Response model containing the predicted intent and confidence score."""
67
+ intent: str = Field(..., description="Predicted intent label")
68
+ confidence: float = Field(..., description="Confidence score between 0.0 and 1.0")
69
 
70
+ # --- Routes ---
71
 
72
+ @app.get("/", tags=["General"])
73
  def read_root() -> Dict[str, str]:
74
+ """Root endpoint returning welcome message."""
75
+ return {"message": "Welcome to the Darija NLU API. Use POST /predict to analyze text."}
 
76
 
77
+ @app.get("/health", tags=["General"])
78
  def health_check() -> Dict[str, str]:
79
+ """Health check endpoint to verify service status and model loading."""
80
  if nlu_pipeline is None:
81
+ raise HTTPException(status_code=503, detail="Service initializing or model failed to load.")
82
  return {"status": "ok", "model_status": "loaded"}
83
 
84
+ @app.post("/predict", response_model=PredictionResponse, tags=["Inference"])
85
+ async def predict_intent(request: TextInput) -> PredictionResponse:
 
86
  """
87
+ Predict the intent of the provided Darija text.
 
88
  """
89
  if nlu_pipeline is None:
90
+ raise HTTPException(status_code=503, detail="Model not initialized.")
 
 
 
91
 
92
  try:
93
+ # Pipeline returns a list of dicts: [{'label': 'intent_name', 'score': 0.99}]
94
+ # We assume top_k=1 by default
95
  prediction = nlu_pipeline(request.text, top_k=1)[0]
96
 
97
+ return PredictionResponse(
98
+ intent=prediction['label'],
99
+ confidence=prediction['score']
100
+ )
 
 
 
101
  except Exception as e:
102
+ # Log the error internally here
103
+ print(f"Inference error: {e}")
104
+ raise HTTPException(status_code=500, detail="Internal processing error")
105
+
106
+ if __name__ == "__main__":
107
+ import uvicorn
108
+ uvicorn.run(app, host="0.0.0.0", port=7860) # 7860 is the default port for HF Spaces
mon_modele_darija_final/config.json DELETED
@@ -1,54 +0,0 @@
1
- {
2
- "architectures": [
3
- "BertForSequenceClassification"
4
- ],
5
- "attention_probs_dropout_prob": 0.1,
6
- "classifier_dropout": null,
7
- "directionality": "bidi",
8
- "gradient_checkpointing": false,
9
- "hidden_act": "gelu",
10
- "hidden_dropout_prob": 0.1,
11
- "hidden_size": 768,
12
- "id2label": {
13
- "0": "consulter_solde",
14
- "1": "declarer_panne",
15
- "2": "demander_agent_humain",
16
- "3": "hors_scope",
17
- "4": "info_forfait",
18
- "5": "reclamer_facture",
19
- "6": "recuperer_mot_de_passe",
20
- "7": "remerciements",
21
- "8": "salutations"
22
- },
23
- "initializer_range": 0.02,
24
- "intermediate_size": 3072,
25
- "label2id": {
26
- "consulter_solde": 0,
27
- "declarer_panne": 1,
28
- "demander_agent_humain": 2,
29
- "hors_scope": 3,
30
- "info_forfait": 4,
31
- "reclamer_facture": 5,
32
- "recuperer_mot_de_passe": 6,
33
- "remerciements": 7,
34
- "salutations": 8
35
- },
36
- "layer_norm_eps": 1e-12,
37
- "max_position_embeddings": 512,
38
- "model_type": "bert",
39
- "num_attention_heads": 12,
40
- "num_hidden_layers": 12,
41
- "pad_token_id": 0,
42
- "pooler_fc_size": 768,
43
- "pooler_num_attention_heads": 12,
44
- "pooler_num_fc_layers": 3,
45
- "pooler_size_per_head": 128,
46
- "pooler_type": "first_token_transform",
47
- "position_embedding_type": "absolute",
48
- "problem_type": "single_label_classification",
49
- "torch_dtype": "float32",
50
- "transformers_version": "4.52.4",
51
- "type_vocab_size": 2,
52
- "use_cache": true,
53
- "vocab_size": 100000
54
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
mon_modele_darija_final/model.safetensors DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:08322d4ab747d8187518d1d649c0bd36e7592fe4224f6b9885c3d2abe821d689
3
- size 651416604
 
 
 
 
mon_modele_darija_final/special_tokens_map.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "cls_token": "[CLS]",
3
- "mask_token": "[MASK]",
4
- "pad_token": "[PAD]",
5
- "sep_token": "[SEP]",
6
- "unk_token": "[UNK]"
7
- }
 
 
 
 
 
 
 
 
mon_modele_darija_final/tokenizer.json DELETED
The diff for this file is too large to render. See raw diff
 
mon_modele_darija_final/tokenizer_config.json DELETED
@@ -1,58 +0,0 @@
1
- {
2
- "added_tokens_decoder": {
3
- "0": {
4
- "content": "[PAD]",
5
- "lstrip": false,
6
- "normalized": false,
7
- "rstrip": false,
8
- "single_word": false,
9
- "special": true
10
- },
11
- "1": {
12
- "content": "[UNK]",
13
- "lstrip": false,
14
- "normalized": false,
15
- "rstrip": false,
16
- "single_word": false,
17
- "special": true
18
- },
19
- "2": {
20
- "content": "[CLS]",
21
- "lstrip": false,
22
- "normalized": false,
23
- "rstrip": false,
24
- "single_word": false,
25
- "special": true
26
- },
27
- "3": {
28
- "content": "[SEP]",
29
- "lstrip": false,
30
- "normalized": false,
31
- "rstrip": false,
32
- "single_word": false,
33
- "special": true
34
- },
35
- "4": {
36
- "content": "[MASK]",
37
- "lstrip": false,
38
- "normalized": false,
39
- "rstrip": false,
40
- "single_word": false,
41
- "special": true
42
- }
43
- },
44
- "clean_up_tokenization_spaces": true,
45
- "cls_token": "[CLS]",
46
- "do_basic_tokenize": true,
47
- "do_lower_case": true,
48
- "extra_special_tokens": {},
49
- "mask_token": "[MASK]",
50
- "model_max_length": 1000000000000000019884624838656,
51
- "never_split": null,
52
- "pad_token": "[PAD]",
53
- "sep_token": "[SEP]",
54
- "strip_accents": null,
55
- "tokenize_chinese_chars": true,
56
- "tokenizer_class": "BertTokenizer",
57
- "unk_token": "[UNK]"
58
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
mon_modele_darija_final/training_args.bin DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:1bd13abe00ada94ffbf7c954ed271cc6b814dccf8eb05202ad4977182cdba021
3
- size 5304
 
 
 
 
mon_modele_darija_final/vocab.txt DELETED
The diff for this file is too large to render. See raw diff
 
requirements.txt CHANGED
@@ -1,14 +1,7 @@
1
- # ---- Core API Framework ----
2
- fastapi
3
- uvicorn
4
-
5
- # ---- Machine Learning Model & Pipeline ----
6
- # On ne spécifie pas la version de torch car il est préférable de l'installer séparément
7
- # ou de laisser pip résoudre la dépendance en fonction de la plateforme (CPU/GPU)
8
- # mais pour une image Docker déterministe, la figer est une option.
9
- torch==2.7.1
10
- transformers==4.52.4
11
-
12
- # ---- FastAPI Specific ----
13
- # Nécessaire pour gérer les formulaires et le téléversement de fichiers, bonne pratique.
14
- python-multipart
 
1
+ fastapi>=0.68.0
2
+ uvicorn>=0.15.0
3
+ torch>=1.9.0
4
+ transformers>=4.10.0
5
+ pydantic>=1.8.0
6
+ sentencepiece
7
+ protobuf