Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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
|
| 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 |
-
#
|
| 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("🚀 تحميل مسبق للنم
|
| 112 |
get_qwen_model(QWEN_MODEL)
|
| 113 |
-
|
| 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(
|
| 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
|
| 134 |
try:
|
| 135 |
-
return _chat_engine_impl(user_input, chat_history, model_choice, enable_thinking, api_key, system_prompt
|
| 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
|
| 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(
|
| 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
|
| 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
|
| 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 |
+
|