AhmedBenElsayedBenAhmed commited on
Commit
3fff865
·
1 Parent(s): 0429063
Files changed (1) hide show
  1. app.py +300 -93
app.py CHANGED
@@ -2,26 +2,98 @@
2
  # 1️⃣ استيراد المكتبات
3
  # ===============================
4
  import os
5
- import requests
6
- import threading
7
- import json
 
 
 
8
  from flask import Flask, request, jsonify
9
  from flask_cors import CORS
 
10
  from datasets import Dataset
11
  import difflib
12
 
13
  # ===============================
14
  # 2️⃣ إعداد المتغيرات
15
  # ===============================
16
- HF_API_URL = "https://api-inference.huggingface.co/models/AhmedBenElsayed/Graduation_Project"
17
- HF_API_TOKEN = "YOUR_HUGGINGFACE_API_TOKEN" # ضع التوكن هنا
18
- HEADERS = {"Authorization": f"Bearer {HF_API_TOKEN}"}
19
-
20
  FLASK_PORT = 7860
 
 
21
  DATA_DIR = "/tmp/data"
22
- DATASET_FILE = os.path.join(DATA_DIR, "dataset.json")
23
  os.makedirs(DATA_DIR, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  current_dataset = None
26
 
27
  # Fake DB
@@ -32,7 +104,137 @@ users_db = {
32
  }
33
 
34
  # ===============================
35
- # 3️⃣ تحميل الداتا
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  # ===============================
37
  def load_dataset():
38
  global current_dataset
@@ -62,110 +264,115 @@ def load_dataset():
62
  load_dataset()
63
 
64
  # ===============================
65
- # 4️⃣ Flask API
66
  # ===============================
67
- app = Flask(__name__)
68
- CORS(app)
69
-
70
- # ----- توليد النصوص -----
71
- @app.route("/generate", methods=["POST"])
72
- def generate():
73
  try:
74
- data = request.json or {}
75
- prompt = str(data.get("prompt", "")).strip()
76
- user_id = str(data.get("userId", "")).strip()
77
- focus_level = float(data.get("focus_level", 0.5))
78
- n_examples = int(data.get("n_examples", 2))
79
 
80
- user_info = users_db.get(user_id)
81
- user_name = user_info["name"] if user_info else "مستخدم"
82
-
83
- if not prompt:
84
- return jsonify({"messages": [{"role": "assistant", "content": "⚠️ الرجاء ارسال سؤال أو استفسار صحيح."}]}), 400
 
 
 
85
 
86
- # =====================
87
- # حالة: لا يوجد داتا تدريبية
88
- # =====================
89
- if current_dataset is None:
90
- final_prompt = (
91
- f"أنت مساعد قانوني ذكي اسمه زين 👨‍⚖️.\n"
92
- f"مهمة زين: الإجابة على الأسئلة القانونية بأسلوب واضح ومبسط.\n\n"
93
- f"السؤال:\n{prompt}\n\nالإجابة:"
94
- )
95
  else:
96
- # =====================
97
- # حالة: يوجد داتا تدريبية
98
- # =====================
99
- candidates = []
100
- for item in current_dataset:
101
- text = item.get("text", "")
102
- ratio = difflib.SequenceMatcher(None, prompt.lower(), text.lower()).ratio()
103
- candidates.append((ratio, text))
104
- candidates.sort(reverse=True, key=lambda x: x[0])
105
- top_texts = [t for r, t in candidates[:n_examples] if r > 0.05]
106
-
107
- context_parts = []
108
- for t in top_texts:
109
- lines = t.split("\n")
110
- u_lines, a_lines = [], []
111
- for l in lines:
112
- if ":" in l:
113
- role, content = l.split(":", 1)
114
- role = role.strip().lower()
115
- if role.startswith("user"):
116
- u_lines.append(content.strip())
117
- elif role.startswith("assistant") or role.startswith("ai") or role.startswith("system"):
118
- a_lines.append(content.strip())
119
- if u_lines or a_lines:
120
- context_parts.append("User: " + " ".join(u_lines) + "\nAssistant: " + " ".join(a_lines))
121
-
122
- context_text = "\n\n---\n\n".join(context_parts)
123
- instruction = (
124
- "التعليمات: استعن بالأمثلة التدريبية كمرجع، "
125
- "لكن أجب بأسلوبك الخاص وبكلمات مختلفة. "
126
- "حافظ على صحة المعلومة القانونية ولا تنسخ النصوص كما هي."
127
- )
128
- if context_text and focus_level > 0.0:
129
- emphasis = "ركز بشكل كبير على الأمثلة" if focus_level > 0.7 else "خذ الأمثلة كمرجع فقط"
130
- final_prompt = (
131
- f"{instruction}\n\n{emphasis}\n\n"
132
- f"أمثلة تدريبية (مستوى التركيز={focus_level}):\n{context_text}\n\n"
133
- f"السؤال:\n{prompt}\n\nالإجابة:"
134
- )
135
  else:
136
- final_prompt = f"{instruction}\n\nالسؤال:\n{prompt}\n\nالإجابة:"
 
 
 
137
 
138
- # ==== استدعاء API HuggingFace
139
- payload = {"inputs": final_prompt}
140
- response = requests.post(HF_API_URL, headers=HEADERS, json=payload, timeout=60)
141
 
142
- if response.status_code != 200:
143
- return jsonify({"messages": [{"role": "assistant", "content": f"❌ خطأ من HuggingFace API: {response.text}"}]})
 
 
 
 
 
 
 
 
144
 
145
- result = response.json()
146
- if isinstance(result, list) and "generated_text" in result[0]:
147
- reply = result[0]["generated_text"]
148
- else:
149
- reply = str(result)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
  return jsonify({
152
- "messages": [
153
- {"role": "assistant", "content": f"أهلاً {user_name} 👋"},
154
- {"role": "assistant", "content": reply}
155
- ],
156
- "meta": {"mode": "api", "focus_level": focus_level}
157
  })
158
 
159
  except Exception as e:
160
- return jsonify({"messages": [{"role": "assistant", "content": f"❌ خطأ: {str(e)}"}]})
161
 
162
- # ----- الصفحة الرئيسية -----
 
 
163
  @app.route("/", methods=["GET"])
164
  def home():
165
  return jsonify({
166
  "status": "ok",
167
  "message": "Graduation Project API is running 🚀",
168
- "endpoints": ["/generate"]
169
  })
170
 
171
  if __name__ == "__main__":
 
2
  # 1️⃣ استيراد المكتبات
3
  # ===============================
4
  import os
5
+ os.environ["HF_HOME"] = "/tmp"
6
+ os.environ["TRANSFORMERS_CACHE"] = "/tmp"
7
+ os.environ["PEFT_HOME"] = "/tmp"
8
+
9
+ from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments
10
+ from peft import LoraConfig, get_peft_model, TaskType, PeftModel
11
  from flask import Flask, request, jsonify
12
  from flask_cors import CORS
13
+ import threading, torch, json
14
  from datasets import Dataset
15
  import difflib
16
 
17
  # ===============================
18
  # 2️⃣ إعداد المتغيرات
19
  # ===============================
20
+ MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
 
 
 
21
  FLASK_PORT = 7860
22
+
23
+ SAVE_DIR = "/tmp/lora_model"
24
  DATA_DIR = "/tmp/data"
 
25
  os.makedirs(DATA_DIR, exist_ok=True)
26
+ os.makedirs(SAVE_DIR, exist_ok=True)
27
+
28
+ DATASET_FILE = os.path.join(DATA_DIR, "dataset.json")
29
+
30
+ # ===============================
31
+ # 3️⃣ تحديد الجهاز
32
+ # ===============================
33
+ device = "cuda" if torch.cuda.is_available() else "cpu"
34
+ print("🔹 Using device:", device)
35
+
36
+ # ===============================
37
+ # 4️⃣ تحميل الموديل الأساسي
38
+ # ===============================
39
+ print("🔄 Loading base model...")
40
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
41
+
42
+ try:
43
+ model = AutoModelForCausalLM.from_pretrained(
44
+ MODEL_NAME,
45
+ device_map="auto" if device=="cuda" else {"": "cpu"},
46
+ torch_dtype=torch.float16 if device=="cuda" else torch.float32,
47
+ load_in_8bit=(device=="cuda"),
48
+ trust_remote_code=True
49
+ )
50
+ model.to(device)
51
+ print("✅ Base model loaded!")
52
+ except Exception as e:
53
+ print("⚠️ Failed to load base model:", e)
54
+ raise e
55
+
56
+ # ===============================
57
+ # 5️⃣ إعداد LoRA
58
+ # ===============================
59
+ lora_config = LoraConfig(
60
+ task_type=TaskType.CAUSAL_LM,
61
+ r=2,
62
+ lora_alpha=8,
63
+ lora_dropout=0.05,
64
+ target_modules=["q_proj", "v_proj"]
65
+ )
66
+ model = get_peft_model(model, lora_config)
67
+ model.to(device)
68
+ print("✅ LoRA model ready on device:", device)
69
 
70
+ # ===============================
71
+ # 6️⃣ Auto load LoRA weights
72
+ # ===============================
73
+ if os.path.exists(SAVE_DIR):
74
+ try:
75
+ base_model = AutoModelForCausalLM.from_pretrained(
76
+ MODEL_NAME,
77
+ device_map="auto" if device=="cuda" else {"": "cpu"},
78
+ torch_dtype=torch.float16 if device=="cuda" else torch.float32,
79
+ load_in_8bit=(device=="cuda"),
80
+ trust_remote_code=True
81
+ )
82
+ model = PeftModel.from_pretrained(base_model, SAVE_DIR)
83
+ model.to(device)
84
+ print("✅ LoRA weights auto-loaded from disk!")
85
+ except Exception as e:
86
+ print("⚠️ Failed to auto-load LoRA weights:", e)
87
+ else:
88
+ print("ℹ️ No saved LoRA weights found, running with base model.")
89
+
90
+ # ===============================
91
+ # 7️⃣ إعداد Flask API
92
+ # ===============================
93
+ app = Flask(__name__)
94
+ CORS(app)
95
+
96
+ trainer = None
97
  current_dataset = None
98
 
99
  # Fake DB
 
104
  }
105
 
106
  # ===============================
107
+ # 7a️⃣ توليد نصوص
108
+ # ===============================
109
+ @app.route("/generate", methods=["POST"])
110
+ def generate():
111
+ try:
112
+ data = request.json or {}
113
+ prompt = str(data.get("prompt", "")).strip()
114
+ user_id = str(data.get("userId", "")).strip()
115
+ focus_level = float(data.get("focus_level", 0.5))
116
+ n_examples = int(data.get("n_examples", 2))
117
+
118
+ user_info = users_db.get(user_id)
119
+ user_name = user_info["name"] if user_info else "مستخدم"
120
+
121
+ if not prompt:
122
+ return jsonify({"messages": [{"role": "assistant", "content": "⚠️ الرجاء ارسال سؤال أو استفسار صحيح."}]}), 400
123
+
124
+ # =====================
125
+ # حالة: لا يوجد داتا تدريبية
126
+ # =====================
127
+ if current_dataset is None:
128
+ base_prompt = (
129
+ f"أنت مساعد قانوني ذكي اسمه زين 👨‍⚖️.\n"
130
+ f"مهمة زين: الإجابة على الأسئلة القانونية بأسلوب واضح ومبسط "
131
+ f"مع الحفاظ على الدقة القانونية.\n\n"
132
+ f"السؤال:\n{prompt}\n\nالإجابة:"
133
+ )
134
+
135
+ inputs = tokenizer(base_prompt, return_tensors="pt", truncation=True, max_length=1024)
136
+ inputs = {k: v.to(model.device) for k, v in inputs.items()}
137
+ input_len = inputs["input_ids"].shape[-1]
138
+
139
+ outputs = model.generate(
140
+ **inputs,
141
+ max_new_tokens=200,
142
+ temperature=0.7,
143
+ top_p=0.9,
144
+ do_sample=True,
145
+ pad_token_id=tokenizer.eos_token_id,
146
+ repetition_penalty=1.1
147
+ )
148
+
149
+ reply = tokenizer.decode(outputs[0][input_len:], skip_special_tokens=True).strip()
150
+
151
+ return jsonify({
152
+ "messages": [
153
+ {"role": "assistant", "content": f"أهلاً {user_name} 👋"},
154
+ {"role": "assistant", "content": reply}
155
+ ],
156
+ "meta": {"mode": "base"}
157
+ })
158
+
159
+ # =====================
160
+ # حالة: يوجد داتا تدريبية
161
+ # =====================
162
+ candidates = []
163
+ for item in current_dataset:
164
+ text = item.get("text", "")
165
+ ratio = difflib.SequenceMatcher(None, prompt.lower(), text.lower()).ratio()
166
+ candidates.append((ratio, text))
167
+
168
+ candidates.sort(reverse=True, key=lambda x: x[0])
169
+ top_texts = [t for r, t in candidates[:n_examples] if r > 0.05]
170
+
171
+ context_parts = []
172
+ for t in top_texts:
173
+ lines = t.split("\n")
174
+ u_lines, a_lines = [], []
175
+ for l in lines:
176
+ if ":" in l:
177
+ role, content = l.split(":", 1)
178
+ role = role.strip().lower()
179
+ if role.startswith("user"):
180
+ u_lines.append(content.strip())
181
+ elif role.startswith("assistant") or role.startswith("ai") or role.startswith("system"):
182
+ a_lines.append(content.strip())
183
+ if u_lines or a_lines:
184
+ context_parts.append("User: " + " ".join(u_lines) + "\nAssistant: " + " ".join(a_lines))
185
+
186
+ context_text = "\n\n---\n\n".join(context_parts)
187
+
188
+ instruction = (
189
+ "التعليمات: استعن بالأمثلة التدريبية كمرجع أساسي، "
190
+ "لكن أجب بأسلوبك الخاص وبكلمات مختلفة. "
191
+ "حافظ على صحة المعلومة القانونية ولا تنسخ النصوص كما هي."
192
+ )
193
+
194
+ if context_text and focus_level > 0.0:
195
+ emphasis = "ركز بشكل كبير على الأمثلة" if focus_level > 0.7 else "خذ الأمثلة كمرجع فقط"
196
+ final_prompt = (
197
+ f"{instruction}\n\n{emphasis}\n\n"
198
+ f"أمثلة تدريبية (مستوى التركيز={focus_level}):\n{context_text}\n\n"
199
+ f"السؤال:\n{prompt}\n\nالإجابة:"
200
+ )
201
+ else:
202
+ final_prompt = f"{instruction}\n\nالسؤال:\n{prompt}\n\nالإجابة:"
203
+
204
+ # ==== توليد
205
+ inputs = tokenizer(final_prompt, return_tensors="pt", truncation=True, max_length=1024)
206
+ inputs = {k: v.to(model.device) for k, v in inputs.items()}
207
+ input_len = inputs["input_ids"].shape[-1]
208
+
209
+ outputs = model.generate(
210
+ **inputs,
211
+ max_new_tokens=200,
212
+ temperature=0.6,
213
+ top_p=0.9,
214
+ do_sample=True,
215
+ pad_token_id=tokenizer.eos_token_id,
216
+ repetition_penalty=1.1
217
+ )
218
+
219
+ reply = tokenizer.decode(outputs[0][input_len:], skip_special_tokens=True).strip()
220
+
221
+ return jsonify({
222
+ "messages": [
223
+ {"role": "assistant", "content": f"أهلاً {user_name} 👋"},
224
+ {"role": "assistant", "content": reply}
225
+ ],
226
+ "meta": {
227
+ "mode": "trained",
228
+ "used_examples": len(context_parts),
229
+ "focus_level": focus_level
230
+ }
231
+ })
232
+
233
+ except Exception as e:
234
+ return jsonify({"messages": [{"role": "assistant", "content": f"❌ خطأ: {str(e)}"}]})
235
+
236
+ # ===============================
237
+ # 7b️⃣ تحميل الداتا
238
  # ===============================
239
  def load_dataset():
240
  global current_dataset
 
264
  load_dataset()
265
 
266
  # ===============================
267
+ # 7c️⃣ تدريب LoRA (Async)
268
  # ===============================
269
+ @app.route("/train", methods=["POST"])
270
+ def train():
271
+ global trainer, current_dataset, model
 
 
 
272
  try:
273
+ data = []
274
+ epochs = 1
275
+ batch_size = 1
 
 
276
 
277
+ if request.is_json:
278
+ body = request.get_json()
279
+ if isinstance(body, list):
280
+ data = body
281
+ elif isinstance(body, dict) and "data" in body:
282
+ data = body["data"]
283
+ else:
284
+ return jsonify({"error": "JSON format not recognized"}), 400
285
 
286
+ epochs = int(body.get("epochs", 1)) if isinstance(body, dict) else 1
287
+ batch_size = int(body.get("batch_size", 1)) if isinstance(body, dict) else 1
 
 
 
 
 
 
 
288
  else:
289
+ file = request.files.get("file")
290
+ if file:
291
+ data = json.load(file)
292
+ epochs = int(request.form.get("epochs", 1))
293
+ batch_size = int(request.form.get("batch_size", 1))
294
+ else:
295
+ return jsonify({"error": "No file uploaded or JSON provided"}), 400
296
+
297
+ old_data = []
298
+ if os.path.exists(DATASET_FILE):
299
+ with open(DATASET_FILE, "r", encoding="utf-8") as f:
300
+ try:
301
+ old_data = json.load(f)
302
+ except:
303
+ old_data = []
304
+
305
+ combined = old_data + data
306
+ unique_data, seen_texts = [], set()
307
+ for item in combined:
308
+ if "messages" in item:
309
+ text_key = "\n".join([f"{m['role'].strip().lower()}:{m['content'].strip()}" for m in item["messages"]])
310
+ elif "text" in item:
311
+ text_key = item["text"].strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  else:
313
+ continue
314
+ if text_key not in seen_texts:
315
+ seen_texts.add(text_key)
316
+ unique_data.append(item)
317
 
318
+ with open(DATASET_FILE, "w", encoding="utf-8") as f:
319
+ json.dump(unique_data, f, ensure_ascii=False, indent=2)
 
320
 
321
+ formatted = []
322
+ for item in unique_data:
323
+ if "messages" in item:
324
+ conversation = ""
325
+ for msg in item["messages"]:
326
+ conversation += f"{msg['role'].capitalize()}: {msg['content']}\n"
327
+ formatted.append({"text": conversation.strip()})
328
+ elif "text" in item:
329
+ formatted.append({"text": item["text"]})
330
+ current_dataset = Dataset.from_list(formatted)
331
 
332
+ def tokenize_function(examples):
333
+ return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=256)
334
+
335
+ tokenized_dataset = current_dataset.map(tokenize_function, batched=True, remove_columns=["text"])
336
+
337
+ training_args = TrainingArguments(
338
+ output_dir=SAVE_DIR,
339
+ per_device_train_batch_size=batch_size,
340
+ num_train_epochs=epochs,
341
+ logging_steps=2,
342
+ save_strategy="epoch",
343
+ fp16=torch.cuda.is_available(),
344
+ report_to=None
345
+ )
346
+
347
+ trainer = Trainer(model=model, args=training_args, train_dataset=tokenized_dataset)
348
+
349
+ def run_training():
350
+ print("🔄 Training started...")
351
+ trainer.train()
352
+ trainer.save_model(SAVE_DIR)
353
+ print("✅ Training completed & model saved!")
354
+
355
+ threading.Thread(target=run_training, daemon=True).start()
356
 
357
  return jsonify({
358
+ "status": "training started",
359
+ "epochs": epochs,
360
+ "batch_size": batch_size,
361
+ "records": len(unique_data)
 
362
  })
363
 
364
  except Exception as e:
365
+ return jsonify({"error": str(e)})
366
 
367
+ # ===============================
368
+ # 8️⃣ تشغيل Flask
369
+ # ===============================
370
  @app.route("/", methods=["GET"])
371
  def home():
372
  return jsonify({
373
  "status": "ok",
374
  "message": "Graduation Project API is running 🚀",
375
+ "endpoints": ["/generate", "/train"]
376
  })
377
 
378
  if __name__ == "__main__":