isahsn commited on
Commit
30e54a3
·
verified ·
1 Parent(s): 0834fe1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +15 -80
app.py CHANGED
@@ -1,8 +1,7 @@
1
  import os
2
- import re
3
  import gradio as gr
4
  import torch
5
- from transformers import AutoModelForCausalLM, AutoTokenizer, MarianMTModel, MarianTokenizer
6
  from openai import OpenAI
7
 
8
  # ==========================================
@@ -35,18 +34,6 @@ def get_qwen_model(model_id):
35
  mdl = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.bfloat16, low_cpu_mem_usage=True)
36
  _qwen_models[model_id] = (tok, mdl)
37
  return _qwen_models[model_id]
38
- # ==========================================
39
- # وحدة كشف اللغة: تحدد متى نشغّل الجسر العربي تلقائياً
40
- # ==========================================
41
- _ARABIC_CHAR = re.compile(r"[؀-ۿݐ-ݿࢠ-ࣿ]")
42
-
43
- def is_arabic(text):
44
- """يرجّع True إذا كانت أغلب أحرف النص عربية — هكذا يعرف البوت أي مسار يستخدم."""
45
- letters = [c for c in text if c.isalpha()]
46
- if not letters:
47
- return False
48
- arabic = [c for c in letters if _ARABIC_CHAR.match(c)]
49
- return (len(arabic) / len(letters)) > 0.4
50
 
51
  # ==========================================
52
  # توليد Qwen عبر Transformers مع دعم وضع التفكير القابل للتبديل
@@ -70,49 +57,14 @@ def qwen_generate(messages, enable_thinking, tok, mdl, max_new_tokens=1024):
70
  return generated_ids[0][len(model_inputs.input_ids[0]):].tolist()
71
 
72
  # ==========================================
73
- # جسر الترجمة عبر نماذج opus-mt المحلية (Helsinki-NLP)
74
- # نماذج مخصّصة للترجمة، تعمل بدون إنترنت، وأدق بكثير من ترجمة النموذج لنفسه.
75
- # تُحمّل كسولاً عند أول استخدام حتى لا تُبطئ بدء التشغيل.
76
- # ==========================================
77
- AR_EN_MODEL = "Helsinki-NLP/opus-mt-ar-en"
78
- EN_AR_MODEL = "Helsinki-NLP/opus-mt-en-ar"
79
- _translators = {}
80
-
81
- def _get_translator(model_id):
82
- if model_id not in _translators:
83
- print(f"⏳ جاري تحميل نموذج الترجمة {model_id} ...")
84
- tok = MarianTokenizer.from_pretrained(model_id)
85
- mdl = MarianMTModel.from_pretrained(model_id)
86
- _translators[model_id] = (tok, mdl)
87
- return _translators[model_id]
88
-
89
- def opus_translate(text, model_id):
90
- """يترجم النص جملةً جملة حتى لا يتجاوز حد النموذج (512) وتبقى الدقة عالية."""
91
- tok, mdl = _get_translator(model_id)
92
- chunks = [c for c in re.split(r"(?<=[,.!؟?\n])\s+", text.strip()) if c.strip()]
93
- if not chunks:
94
- return ""
95
- batch = tok(chunks, return_tensors="pt", padding=True, truncation=True, max_length=1024)
96
- generated = mdl.generate(**batch, max_new_tokens=1024)
97
- return " ".join(tok.decode(g, skip_special_tokens=True).strip() for g in generated)
98
-
99
- def ar_to_en(text):
100
- return opus_translate(text, AR_EN_MODEL)
101
-
102
- def en_to_ar(text):
103
- return opus_translate(text, EN_AR_MODEL)
104
-
105
- # ==========================================
106
- # تحميل مسبق للنماذج عند بدء التشغيل بدل التحميل الكسول
107
- # نحمّل Qwen ونموذجي الترجمة مقدّماً حتى لا يتأخر أول ردّ للمستخدم.
108
- # (تبقى دوال get_qwen_model/_get_translator كما هي فتجد النماذج محمّلة في سجلّها)
109
  # ==========================================
110
  def preload_models():
111
- print("🚀 تحميل مسبق للنماذج (Qwen + جسر الترجمة)...")
112
  get_qwen_model(QWEN_MODEL)
113
- _get_translator(AR_EN_MODEL)
114
- _get_translator(EN_AR_MODEL)
115
- print("✅ اكتمل التحميل المسبق للنماذج.")
116
 
117
  # ==========================================
118
  # تنظيف رسائل المساعد القديمة من الشارات/كواليس التفكير قبل تمريرها للنموذج
@@ -122,7 +74,7 @@ def _strip_assistant_extras(content):
122
  return ""
123
  if "</details>" in content: # إزالة صندوق التفكير (Qwen)
124
  content = content.split("</details>")[-1].strip()
125
- if content.startswith(("🌉", "🟢")) and "\n\n" in content: # إزالة شارة المسار
126
  content = content.split("\n\n", 1)[-1].strip()
127
  return content
128
 
@@ -130,9 +82,9 @@ def _strip_assistant_extras(content):
130
  # الدالة الأساسية لمعالجة المحادثة بنظام الـ Messages الحديث
131
  # (غلاف يلتقط أي خطأ ويعرضه نصاً في الشات بدل شارة Gradio العامة "Error")
132
  # ==========================================
133
- def chat_engine(user_input, chat_history, model_choice, enable_thinking, api_key, system_prompt, enable_bridge):
134
  try:
135
- return _chat_engine_impl(user_input, chat_history, model_choice, enable_thinking, api_key, system_prompt, enable_bridge)
136
  except Exception as e:
137
  import traceback
138
  traceback.print_exc()
@@ -142,7 +94,7 @@ def chat_engine(user_input, chat_history, model_choice, enable_thinking, api_key
142
  chat_history.append({"role": "assistant", "content": f"❌ خطأ عام في المعالجة:\n\n`{type(e).__name__}: {str(e)}`"})
143
  return chat_history, ""
144
 
145
- def _chat_engine_impl(user_input, chat_history, model_choice, enable_thinking, api_key, system_prompt, enable_bridge):
146
  if not user_input.strip():
147
  return chat_history, ""
148
 
@@ -170,23 +122,10 @@ def _chat_engine_impl(user_input, chat_history, model_choice, enable_thinking, a
170
  # ------------------------------------------
171
  if model_choice == QWEN_MODEL:
172
  try:
173
- # 🌉 الجسر العربي: يقرر البوت تلقائياً متى يحوّل المسار عبر الإنجليزية
174
- use_bridge = enable_bridge and is_arabic(user_input)
175
-
176
- if use_bridge:
177
- # 1) ترجمة سؤال المستخدم من العربية إلى الإنجليزية ليفكّر النموذج بلغته الأقوى
178
- en_input = ar_to_en(user_input)
179
- gen_messages = [{"role": "system", "content": "You are a smart, concise and helpful assistant. Answer clearly and accurately in English."}]
180
- for msg in messages[1:-1]: # نمرر السياق السابق كما هو (نتجاوز الـ system والرسالة الحالية)
181
- gen_messages.append(msg)
182
- gen_messages.append({"role": "user", "content": en_input})
183
- badge = "🌉 *تم الردّ عبر جسر الترجمة (عربي ← إنجليزي ← عربي)*\n\n"
184
- else:
185
- gen_messages = messages
186
- badge = "🟢 *ردّ مباشر من Qwen*\n\n"
187
 
188
  tok, mdl = get_qwen_model(model_choice)
189
- output_ids = qwen_generate(gen_messages, enable_thinking, tok, mdl)
190
 
191
  # نفصل التفكير عن الجواب اعتماداً على رمز نهاية التفكير </think> (151668).
192
  # نقوم بهذا دائماً (حتى في الوضع العادي) حتى لا تتسرّب كواليس التفكير إلى الجواب.
@@ -212,10 +151,6 @@ def _chat_engine_impl(user_input, chat_history, model_choice, enable_thinking, a
212
  if not assistant_content:
213
  assistant_content = "⚠️ لم يكتمل الجواب ضمن حدّ التوليد. جرّب إيقاف وضع التفكير أو إعادة المحاولة."
214
 
215
- # 2) ترجمة الجواب الإنجليزي رجوعاً إلى العربية
216
- if use_bridge and assistant_content:
217
- assistant_content = en_to_ar(assistant_content)
218
-
219
  if thinking_content:
220
  final_output = f"{badge}<details><summary>🧠 كواليس تفكير النموذج</summary>\n\n{thinking_content}\n\n</details>\n\n{assistant_content}"
221
  else:
@@ -266,7 +201,6 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="slate"))
266
  )
267
  model_dropdown = gr.Dropdown(choices=[QWEN_MODEL, GPT_LABEL], value=QWEN_MODEL, label="اختر النموذج")
268
  thinking_checkbox = gr.Checkbox(label="🧠 تفعيل وضع التفكير (Qwen فقط)", value=True)
269
- bridge_checkbox = gr.Checkbox(label="🌉 الجسر العربي التلقائي (يحسّن أداء النموذج مع العربية)", value=True)
270
  api_key_input = gr.Textbox(label="OpenAI API Key", type="password")
271
  clear_button = gr.Button("🧹 مسح الذاكرة")
272
 
@@ -278,12 +212,12 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="slate"))
278
 
279
  submit_event = submit_button.click(
280
  chat_engine,
281
- inputs=[user_msg, chatbot, model_dropdown, thinking_checkbox, api_key_input, system_prompt_input, bridge_checkbox],
282
  outputs=[chatbot, user_msg]
283
  )
284
  user_msg.submit(
285
  chat_engine,
286
- inputs=[user_msg, chatbot, model_dropdown, thinking_checkbox, api_key_input, system_prompt_input, bridge_checkbox],
287
  outputs=[chatbot, user_msg]
288
  )
289
  clear_button.click(fn=lambda: [], outputs=chatbot, queue=False)
@@ -294,3 +228,4 @@ if __name__ == "__main__":
294
  # على Colab نحتاج share=True لإنشاء رابط عام؛ على Hugging Face Space يجب ألا نفعّله.
295
  on_hf_space = os.environ.get("SPACE_ID") is not None
296
  demo.launch(share=not on_hf_space)
 
 
1
  import os
 
2
  import gradio as gr
3
  import torch
4
+ from transformers import AutoModelForCausalLM, AutoTokenizer
5
  from openai import OpenAI
6
 
7
  # ==========================================
 
34
  mdl = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.bfloat16, low_cpu_mem_usage=True)
35
  _qwen_models[model_id] = (tok, mdl)
36
  return _qwen_models[model_id]
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  # ==========================================
39
  # توليد Qwen عبر Transformers مع دعم وضع التفكير القابل للتبديل
 
57
  return generated_ids[0][len(model_inputs.input_ids[0]):].tolist()
58
 
59
  # ==========================================
60
+ # تحميل مسبق للنموذج عند بدء التشغيل بدل التحميل الكسول
61
+ # نحمّل Qwen مقدّماً حتى لا يتأخر أول ردّ للمستخدم.
62
+ # (تبقى دالة get_qwen_model كما هي فتجد النموذج محمّلاً في سجلّه)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  # ==========================================
64
  def preload_models():
65
+ print("🚀 تحميل مسبق للنموذج (Qwen)...")
66
  get_qwen_model(QWEN_MODEL)
67
+ print("✅ اكتمل التحميل المسبق للنموذج.")
 
 
68
 
69
  # ==========================================
70
  # تنظيف رسائل المساعد القديمة من الشارات/كواليس التفكير قبل تمريرها للنموذج
 
74
  return ""
75
  if "</details>" in content: # إزالة صندوق التفكير (Qwen)
76
  content = content.split("</details>")[-1].strip()
77
+ if content.startswith("🟢") and "\n\n" in content: # إزالة شارة المسار
78
  content = content.split("\n\n", 1)[-1].strip()
79
  return content
80
 
 
82
  # الدالة الأساسية لمعالجة المحادثة بنظام الـ Messages الحديث
83
  # (غلاف يلتقط أي خطأ ويعرضه نصاً في الشات بدل شارة Gradio العامة "Error")
84
  # ==========================================
85
+ def chat_engine(user_input, chat_history, model_choice, enable_thinking, api_key, system_prompt):
86
  try:
87
+ return _chat_engine_impl(user_input, chat_history, model_choice, enable_thinking, api_key, system_prompt)
88
  except Exception as e:
89
  import traceback
90
  traceback.print_exc()
 
94
  chat_history.append({"role": "assistant", "content": f"❌ خطأ عام في المعالجة:\n\n`{type(e).__name__}: {str(e)}`"})
95
  return chat_history, ""
96
 
97
+ def _chat_engine_impl(user_input, chat_history, model_choice, enable_thinking, api_key, system_prompt):
98
  if not user_input.strip():
99
  return chat_history, ""
100
 
 
122
  # ------------------------------------------
123
  if model_choice == QWEN_MODEL:
124
  try:
125
+ badge = "🟢 *ردّ مباشر من Qwen*\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
  tok, mdl = get_qwen_model(model_choice)
128
+ output_ids = qwen_generate(messages, enable_thinking, tok, mdl)
129
 
130
  # نفصل التفكير عن الجواب اعتماداً على رمز نهاية التفكير </think> (151668).
131
  # نقوم بهذا دائماً (حتى في الوضع العادي) حتى لا تتسرّب كواليس التفكير إلى الجواب.
 
151
  if not assistant_content:
152
  assistant_content = "⚠️ لم يكتمل الجواب ضمن حدّ التوليد. جرّب إيقاف وضع التفكير أو إعادة المحاولة."
153
 
 
 
 
 
154
  if thinking_content:
155
  final_output = f"{badge}<details><summary>🧠 كواليس تفكير النموذج</summary>\n\n{thinking_content}\n\n</details>\n\n{assistant_content}"
156
  else:
 
201
  )
202
  model_dropdown = gr.Dropdown(choices=[QWEN_MODEL, GPT_LABEL], value=QWEN_MODEL, label="اختر النموذج")
203
  thinking_checkbox = gr.Checkbox(label="🧠 تفعيل وضع التفكير (Qwen فقط)", value=True)
 
204
  api_key_input = gr.Textbox(label="OpenAI API Key", type="password")
205
  clear_button = gr.Button("🧹 مسح الذاكرة")
206
 
 
212
 
213
  submit_event = submit_button.click(
214
  chat_engine,
215
+ inputs=[user_msg, chatbot, model_dropdown, thinking_checkbox, api_key_input, system_prompt_input],
216
  outputs=[chatbot, user_msg]
217
  )
218
  user_msg.submit(
219
  chat_engine,
220
+ inputs=[user_msg, chatbot, model_dropdown, thinking_checkbox, api_key_input, system_prompt_input],
221
  outputs=[chatbot, user_msg]
222
  )
223
  clear_button.click(fn=lambda: [], outputs=chatbot, queue=False)
 
228
  # على Colab نحتاج share=True لإنشاء رابط عام؛ على Hugging Face Space يجب ألا نفعّله.
229
  on_hf_space = os.environ.get("SPACE_ID") is not None
230
  demo.launch(share=not on_hf_space)
231
+