Spaces:
Sleeping
Sleeping
- data/Dockerfile +2 -2
- data/app.py +57 -42
- data/main.py +17 -0
- data/memory.py +6 -4
- data/monitor.py +83 -71
data/Dockerfile
CHANGED
@@ -2,7 +2,7 @@ FROM python:3.10-slim
|
|
2 |
|
3 |
# إضافة حزم أساسية فقط
|
4 |
RUN apt-get update && apt-get install -y \
|
5 |
-
build-essential pkg-config \
|
6 |
&& rm -rf /var/lib/apt/lists/*
|
7 |
|
8 |
# إضافة مستخدم غير root
|
@@ -25,4 +25,4 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|
25 |
|
26 |
USER user
|
27 |
|
28 |
-
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
2 |
|
3 |
# إضافة حزم أساسية فقط
|
4 |
RUN apt-get update && apt-get install -y \
|
5 |
+
build-essential pkg-config curl \
|
6 |
&& rm -rf /var/lib/apt/lists/*
|
7 |
|
8 |
# إضافة مستخدم غير root
|
|
|
25 |
|
26 |
USER user
|
27 |
|
28 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--log-config", "logging.conf"]
|
data/app.py
CHANGED
@@ -1,7 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from fastapi import FastAPI, HTTPException
|
2 |
from pydantic import BaseModel
|
3 |
from llama_cpp import Llama
|
4 |
-
import logging
|
5 |
import os
|
6 |
import threading
|
7 |
from fastapi.middleware.cors import CORSMiddleware
|
@@ -9,13 +20,6 @@ from monitor import get_current_metrics, start_monitoring_thread
|
|
9 |
from huggingface_hub import hf_hub_download
|
10 |
from memory import get_history, save_history
|
11 |
|
12 |
-
# إعداد السجل العام للتطبيق
|
13 |
-
logging.basicConfig(
|
14 |
-
level=logging.DEBUG,
|
15 |
-
format="🪵 [%(asctime)s] [%(levelname)s] %(message)s",
|
16 |
-
)
|
17 |
-
logger = logging.getLogger(__name__)
|
18 |
-
|
19 |
# إعدادات النموذج
|
20 |
MODEL_REPO = "Qwen/Qwen2-1.5B-Instruct-GGUF"
|
21 |
MODEL_FILE = "qwen2-1_5b-instruct-q6_k.gguf"
|
@@ -30,6 +34,31 @@ Follow these rules:
|
|
30 |
3. Keep responses concise but complete
|
31 |
4. For complex requests, ask clarifying questions<|im_end|>"""
|
32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
# تحميل النموذج إذا لم يكن موجوداً
|
34 |
def load_model():
|
35 |
if not os.path.exists(MODEL_PATH):
|
@@ -60,9 +89,6 @@ def load_model():
|
|
60 |
verbose=False
|
61 |
)
|
62 |
|
63 |
-
# بدء مراقبة الموارد
|
64 |
-
start_monitoring_thread()
|
65 |
-
|
66 |
# إنشاء تطبيق FastAPI
|
67 |
app = FastAPI()
|
68 |
|
@@ -97,6 +123,9 @@ async def startup_event():
|
|
97 |
logger.info("🚀 بدء تحميل النموذج في الخلفية...")
|
98 |
model_loading_thread = threading.Thread(target=load_model_in_background, daemon=True)
|
99 |
model_loading_thread.start()
|
|
|
|
|
|
|
100 |
|
101 |
# نموذج الطلب والرد
|
102 |
class ChatRequest(BaseModel):
|
@@ -107,31 +136,6 @@ class ChatResponse(BaseModel):
|
|
107 |
response: str
|
108 |
updated_history: list[list[str]]
|
109 |
|
110 |
-
# تنسيق prompt الدردشة
|
111 |
-
def format_chat(history, new_message):
|
112 |
-
messages = [("system", SYSTEM_PROMPT)]
|
113 |
-
|
114 |
-
for item in history[-8:]:
|
115 |
-
if isinstance(item, list) and len(item) == 2:
|
116 |
-
user_msg, bot_msg = item
|
117 |
-
messages.append(("user", user_msg))
|
118 |
-
messages.append(("assistant", bot_msg))
|
119 |
-
else:
|
120 |
-
logger.warning(f"⚠️ عنصر غير متوافق في التاريخ: {item}")
|
121 |
-
|
122 |
-
messages.append(("user", new_message))
|
123 |
-
|
124 |
-
formatted = []
|
125 |
-
for role, content in messages:
|
126 |
-
if role == "system":
|
127 |
-
formatted.append(f"<|im_start|>system\n{content}<|im_end|>")
|
128 |
-
elif role == "user":
|
129 |
-
formatted.append(f"<|im_start|>user\n{content}<|im_end|>")
|
130 |
-
else:
|
131 |
-
formatted.append(f"<|im_start|>assistant\n{content}<|im_end|>")
|
132 |
-
formatted.append("<|im_start|>assistant\n")
|
133 |
-
return "\n".join(formatted)
|
134 |
-
|
135 |
# التحقق من حالة النموذج
|
136 |
@app.get("/model-status")
|
137 |
def model_status():
|
@@ -157,7 +161,7 @@ def chat(req: ChatRequest):
|
|
157 |
logger.info(f"📩 طلب جديد من جلسة {req.session_id}: {req.message}")
|
158 |
try:
|
159 |
history = get_history(req.session_id)
|
160 |
-
logger.info(f"📜 التاريخ
|
161 |
prompt = format_chat(history, req.message)
|
162 |
|
163 |
output = llm(
|
@@ -187,16 +191,27 @@ def chat(req: ChatRequest):
|
|
187 |
def root():
|
188 |
return {"message": "الخادم يعمل", "status": "ok"}
|
189 |
|
190 |
-
#
|
191 |
@app.get("/monitor-log")
|
192 |
def read_monitor_log():
|
193 |
-
|
|
|
|
|
|
|
|
|
194 |
if not os.path.exists(log_path):
|
195 |
-
raise HTTPException(
|
|
|
|
|
|
|
|
|
196 |
try:
|
197 |
-
with open(log_path, encoding="utf-8") as f:
|
198 |
content = f.read()
|
199 |
return {"log": content}
|
200 |
except Exception as e:
|
201 |
logger.error(f"❌ فشل قراءة سجل المراقبة: {str(e)}")
|
202 |
-
raise HTTPException(
|
|
|
|
|
|
|
|
1 |
+
# إعداد السجل العام للتطبيق
|
2 |
+
import logging
|
3 |
+
logging.basicConfig(
|
4 |
+
level=logging.DEBUG,
|
5 |
+
format="🪵 [%(asctime)s] [%(levelname)s] %(message)s",
|
6 |
+
handlers=[
|
7 |
+
logging.StreamHandler(),
|
8 |
+
logging.FileHandler("app.log")
|
9 |
+
]
|
10 |
+
)
|
11 |
+
logger = logging.getLogger(__name__)
|
12 |
+
|
13 |
from fastapi import FastAPI, HTTPException
|
14 |
from pydantic import BaseModel
|
15 |
from llama_cpp import Llama
|
|
|
16 |
import os
|
17 |
import threading
|
18 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
20 |
from huggingface_hub import hf_hub_download
|
21 |
from memory import get_history, save_history
|
22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
# إعدادات النموذج
|
24 |
MODEL_REPO = "Qwen/Qwen2-1.5B-Instruct-GGUF"
|
25 |
MODEL_FILE = "qwen2-1_5b-instruct-q6_k.gguf"
|
|
|
34 |
3. Keep responses concise but complete
|
35 |
4. For complex requests, ask clarifying questions<|im_end|>"""
|
36 |
|
37 |
+
# تنسيق prompt الدردشة
|
38 |
+
def format_chat(history, new_message):
|
39 |
+
messages = [("system", SYSTEM_PROMPT)]
|
40 |
+
|
41 |
+
for item in history[-8:]:
|
42 |
+
if isinstance(item, list) and len(item) == 2:
|
43 |
+
user_msg, bot_msg = item
|
44 |
+
messages.append(("user", user_msg))
|
45 |
+
messages.append(("assistant", bot_msg))
|
46 |
+
else:
|
47 |
+
logger.warning(f"⚠️ عنصر غير متوافق في التاريخ: {item}")
|
48 |
+
|
49 |
+
messages.append(("user", new_message))
|
50 |
+
|
51 |
+
formatted = []
|
52 |
+
for role, content in messages:
|
53 |
+
if role == "system":
|
54 |
+
formatted.append(f"<|im_start|>system\n{content}<|im_end|>")
|
55 |
+
elif role == "user":
|
56 |
+
formatted.append(f"<|im_start|>user\n{content}<|im_end|>")
|
57 |
+
else:
|
58 |
+
formatted.append(f"<|im_start|>assistant\n{content}<|im_end|>")
|
59 |
+
formatted.append("<|im_start|>assistant\n")
|
60 |
+
return "\n".join(formatted)
|
61 |
+
|
62 |
# تحميل النموذج إذا لم يكن موجوداً
|
63 |
def load_model():
|
64 |
if not os.path.exists(MODEL_PATH):
|
|
|
89 |
verbose=False
|
90 |
)
|
91 |
|
|
|
|
|
|
|
92 |
# إنشاء تطبيق FastAPI
|
93 |
app = FastAPI()
|
94 |
|
|
|
123 |
logger.info("🚀 بدء تحميل النموذج في الخلفية...")
|
124 |
model_loading_thread = threading.Thread(target=load_model_in_background, daemon=True)
|
125 |
model_loading_thread.start()
|
126 |
+
|
127 |
+
# بدء مراقبة الموارد بعد إنشاء التطبيق
|
128 |
+
start_monitoring_thread()
|
129 |
|
130 |
# نموذج الطلب والرد
|
131 |
class ChatRequest(BaseModel):
|
|
|
136 |
response: str
|
137 |
updated_history: list[list[str]]
|
138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
# التحقق من حالة النموذج
|
140 |
@app.get("/model-status")
|
141 |
def model_status():
|
|
|
161 |
logger.info(f"📩 طلب جديد من جلسة {req.session_id}: {req.message}")
|
162 |
try:
|
163 |
history = get_history(req.session_id)
|
164 |
+
logger.info(f"📜 التاريخ الحالي: {len(history)} رسائل")
|
165 |
prompt = format_chat(history, req.message)
|
166 |
|
167 |
output = llm(
|
|
|
191 |
def root():
|
192 |
return {"message": "الخادم يعمل", "status": "ok"}
|
193 |
|
194 |
+
# endpoint قراءة سجل مراقبة الموارد
|
195 |
@app.get("/monitor-log")
|
196 |
def read_monitor_log():
|
197 |
+
import os
|
198 |
+
# استخدام المسار المطلق للتأكد من العثور على الملف
|
199 |
+
base_dir = os.path.dirname(os.path.abspath(__file__))
|
200 |
+
log_path = os.path.join(base_dir, "data", "monitor.log")
|
201 |
+
|
202 |
if not os.path.exists(log_path):
|
203 |
+
raise HTTPException(
|
204 |
+
status_code=404,
|
205 |
+
detail=f"لم يتم العثور على ملف السجل في المسار: {log_path}"
|
206 |
+
)
|
207 |
+
|
208 |
try:
|
209 |
+
with open(log_path, "r", encoding="utf-8") as f:
|
210 |
content = f.read()
|
211 |
return {"log": content}
|
212 |
except Exception as e:
|
213 |
logger.error(f"❌ فشل قراءة سجل المراقبة: {str(e)}")
|
214 |
+
raise HTTPException(
|
215 |
+
status_code=500,
|
216 |
+
detail=f"حدث خطأ أثناء قراءة السجل: {str(e)}"
|
217 |
+
)
|
data/main.py
CHANGED
@@ -1,9 +1,26 @@
|
|
1 |
import logging
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
logger = logging.getLogger(__name__)
|
3 |
|
4 |
try:
|
5 |
from app import app
|
6 |
logger.info("✅ استيراد app من app.py ناجح")
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
except ImportError as e:
|
8 |
logger.error(f"❌ فشل استيراد app من app.py: {str(e)}")
|
9 |
raise
|
|
|
1 |
import logging
|
2 |
+
import os
|
3 |
+
|
4 |
+
# إعداد السجل قبل الاستيرادات الأخرى
|
5 |
+
logging.basicConfig(
|
6 |
+
level=logging.DEBUG,
|
7 |
+
format="🪵 [%(asctime)s] [%(levelname)s] %(message)s",
|
8 |
+
handlers=[
|
9 |
+
logging.StreamHandler(),
|
10 |
+
logging.FileHandler("app.log")
|
11 |
+
]
|
12 |
+
)
|
13 |
logger = logging.getLogger(__name__)
|
14 |
|
15 |
try:
|
16 |
from app import app
|
17 |
logger.info("✅ استيراد app من app.py ناجح")
|
18 |
+
|
19 |
+
# التأكد من وجود مجلد البيانات
|
20 |
+
data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")
|
21 |
+
os.makedirs(data_dir, exist_ok=True)
|
22 |
+
logger.info(f"📁 مجلد البيانات جاهز: {data_dir}")
|
23 |
+
|
24 |
except ImportError as e:
|
25 |
logger.error(f"❌ فشل استيراد app من app.py: {str(e)}")
|
26 |
raise
|
data/memory.py
CHANGED
@@ -5,7 +5,8 @@ import logging
|
|
5 |
|
6 |
logger = logging.getLogger(__name__)
|
7 |
|
8 |
-
|
|
|
9 |
DB_PATH = os.path.join(DB_DIR, "chat_memory.db")
|
10 |
|
11 |
os.makedirs(DB_DIR, exist_ok=True)
|
@@ -21,6 +22,7 @@ try:
|
|
21 |
''')
|
22 |
conn.commit()
|
23 |
conn.close()
|
|
|
24 |
except sqlite3.Error as e:
|
25 |
logger.error(f"❌ خطأ في إنشاء قاعدة البيانات: {str(e)}")
|
26 |
|
@@ -39,7 +41,7 @@ def get_history(session_id: str):
|
|
39 |
if isinstance(data, list) and all(isinstance(pair, list) and len(pair) == 2 for pair in data):
|
40 |
return data
|
41 |
else:
|
42 |
-
logger.warning(f"⚠️ تم تجاهل تاريخ غير متوافق: {data}")
|
43 |
return []
|
44 |
except Exception as e:
|
45 |
logger.error(f"❌ خطأ أثناء استرجاع التاريخ: {str(e)}")
|
@@ -52,8 +54,8 @@ def save_history(session_id: str, history: list):
|
|
52 |
c.execute(
|
53 |
"REPLACE INTO memory (session_id, history) VALUES (?, ?)",
|
54 |
(session_id, json.dumps(history))
|
55 |
-
)
|
56 |
conn.commit()
|
57 |
conn.close()
|
|
|
58 |
except Exception as e:
|
59 |
-
logger.error(f"❌ خطأ أثناء حفظ التاريخ: {str(e)}")
|
|
|
5 |
|
6 |
logger = logging.getLogger(__name__)
|
7 |
|
8 |
+
base_dir = os.path.dirname(os.path.abspath(__file__))
|
9 |
+
DB_DIR = os.path.join(base_dir, "data")
|
10 |
DB_PATH = os.path.join(DB_DIR, "chat_memory.db")
|
11 |
|
12 |
os.makedirs(DB_DIR, exist_ok=True)
|
|
|
22 |
''')
|
23 |
conn.commit()
|
24 |
conn.close()
|
25 |
+
logger.info(f"✅ تم إنشاء/فتح قاعدة البيانات في: {DB_PATH}")
|
26 |
except sqlite3.Error as e:
|
27 |
logger.error(f"❌ خطأ في إنشاء قاعدة البيانات: {str(e)}")
|
28 |
|
|
|
41 |
if isinstance(data, list) and all(isinstance(pair, list) and len(pair) == 2 for pair in data):
|
42 |
return data
|
43 |
else:
|
44 |
+
logger.warning(f"⚠️ تم تجاهل تاريخ غير متوافق: {type(data)}")
|
45 |
return []
|
46 |
except Exception as e:
|
47 |
logger.error(f"❌ خطأ أثناء استرجاع التاريخ: {str(e)}")
|
|
|
54 |
c.execute(
|
55 |
"REPLACE INTO memory (session_id, history) VALUES (?, ?)",
|
56 |
(session_id, json.dumps(history))
|
|
|
57 |
conn.commit()
|
58 |
conn.close()
|
59 |
+
logger.debug(f"💾 تم حفظ التاريخ للجلسة: {session_id}")
|
60 |
except Exception as e:
|
61 |
+
logger.error(f"❌ خطأ أثناء حفظ التاريخ: {str(e)}")
|
data/monitor.py
CHANGED
@@ -1,71 +1,83 @@
|
|
1 |
-
import threading
|
2 |
-
import time
|
3 |
-
import psutil
|
4 |
-
import logging
|
5 |
-
from collections import deque
|
6 |
-
import os
|
7 |
-
import tracemalloc
|
8 |
-
|
9 |
-
logger
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import threading
|
2 |
+
import time
|
3 |
+
import psutil
|
4 |
+
import logging
|
5 |
+
from collections import deque
|
6 |
+
import os
|
7 |
+
import tracemalloc
|
8 |
+
|
9 |
+
# إنشاء logger خاص لهذه الوحدة
|
10 |
+
logger = logging.getLogger("monitor")
|
11 |
+
logger.setLevel(logging.DEBUG)
|
12 |
+
|
13 |
+
# إعداد ملف السجل
|
14 |
+
base_dir = os.path.dirname(os.path.abspath(__file__))
|
15 |
+
log_dir = os.path.join(base_dir, "data")
|
16 |
+
os.makedirs(log_dir, exist_ok=True)
|
17 |
+
log_path = os.path.join(log_dir, "monitor.log")
|
18 |
+
|
19 |
+
# إعداد handler لملف السجل
|
20 |
+
file_handler = logging.FileHandler(log_path)
|
21 |
+
file_handler.setLevel(logging.DEBUG)
|
22 |
+
formatter = logging.Formatter("📁 [%(asctime)s] [%(levelname)s] %(message)s")
|
23 |
+
file_handler.setFormatter(formatter)
|
24 |
+
logger.addHandler(file_handler)
|
25 |
+
|
26 |
+
# إعداد handler للوحدة الأساسية (اختياري)
|
27 |
+
stream_handler = logging.StreamHandler()
|
28 |
+
stream_handler.setFormatter(logging.Formatter("🪵 [%(asctime)s] [%(levelname)s] %(message)s"))
|
29 |
+
logger.addHandler(stream_handler)
|
30 |
+
|
31 |
+
# تفعيل تتبع الذاكرة
|
32 |
+
tracemalloc.start()
|
33 |
+
logger.info("🔍 بدأ تتبع الذاكرة")
|
34 |
+
|
35 |
+
# تخزين آخر 10 قياسات
|
36 |
+
cpu_history = deque(maxlen=10)
|
37 |
+
mem_history = deque(maxlen=10)
|
38 |
+
current_metrics = {'cpu': 0, 'memory': 0}
|
39 |
+
|
40 |
+
def monitor_resources():
|
41 |
+
counter = 0
|
42 |
+
logger.info("🔁 بدأ تشغيل مراقبة الموارد")
|
43 |
+
while True:
|
44 |
+
try:
|
45 |
+
process = psutil.Process(os.getpid())
|
46 |
+
mem_info = process.memory_info()
|
47 |
+
cpu_percent = psutil.cpu_percent(interval=0.5)
|
48 |
+
mem_percent = psutil.virtual_memory().percent
|
49 |
+
|
50 |
+
current_metrics['cpu'] = cpu_percent
|
51 |
+
current_metrics['memory'] = mem_percent
|
52 |
+
cpu_history.append(cpu_percent)
|
53 |
+
mem_history.append(mem_percent)
|
54 |
+
|
55 |
+
logger.info(f"🧠 الذاكرة - RSS: {mem_info.rss / 1024**2:.2f} MB | VMS: {mem_info.vms / 1024**2:.2f} MB")
|
56 |
+
current, peak = tracemalloc.get_traced_memory()
|
57 |
+
logger.info(f"📊 tracemalloc - الحالي: {current / 1024**2:.2f} MB | الأعلى: {peak / 1024**2:.2f} MB")
|
58 |
+
|
59 |
+
counter += 1
|
60 |
+
if counter % 12 == 0:
|
61 |
+
snapshot = tracemalloc.take_snapshot()
|
62 |
+
top_stats = snapshot.statistics('lineno')
|
63 |
+
logger.info("📌 أعلى 5 أسطر استهلاكًا للذاكرة:")
|
64 |
+
for stat in top_stats[:5]:
|
65 |
+
logger.info(f" {stat}")
|
66 |
+
|
67 |
+
except Exception as e:
|
68 |
+
logger.exception(f"❌ استثناء في مراقبة الموارد: {str(e)}")
|
69 |
+
|
70 |
+
time.sleep(5)
|
71 |
+
|
72 |
+
def get_current_metrics():
|
73 |
+
return {
|
74 |
+
'cpu': current_metrics['cpu'],
|
75 |
+
'memory': current_metrics['memory'],
|
76 |
+
'cpu_history': list(cpu_history),
|
77 |
+
'mem_history': list(mem_history)
|
78 |
+
}
|
79 |
+
|
80 |
+
def start_monitoring_thread():
|
81 |
+
thread = threading.Thread(target=monitor_resources, daemon=True)
|
82 |
+
thread.start()
|
83 |
+
logger.info("✅ تم بدء مراقبة الموارد في خيط منفصل")
|