# ============================================================ # 📄 الملف: app/models_loader.py # 🎯 الغرض: تحميل وإدارة الموارد الثقيلة في الذاكرة (مرة واحدة فقط). # # الموارد هي: # - البيانات المنظّفة (data.csv) # - ملفات الـ embeddings المحفوظة (.npy) # - نماذج SentenceTransformer # # الفكرة: هذه الموارد كبيرة وبطيئة التحميل، فنحمّلها مرة واحدة # ونحتفظ بها في كائن واحد (state) يُشارَك بين كل الطلبات، # بدل ما نحمّلها في كل طلب بحث (وهذا يبطّئ كل شيء). # ============================================================ import numpy as np import pandas as pd from sentence_transformers import SentenceTransformer import config class ResourceManager: """يحتفظ بكل الموارد المحمّلة ويحمّلها كسولًا (lazy) عند أول استخدام.""" def __init__(self): self.df: pd.DataFrame | None = None self.embeddings: dict[str, np.ndarray] = {} # model_key -> مصفوفة embeddings self.models: dict[str, SentenceTransformer] = {} # model_key -> النموذج المحمّل # -------------------------------------------------------- # 📂 تحميل البيانات المنظّفة # -------------------------------------------------------- def get_dataframe(self) -> pd.DataFrame: if self.df is None: if not config.PROCESSED_DATA_PATH.exists(): raise FileNotFoundError( f"ملف البيانات المنظّفة غير موجود: {config.PROCESSED_DATA_PATH}\n" "شغّل أولًا: python scripts/prepare_data.py" ) self.df = pd.read_csv(config.PROCESSED_DATA_PATH) return self.df # -------------------------------------------------------- # 🔢 تحميل الـ embeddings المحفوظة لنموذج معيّن # -------------------------------------------------------- def get_embeddings(self, model_key: str) -> np.ndarray: if model_key not in self.embeddings: path = config.embedding_path(model_key) if not path.exists(): raise FileNotFoundError( f"ملف الـ embeddings غير موجود: {path}\n" "شغّل أولًا: python scripts/build_embeddings.py" ) # mmap_mode="r": تبقى المصفوفة على القرص ولا تُحمّل كاملة في # الذاكرة — يوفّر ~مئات الميغابايت (مهم للنشر على ذاكرة محدودة). self.embeddings[model_key] = np.load(path, mmap_mode="r") return self.embeddings[model_key] # -------------------------------------------------------- # 🤖 تحميل نموذج SentenceTransformer (كسولًا) # -------------------------------------------------------- def get_model(self, model_key: str, hf_id: str) -> SentenceTransformer: if model_key not in self.models: print(f"⏳ تحميل النموذج: {model_key} ...") self.models[model_key] = SentenceTransformer(hf_id, device=config.DEVICE) print(f"✅ تم تحميل: {model_key}") return self.models[model_key] # -------------------------------------------------------- # 🔥 تسخين: تحميل كل النماذج والـ embeddings مسبقًا # -------------------------------------------------------- def warmup(self): """يُستدعى عند إقلاع الخادم لتحميل كل شيء مقدّمًا.""" self.get_dataframe() for cfg in config.MODELS: self.get_embeddings(cfg["key"]) self.get_model(cfg["key"], cfg["hf_id"]) # نسخة وحيدة (singleton) تُستخدم في كل المشروع resources = ResourceManager()