SaherMuhamed commited on
Commit
d657608
·
1 Parent(s): 7b5e5f7

add the fine tuned BERT model with flask app integrated with Fast API

Browse files
Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /code
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
7
+
8
+ # Copy requirements and install
9
+ COPY requirements.txt .
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ # Copy the rest of the code
13
+ COPY . .
14
+
15
+ # Expose FastAPI port
16
+ EXPOSE 8000
17
+
18
+ # Hugging Face Spaces expects the app to run on 0.0.0.0:8000
19
+ CMD ["uvicorn", "intent-classifier-chatbot.model.api.api:app", "--host", "0.0.0.0", "--port", "8000"]
README.md CHANGED
@@ -1,12 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: Intent Classifier Chatbot
3
- emoji: 🐨
4
- colorFrom: green
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
- short_description: Intent Detection API using BERT and Flask
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  ---
 
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🤖 Bert Intent Chatbot (Encoder-Only)
2
+
3
+ **Intent Detection API using BERT and Flask**
4
+ This project uses a fine-tuned BERT model that is an encoder only model without a decoder used to detect user intent from text inputs (e.g., chatbot queries). It provides a lightweight Flask API to classify input sentences into predefined intent categories.
5
+
6
+ ---
7
+
8
+ ## 🔧 Features
9
+
10
+ - ✅ Pretrained BERT fine-tuned on [CLINC150](https://huggingface.co/datasets/clinc_oos)
11
+ - 🧠 Real-time intent classification from natural text
12
+ - 🌐 REST API using Flask
13
+ - 🤖 Easy to integrate with chatbots, voice assistants, or NLP systems
14
+
15
  ---
16
+ ## 📊 Dataset: CLINC150
17
+
18
+ The project uses the **CLINC150 dataset**, a benchmark dataset for intent classification in task-oriented dialogue systems.
19
+
20
+ ### 🧾 Overview
21
+
22
+ - **Total intents**: 150 unique user intents
23
+ - **Domains**: 10 real-world domains (e.g., banking, travel, weather, small talk)
24
+ - **Examples**: ~22,500 utterances
25
+ - **Language**: English
26
+ - **Out-of-scope (OOS)**: Includes OOS examples to test robustness
27
+
28
+ ### 📁 Dataset Splits
29
+
30
+ | Split | Examples |
31
+ |-------------|----------|
32
+ | Train | 7,000 |
33
+ | Validation | 3,000 |
34
+ | Test | 5,500 |
35
+
36
+ ### 📦 Source
37
+
38
+ - Official repo: [clinc/oos-eval](https://github.com/clinc/oos-eval)
39
+ - Hugging Face: [`clinc_oos`](https://huggingface.co/datasets/clinc_oos)
40
+
41
+
42
  ---
43
+ ## 🚀 Example
44
 
45
+ ### Request
46
+ ```bash
47
+ {
48
+ "text": "I want to book a flight"
49
+ }
50
+ ```
51
+ ### Response
52
+ ```bash
53
+ {
54
+ "intent": "book_flight"
55
+ }
56
+ ```
intent_classifier_model/config.json ADDED
@@ -0,0 +1,332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "BertForSequenceClassification"
4
+ ],
5
+ "attention_probs_dropout_prob": 0.1,
6
+ "classifier_dropout": null,
7
+ "gradient_checkpointing": false,
8
+ "hidden_act": "gelu",
9
+ "hidden_dropout_prob": 0.1,
10
+ "hidden_size": 768,
11
+ "id2label": {
12
+ "0": "LABEL_0",
13
+ "1": "LABEL_1",
14
+ "2": "LABEL_2",
15
+ "3": "LABEL_3",
16
+ "4": "LABEL_4",
17
+ "5": "LABEL_5",
18
+ "6": "LABEL_6",
19
+ "7": "LABEL_7",
20
+ "8": "LABEL_8",
21
+ "9": "LABEL_9",
22
+ "10": "LABEL_10",
23
+ "11": "LABEL_11",
24
+ "12": "LABEL_12",
25
+ "13": "LABEL_13",
26
+ "14": "LABEL_14",
27
+ "15": "LABEL_15",
28
+ "16": "LABEL_16",
29
+ "17": "LABEL_17",
30
+ "18": "LABEL_18",
31
+ "19": "LABEL_19",
32
+ "20": "LABEL_20",
33
+ "21": "LABEL_21",
34
+ "22": "LABEL_22",
35
+ "23": "LABEL_23",
36
+ "24": "LABEL_24",
37
+ "25": "LABEL_25",
38
+ "26": "LABEL_26",
39
+ "27": "LABEL_27",
40
+ "28": "LABEL_28",
41
+ "29": "LABEL_29",
42
+ "30": "LABEL_30",
43
+ "31": "LABEL_31",
44
+ "32": "LABEL_32",
45
+ "33": "LABEL_33",
46
+ "34": "LABEL_34",
47
+ "35": "LABEL_35",
48
+ "36": "LABEL_36",
49
+ "37": "LABEL_37",
50
+ "38": "LABEL_38",
51
+ "39": "LABEL_39",
52
+ "40": "LABEL_40",
53
+ "41": "LABEL_41",
54
+ "42": "LABEL_42",
55
+ "43": "LABEL_43",
56
+ "44": "LABEL_44",
57
+ "45": "LABEL_45",
58
+ "46": "LABEL_46",
59
+ "47": "LABEL_47",
60
+ "48": "LABEL_48",
61
+ "49": "LABEL_49",
62
+ "50": "LABEL_50",
63
+ "51": "LABEL_51",
64
+ "52": "LABEL_52",
65
+ "53": "LABEL_53",
66
+ "54": "LABEL_54",
67
+ "55": "LABEL_55",
68
+ "56": "LABEL_56",
69
+ "57": "LABEL_57",
70
+ "58": "LABEL_58",
71
+ "59": "LABEL_59",
72
+ "60": "LABEL_60",
73
+ "61": "LABEL_61",
74
+ "62": "LABEL_62",
75
+ "63": "LABEL_63",
76
+ "64": "LABEL_64",
77
+ "65": "LABEL_65",
78
+ "66": "LABEL_66",
79
+ "67": "LABEL_67",
80
+ "68": "LABEL_68",
81
+ "69": "LABEL_69",
82
+ "70": "LABEL_70",
83
+ "71": "LABEL_71",
84
+ "72": "LABEL_72",
85
+ "73": "LABEL_73",
86
+ "74": "LABEL_74",
87
+ "75": "LABEL_75",
88
+ "76": "LABEL_76",
89
+ "77": "LABEL_77",
90
+ "78": "LABEL_78",
91
+ "79": "LABEL_79",
92
+ "80": "LABEL_80",
93
+ "81": "LABEL_81",
94
+ "82": "LABEL_82",
95
+ "83": "LABEL_83",
96
+ "84": "LABEL_84",
97
+ "85": "LABEL_85",
98
+ "86": "LABEL_86",
99
+ "87": "LABEL_87",
100
+ "88": "LABEL_88",
101
+ "89": "LABEL_89",
102
+ "90": "LABEL_90",
103
+ "91": "LABEL_91",
104
+ "92": "LABEL_92",
105
+ "93": "LABEL_93",
106
+ "94": "LABEL_94",
107
+ "95": "LABEL_95",
108
+ "96": "LABEL_96",
109
+ "97": "LABEL_97",
110
+ "98": "LABEL_98",
111
+ "99": "LABEL_99",
112
+ "100": "LABEL_100",
113
+ "101": "LABEL_101",
114
+ "102": "LABEL_102",
115
+ "103": "LABEL_103",
116
+ "104": "LABEL_104",
117
+ "105": "LABEL_105",
118
+ "106": "LABEL_106",
119
+ "107": "LABEL_107",
120
+ "108": "LABEL_108",
121
+ "109": "LABEL_109",
122
+ "110": "LABEL_110",
123
+ "111": "LABEL_111",
124
+ "112": "LABEL_112",
125
+ "113": "LABEL_113",
126
+ "114": "LABEL_114",
127
+ "115": "LABEL_115",
128
+ "116": "LABEL_116",
129
+ "117": "LABEL_117",
130
+ "118": "LABEL_118",
131
+ "119": "LABEL_119",
132
+ "120": "LABEL_120",
133
+ "121": "LABEL_121",
134
+ "122": "LABEL_122",
135
+ "123": "LABEL_123",
136
+ "124": "LABEL_124",
137
+ "125": "LABEL_125",
138
+ "126": "LABEL_126",
139
+ "127": "LABEL_127",
140
+ "128": "LABEL_128",
141
+ "129": "LABEL_129",
142
+ "130": "LABEL_130",
143
+ "131": "LABEL_131",
144
+ "132": "LABEL_132",
145
+ "133": "LABEL_133",
146
+ "134": "LABEL_134",
147
+ "135": "LABEL_135",
148
+ "136": "LABEL_136",
149
+ "137": "LABEL_137",
150
+ "138": "LABEL_138",
151
+ "139": "LABEL_139",
152
+ "140": "LABEL_140",
153
+ "141": "LABEL_141",
154
+ "142": "LABEL_142",
155
+ "143": "LABEL_143",
156
+ "144": "LABEL_144",
157
+ "145": "LABEL_145",
158
+ "146": "LABEL_146",
159
+ "147": "LABEL_147",
160
+ "148": "LABEL_148",
161
+ "149": "LABEL_149",
162
+ "150": "LABEL_150"
163
+ },
164
+ "initializer_range": 0.02,
165
+ "intermediate_size": 3072,
166
+ "label2id": {
167
+ "LABEL_0": 0,
168
+ "LABEL_1": 1,
169
+ "LABEL_10": 10,
170
+ "LABEL_100": 100,
171
+ "LABEL_101": 101,
172
+ "LABEL_102": 102,
173
+ "LABEL_103": 103,
174
+ "LABEL_104": 104,
175
+ "LABEL_105": 105,
176
+ "LABEL_106": 106,
177
+ "LABEL_107": 107,
178
+ "LABEL_108": 108,
179
+ "LABEL_109": 109,
180
+ "LABEL_11": 11,
181
+ "LABEL_110": 110,
182
+ "LABEL_111": 111,
183
+ "LABEL_112": 112,
184
+ "LABEL_113": 113,
185
+ "LABEL_114": 114,
186
+ "LABEL_115": 115,
187
+ "LABEL_116": 116,
188
+ "LABEL_117": 117,
189
+ "LABEL_118": 118,
190
+ "LABEL_119": 119,
191
+ "LABEL_12": 12,
192
+ "LABEL_120": 120,
193
+ "LABEL_121": 121,
194
+ "LABEL_122": 122,
195
+ "LABEL_123": 123,
196
+ "LABEL_124": 124,
197
+ "LABEL_125": 125,
198
+ "LABEL_126": 126,
199
+ "LABEL_127": 127,
200
+ "LABEL_128": 128,
201
+ "LABEL_129": 129,
202
+ "LABEL_13": 13,
203
+ "LABEL_130": 130,
204
+ "LABEL_131": 131,
205
+ "LABEL_132": 132,
206
+ "LABEL_133": 133,
207
+ "LABEL_134": 134,
208
+ "LABEL_135": 135,
209
+ "LABEL_136": 136,
210
+ "LABEL_137": 137,
211
+ "LABEL_138": 138,
212
+ "LABEL_139": 139,
213
+ "LABEL_14": 14,
214
+ "LABEL_140": 140,
215
+ "LABEL_141": 141,
216
+ "LABEL_142": 142,
217
+ "LABEL_143": 143,
218
+ "LABEL_144": 144,
219
+ "LABEL_145": 145,
220
+ "LABEL_146": 146,
221
+ "LABEL_147": 147,
222
+ "LABEL_148": 148,
223
+ "LABEL_149": 149,
224
+ "LABEL_15": 15,
225
+ "LABEL_150": 150,
226
+ "LABEL_16": 16,
227
+ "LABEL_17": 17,
228
+ "LABEL_18": 18,
229
+ "LABEL_19": 19,
230
+ "LABEL_2": 2,
231
+ "LABEL_20": 20,
232
+ "LABEL_21": 21,
233
+ "LABEL_22": 22,
234
+ "LABEL_23": 23,
235
+ "LABEL_24": 24,
236
+ "LABEL_25": 25,
237
+ "LABEL_26": 26,
238
+ "LABEL_27": 27,
239
+ "LABEL_28": 28,
240
+ "LABEL_29": 29,
241
+ "LABEL_3": 3,
242
+ "LABEL_30": 30,
243
+ "LABEL_31": 31,
244
+ "LABEL_32": 32,
245
+ "LABEL_33": 33,
246
+ "LABEL_34": 34,
247
+ "LABEL_35": 35,
248
+ "LABEL_36": 36,
249
+ "LABEL_37": 37,
250
+ "LABEL_38": 38,
251
+ "LABEL_39": 39,
252
+ "LABEL_4": 4,
253
+ "LABEL_40": 40,
254
+ "LABEL_41": 41,
255
+ "LABEL_42": 42,
256
+ "LABEL_43": 43,
257
+ "LABEL_44": 44,
258
+ "LABEL_45": 45,
259
+ "LABEL_46": 46,
260
+ "LABEL_47": 47,
261
+ "LABEL_48": 48,
262
+ "LABEL_49": 49,
263
+ "LABEL_5": 5,
264
+ "LABEL_50": 50,
265
+ "LABEL_51": 51,
266
+ "LABEL_52": 52,
267
+ "LABEL_53": 53,
268
+ "LABEL_54": 54,
269
+ "LABEL_55": 55,
270
+ "LABEL_56": 56,
271
+ "LABEL_57": 57,
272
+ "LABEL_58": 58,
273
+ "LABEL_59": 59,
274
+ "LABEL_6": 6,
275
+ "LABEL_60": 60,
276
+ "LABEL_61": 61,
277
+ "LABEL_62": 62,
278
+ "LABEL_63": 63,
279
+ "LABEL_64": 64,
280
+ "LABEL_65": 65,
281
+ "LABEL_66": 66,
282
+ "LABEL_67": 67,
283
+ "LABEL_68": 68,
284
+ "LABEL_69": 69,
285
+ "LABEL_7": 7,
286
+ "LABEL_70": 70,
287
+ "LABEL_71": 71,
288
+ "LABEL_72": 72,
289
+ "LABEL_73": 73,
290
+ "LABEL_74": 74,
291
+ "LABEL_75": 75,
292
+ "LABEL_76": 76,
293
+ "LABEL_77": 77,
294
+ "LABEL_78": 78,
295
+ "LABEL_79": 79,
296
+ "LABEL_8": 8,
297
+ "LABEL_80": 80,
298
+ "LABEL_81": 81,
299
+ "LABEL_82": 82,
300
+ "LABEL_83": 83,
301
+ "LABEL_84": 84,
302
+ "LABEL_85": 85,
303
+ "LABEL_86": 86,
304
+ "LABEL_87": 87,
305
+ "LABEL_88": 88,
306
+ "LABEL_89": 89,
307
+ "LABEL_9": 9,
308
+ "LABEL_90": 90,
309
+ "LABEL_91": 91,
310
+ "LABEL_92": 92,
311
+ "LABEL_93": 93,
312
+ "LABEL_94": 94,
313
+ "LABEL_95": 95,
314
+ "LABEL_96": 96,
315
+ "LABEL_97": 97,
316
+ "LABEL_98": 98,
317
+ "LABEL_99": 99
318
+ },
319
+ "layer_norm_eps": 1e-12,
320
+ "max_position_embeddings": 512,
321
+ "model_type": "bert",
322
+ "num_attention_heads": 12,
323
+ "num_hidden_layers": 12,
324
+ "pad_token_id": 0,
325
+ "position_embedding_type": "absolute",
326
+ "problem_type": "single_label_classification",
327
+ "torch_dtype": "float32",
328
+ "transformers_version": "4.53.0",
329
+ "type_vocab_size": 2,
330
+ "use_cache": true,
331
+ "vocab_size": 30522
332
+ }
intent_classifier_model/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:43ec753e8b145ec7bf1dc275cc62677fd08e5bf706f5cc957e66c8f78d234453
3
+ size 438416972
intent_classifier_tokenizer/special_tokens_map.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "cls_token": "[CLS]",
3
+ "mask_token": "[MASK]",
4
+ "pad_token": "[PAD]",
5
+ "sep_token": "[SEP]",
6
+ "unk_token": "[UNK]"
7
+ }
intent_classifier_tokenizer/tokenizer_config.json ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ "100": {
12
+ "content": "[UNK]",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ },
19
+ "101": {
20
+ "content": "[CLS]",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false,
25
+ "special": true
26
+ },
27
+ "102": {
28
+ "content": "[SEP]",
29
+ "lstrip": false,
30
+ "normalized": false,
31
+ "rstrip": false,
32
+ "single_word": false,
33
+ "special": true
34
+ },
35
+ "103": {
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": 512,
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
+ }
intent_classifier_tokenizer/vocab.txt ADDED
The diff for this file is too large to render. See raw diff
 
model/api/api.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from pydantic import BaseModel
3
+ from transformers import BertForSequenceClassification, BertTokenizer
4
+ import torch
5
+ import os
6
+
7
+ app = FastAPI()
8
+
9
+ # Get the absolute path to the model directory
10
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
11
+ # go back one level to get the correct path
12
+ BASE_DIR = os.path.dirname(BASE_DIR)
13
+ MODEL_DIR = os.path.join(BASE_DIR, "intent_classifier_model")
14
+ TOKENIZER_DIR = os.path.join(BASE_DIR, "intent_classifier_tokenizer")
15
+
16
+ # Ensure model and tokenizer directories exist
17
+ if not os.path.isdir(MODEL_DIR):
18
+ raise FileNotFoundError(f"Model directory not found: {MODEL_DIR}")
19
+ if not os.path.isdir(TOKENIZER_DIR):
20
+ raise FileNotFoundError(f"Tokenizer directory not found: {TOKENIZER_DIR}")
21
+
22
+ # Load model and tokenizer from local directories only
23
+ model = BertForSequenceClassification.from_pretrained(MODEL_DIR, local_files_only=True)
24
+ tokenizer = BertTokenizer.from_pretrained(TOKENIZER_DIR, local_files_only=True)
25
+
26
+ # Load intent label mapping
27
+ from datasets import load_dataset
28
+ dataset = load_dataset("clinc_oos", "small")
29
+ int2str = dataset["train"].features["intent"].int2str
30
+
31
+ class Query(BaseModel):
32
+ text: str
33
+
34
+ @app.post("/predict")
35
+ def predict_intent(query: Query):
36
+ inputs = tokenizer(query.text, return_tensors="pt", truncation=True, padding=True, max_length=128)
37
+ with torch.no_grad():
38
+ outputs = model(**inputs)
39
+ prediction = outputs.logits.argmax(dim=-1).item()
40
+ intent = int2str(prediction)
41
+ if intent == "oos":
42
+ return {"intent": "out of scope (OOS)"}
43
+ else:
44
+ intent = intent.replace("_", " ").title()
45
+ return {"intent": intent}
model/api/start_server.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import uvicorn
2
+
3
+ if __name__ == "__main__":
4
+ uvicorn.run("api:app", host="0.0.0.0", port=8000, reload=True)
model/api/test.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+
3
+ url = "http://localhost:8000/predict"
4
+ data = {"text": "I want to set an alarm for 7 AM tomorrow."}
5
+
6
+ response = requests.post(url, json=data)
7
+ print("Status code:", response.status_code)
8
+ print("Response:", response.json())
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ transformers
4
+ torch
5
+ datasets
6
+ requests
7
+ chardet
src/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # ...empty file...
src/app.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # NOTE: Make sure the FastAPI server is running at http://localhost:8000 before starting this Flask app.
2
+ from flask import Flask, render_template, request
3
+ import requests
4
+
5
+ app = Flask(__name__)
6
+
7
+ FASTAPI_URL = "http://localhost:8000/predict"
8
+
9
+ @app.route("/", methods=["GET", "POST"])
10
+ def index():
11
+ prediction = None
12
+ user_text = ""
13
+ if request.method == "POST":
14
+ user_text = request.form.get("user_text", "")
15
+ if user_text:
16
+ try:
17
+ response = requests.post(FASTAPI_URL, json={"text": user_text})
18
+ if response.status_code == 200:
19
+ prediction = response.json().get("intent", "Unknown")
20
+ else:
21
+ prediction = "Error: Unable to get prediction."
22
+ except requests.exceptions.ConnectionError:
23
+ prediction = (
24
+ "Error: Could not connect to the FastAPI server at http://localhost:8000.<br>"
25
+ "Please make sure the FastAPI server is running.<br>"
26
+ "To start it, run:<br>"
27
+ "<code>python model/api/start_server.py</code> from the project root."
28
+ )
29
+ except Exception as e:
30
+ prediction = f"Error: {str(e)}"
31
+ return render_template("index.html", prediction=prediction, user_text=user_text)
32
+
33
+ if __name__ == "__main__":
34
+ app.run(debug=True)
src/fastapi_server.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request
2
+ from pydantic import BaseModel
3
+
4
+ app = FastAPI()
5
+
6
+ class PredictRequest(BaseModel):
7
+ text: str
8
+
9
+ @app.post("/predict")
10
+ def predict(req: PredictRequest):
11
+ # Dummy implementation for testing
12
+ # Replace with your model inference logic
13
+ if "alarm" in req.text.lower():
14
+ intent = "set_alarm"
15
+ else:
16
+ intent = "unknown"
17
+ return {"intent": intent}
src/templates/index.html ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Intent Classifier Chatbot</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <style>
8
+ body {
9
+ font-family: 'Segoe UI', Arial, sans-serif;
10
+ margin: 0;
11
+ background: #f7f9fa;
12
+ color: #222;
13
+ }
14
+ .container {
15
+ max-width: 500px;
16
+ margin: 60px auto 30px auto;
17
+ background: #fff;
18
+ border-radius: 12px;
19
+ box-shadow: 0 4px 24px rgba(0,0,0,0.08);
20
+ padding: 32px 28px 24px 28px;
21
+ }
22
+ h1 {
23
+ text-align: center;
24
+ color: #2d6cdf;
25
+ margin-bottom: 18px;
26
+ }
27
+ label {
28
+ font-weight: 500;
29
+ margin-bottom: 8px;
30
+ display: block;
31
+ }
32
+ input[type="text"] {
33
+ width: 100%;
34
+ padding: 12px;
35
+ border: 1px solid #d2d6dc;
36
+ border-radius: 6px;
37
+ font-size: 1em;
38
+ margin-bottom: 18px;
39
+ box-sizing: border-box;
40
+ transition: border 0.2s;
41
+ }
42
+ input[type="text"]:focus {
43
+ border: 1.5px solid #2d6cdf;
44
+ outline: none;
45
+ }
46
+ button {
47
+ width: 100%;
48
+ padding: 12px;
49
+ background: linear-gradient(90deg, #2d6cdf 60%, #4e9cff 100%);
50
+ color: #fff;
51
+ border: none;
52
+ border-radius: 6px;
53
+ font-size: 1.1em;
54
+ font-weight: 600;
55
+ cursor: pointer;
56
+ transition: background 0.2s;
57
+ }
58
+ button:hover {
59
+ background: linear-gradient(90deg, #1b4e9b 60%, #3578c7 100%);
60
+ }
61
+ .result {
62
+ margin-top: 24px;
63
+ font-size: 1.15em;
64
+ background: #eaf3ff;
65
+ border-left: 4px solid #2d6cdf;
66
+ padding: 14px 18px;
67
+ border-radius: 6px;
68
+ color: #1a3a5d;
69
+ word-break: break-word;
70
+ }
71
+ .info {
72
+ margin-top: 18px;
73
+ font-size: 0.98em;
74
+ color: #555;
75
+ background: #f3f6fa;
76
+ border-radius: 6px;
77
+ padding: 10px 14px;
78
+ }
79
+ footer {
80
+ margin-top: 40px;
81
+ text-align: center;
82
+ color: #888;
83
+ font-size: 0.97em;
84
+ padding-bottom: 18px;
85
+ }
86
+ @media (max-width: 600px) {
87
+ .container { padding: 18px 6px 18px 6px; }
88
+ }
89
+ </style>
90
+ </head>
91
+ <body>
92
+ <div class="container">
93
+ <h1>Intent Classifier Chatbot</h1>
94
+ <div class="info">
95
+ Enter a message below and click <b>Predict Intent</b> to see what the AI thinks your intent is.<br>
96
+ <span style="color:#2d6cdf;">Try: <i>"Set an alarm for 7am"</i> or <i>"Transfer money to John"</i></span>
97
+ </div>
98
+ <form method="post" autocomplete="off">
99
+ <label for="user_text">Your Message:</label>
100
+ <input type="text" id="user_text" name="user_text" value="{{ user_text }}" placeholder="Type your message here..." required autofocus>
101
+ <button type="submit">Predict Intent</button>
102
+ </form>
103
+ {% if prediction %}
104
+ <div class="result">
105
+ <strong>Predicted Intent:</strong> {{ prediction }}
106
+ </div>
107
+ {% endif %}
108
+ </div>
109
+ <footer>
110
+ Made by <b>Saher Muhamed</b><br>
111
+ <a href="https://github.com/sahermuhamed1" target="_blank" style="color:#2d6cdf;text-decoration:none;">GitHub</a> &middot;
112
+ <a href="mailto:sahermuhamed176@gmail.com" style="color:#2d6cdf;text-decoration:none;">Contact</a>
113
+ </footer>
114
+ </body>
115
+ </html>
training/workspace.ipynb ADDED
The diff for this file is too large to render. See raw diff