Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -33,14 +33,7 @@ if GEMINI_API_KEY:
|
|
| 33 |
except Exception as e:
|
| 34 |
print(f"[ERROR] Failed to init Gemini Client: {e}")
|
| 35 |
|
| 36 |
-
# ---------------------------------------------------------
|
| 37 |
-
# 2. تعريف التطبيق
|
| 38 |
-
# ---------------------------------------------------------
|
| 39 |
app = FastAPI(title="Hajeen Islamic QA API")
|
| 40 |
-
|
| 41 |
-
# ---------------------------------------------------------
|
| 42 |
-
# 3. دوال مساعدة عامة
|
| 43 |
-
# ---------------------------------------------------------
|
| 44 |
DISCLAIMERS = {
|
| 45 |
"ar": "",
|
| 46 |
"de": "\n\n(Hinweis: Automatisch übersetzt. Konsultieren Sie das arabische Original.)",
|
|
@@ -126,9 +119,7 @@ def check_rate_limit(ip: str):
|
|
| 126 |
raise HTTPException(status_code=429, detail="Rate limit exceeded.")
|
| 127 |
dq.append(now)
|
| 128 |
|
| 129 |
-
|
| 130 |
-
# 4. إعدادات النماذج والبيانات
|
| 131 |
-
# ---------------------------------------------------------
|
| 132 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 133 |
|
| 134 |
DATA_FILE_ID = "1GMG6fVxhUuBEAHP91c8RAUdUJh5TxY5O"
|
|
@@ -154,14 +145,10 @@ def safe_download(file_id, output_path):
|
|
| 154 |
gdown.download(id=file_id, output=output_path, quiet=False)
|
| 155 |
except Exception as e:
|
| 156 |
print(f"[DOWNLOAD ERROR] {e}")
|
| 157 |
-
|
| 158 |
-
# تهيئة النماذج
|
| 159 |
model_name = 'aubmindlab/bert-base-arabertv2'
|
| 160 |
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 161 |
model = AutoModel.from_pretrained(model_name).to(device)
|
| 162 |
vectorizer = TfidfVectorizer(analyzer="char_wb", ngram_range=(3,5), min_df=1)
|
| 163 |
-
|
| 164 |
-
# متغيرات جلوبال
|
| 165 |
df_main = pd.DataFrame()
|
| 166 |
df_learned = pd.DataFrame()
|
| 167 |
df_all = pd.DataFrame()
|
|
@@ -180,14 +167,10 @@ def load_hadith_corpora(paths: dict) -> pd.DataFrame:
|
|
| 180 |
df["matn_clean"] = df["matn_full"].fillna("").apply(normalize_ar)
|
| 181 |
df["source"] = src
|
| 182 |
df["hadith_number"] = df["hadith_number"].astype(str).str.replace(r"\D","",regex=True)
|
| 183 |
-
|
| 184 |
-
# --- FIX: Ensure grading is string and no NaNs ---
|
| 185 |
if "grading" not in df.columns:
|
| 186 |
df["grading"] = ""
|
| 187 |
else:
|
| 188 |
df["grading"] = df["grading"].fillna("").astype(str)
|
| 189 |
-
# -------------------------------------------------
|
| 190 |
-
|
| 191 |
all_dfs.append(df[["source","hadith_number","matn_full","matn_clean","grading"]])
|
| 192 |
except Exception as e:
|
| 193 |
print(f"Error loading {src}: {e}")
|
|
@@ -206,8 +189,6 @@ async def startup_event():
|
|
| 206 |
safe_download(ID_BUKHARI, PATHS["bukhari"])
|
| 207 |
safe_download(ID_MUSLIM, PATHS["muslim"])
|
| 208 |
safe_download(ID_MUSNAD, PATHS["musnad"])
|
| 209 |
-
|
| 210 |
-
# تحميل الفتاوى
|
| 211 |
if os.path.exists(data_path):
|
| 212 |
df_main = pd.read_csv(data_path)
|
| 213 |
|
|
@@ -221,8 +202,6 @@ async def startup_event():
|
|
| 221 |
question_embeddings = np.load(embeddings_path)
|
| 222 |
index = faiss.IndexFlatL2(question_embeddings.shape[1])
|
| 223 |
index.add(question_embeddings.astype('float32'))
|
| 224 |
-
|
| 225 |
-
# تحميل الأحاديث
|
| 226 |
df_all = load_hadith_corpora(PATHS)
|
| 227 |
if not df_all.empty:
|
| 228 |
tfidf_matrix = vectorizer.fit_transform(df_all["matn_clean"])
|
|
@@ -238,9 +217,6 @@ def get_embedding_for_query(text: str):
|
|
| 238 |
outputs = model(**inputs)
|
| 239 |
return outputs.last_hidden_state[:, 0, :].cpu().numpy()
|
| 240 |
|
| 241 |
-
# ---------------------------------------------------------
|
| 242 |
-
# 5. وظيفة Gemini للبحث
|
| 243 |
-
# ---------------------------------------------------------
|
| 244 |
def ask_gemini_with_search(query: str, lang: str = "ar"):
|
| 245 |
if not gemini_client: return None
|
| 246 |
model_id = "gemini-2.0-flash"
|
|
@@ -250,11 +226,17 @@ def ask_gemini_with_search(query: str, lang: str = "ar"):
|
|
| 250 |
مهمتك: الإجابة على الأسئلة الشرعية والفتاوى بدقة بناءً على نتائج البحث الموثوقة.
|
| 251 |
|
| 252 |
تعليمات هامة جداً:
|
| 253 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
. لغة الإجابة: المستخدم يسأل بلغة الكود ({lang}). يجب أن تكون إجابتك بالكامل بهذه اللغة ({lang}). لا تجب بالعربية إذا كان السؤال بغيرها.
|
| 255 |
. استخدم "بحث Google" دائماً للتأكد من المعلومات من مصادر مثل (إسلام ويب، الإسلام سؤال وجواب، ابن باز).
|
| 256 |
. الاختصار المفيد: لا تكثر من الحشو، وأعط الزبدة مع الدليل.
|
| 257 |
-
. اذكر
|
| 258 |
"""
|
| 259 |
|
| 260 |
try:
|
|
@@ -281,10 +263,6 @@ def ask_gemini_with_search(query: str, lang: str = "ar"):
|
|
| 281 |
print(f"[GEMINI ERROR] {e}")
|
| 282 |
return None
|
| 283 |
|
| 284 |
-
# ---------------------------------------------------------
|
| 285 |
-
# 6. API Endpoints
|
| 286 |
-
# ---------------------------------------------------------
|
| 287 |
-
|
| 288 |
class SearchRequest(BaseModel):
|
| 289 |
query: str
|
| 290 |
top_k: int = 1
|
|
@@ -315,8 +293,6 @@ def search(request: SearchRequest):
|
|
| 315 |
if guard:
|
| 316 |
error_msg = translate_error_detail("عذراً، السؤال غير مناسب.", target_lang)
|
| 317 |
raise HTTPException(status_code=400, detail=error_msg)
|
| 318 |
-
|
| 319 |
-
# 1. التعلم الذاتي
|
| 320 |
if not df_learned.empty:
|
| 321 |
row = df_learned[df_learned["question"] == q]
|
| 322 |
if not row.empty:
|
|
@@ -330,7 +306,7 @@ def search(request: SearchRequest):
|
|
| 330 |
"score": 100
|
| 331 |
}]}
|
| 332 |
|
| 333 |
-
|
| 334 |
is_arabic_query = any("\u0600" <= c <= "\u06FF" for c in q)
|
| 335 |
if is_arabic_query and index is not None:
|
| 336 |
query_emb = get_embedding_for_query(q)
|
|
@@ -350,7 +326,7 @@ def search(request: SearchRequest):
|
|
| 350 |
"score": int(similarity)
|
| 351 |
}]}
|
| 352 |
|
| 353 |
-
|
| 354 |
print(f"[INFO] Asking Gemini: {q} (Lang: {target_lang})")
|
| 355 |
gemini_answer = ask_gemini_with_search(q, lang=target_lang)
|
| 356 |
|
|
@@ -385,7 +361,7 @@ def feedback(req: FeedbackRequest):
|
|
| 385 |
pd.DataFrame([req.dict()]).to_csv(FEEDBACK_FILE, mode='a', header=False, index=False)
|
| 386 |
return {"message": "تم حفظ التقييم."}
|
| 387 |
|
| 388 |
-
|
| 389 |
SOURCE_ALIAS = {
|
| 390 |
"bukhari": "صحيح البخاري", "muslim": "صحيح مسلم", "musnad": "مسند أحمد",
|
| 391 |
"صحيح البخاري": "صحيح البخاري", "صحيح مسلم": "صحيح مسلم", "مسند أحمد": "مسند أحمد"
|
|
@@ -472,13 +448,8 @@ def hadith_search(req: HadithSearchRequest, request: Request):
|
|
| 472 |
base_score = sims[i] * 100
|
| 473 |
fuzz_score = fuzz.token_set_ratio(q_norm, row["matn_clean"])
|
| 474 |
source_bonus = 15 if row["source"] == "صحيح البخاري" else (10 if row["source"] == "صحيح مسلم" else 0)
|
| 475 |
-
|
| 476 |
-
# --- FIX: Safe grading check for NaNs ---
|
| 477 |
-
# Ensure grading value is converted to string before check
|
| 478 |
grading_val = str(row.get("grading", "") or "")
|
| 479 |
grading_bonus = 5 if "صحيح" in grading_val else 0
|
| 480 |
-
# ----------------------------------------
|
| 481 |
-
|
| 482 |
final_score = base_score * 0.5 + fuzz_score * 0.5 + source_bonus + grading_bonus
|
| 483 |
candidates.append((row, final_score))
|
| 484 |
|
|
|
|
| 33 |
except Exception as e:
|
| 34 |
print(f"[ERROR] Failed to init Gemini Client: {e}")
|
| 35 |
|
|
|
|
|
|
|
|
|
|
| 36 |
app = FastAPI(title="Hajeen Islamic QA API")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
DISCLAIMERS = {
|
| 38 |
"ar": "",
|
| 39 |
"de": "\n\n(Hinweis: Automatisch übersetzt. Konsultieren Sie das arabische Original.)",
|
|
|
|
| 119 |
raise HTTPException(status_code=429, detail="Rate limit exceeded.")
|
| 120 |
dq.append(now)
|
| 121 |
|
| 122 |
+
|
|
|
|
|
|
|
| 123 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 124 |
|
| 125 |
DATA_FILE_ID = "1GMG6fVxhUuBEAHP91c8RAUdUJh5TxY5O"
|
|
|
|
| 145 |
gdown.download(id=file_id, output=output_path, quiet=False)
|
| 146 |
except Exception as e:
|
| 147 |
print(f"[DOWNLOAD ERROR] {e}")
|
|
|
|
|
|
|
| 148 |
model_name = 'aubmindlab/bert-base-arabertv2'
|
| 149 |
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 150 |
model = AutoModel.from_pretrained(model_name).to(device)
|
| 151 |
vectorizer = TfidfVectorizer(analyzer="char_wb", ngram_range=(3,5), min_df=1)
|
|
|
|
|
|
|
| 152 |
df_main = pd.DataFrame()
|
| 153 |
df_learned = pd.DataFrame()
|
| 154 |
df_all = pd.DataFrame()
|
|
|
|
| 167 |
df["matn_clean"] = df["matn_full"].fillna("").apply(normalize_ar)
|
| 168 |
df["source"] = src
|
| 169 |
df["hadith_number"] = df["hadith_number"].astype(str).str.replace(r"\D","",regex=True)
|
|
|
|
|
|
|
| 170 |
if "grading" not in df.columns:
|
| 171 |
df["grading"] = ""
|
| 172 |
else:
|
| 173 |
df["grading"] = df["grading"].fillna("").astype(str)
|
|
|
|
|
|
|
| 174 |
all_dfs.append(df[["source","hadith_number","matn_full","matn_clean","grading"]])
|
| 175 |
except Exception as e:
|
| 176 |
print(f"Error loading {src}: {e}")
|
|
|
|
| 189 |
safe_download(ID_BUKHARI, PATHS["bukhari"])
|
| 190 |
safe_download(ID_MUSLIM, PATHS["muslim"])
|
| 191 |
safe_download(ID_MUSNAD, PATHS["musnad"])
|
|
|
|
|
|
|
| 192 |
if os.path.exists(data_path):
|
| 193 |
df_main = pd.read_csv(data_path)
|
| 194 |
|
|
|
|
| 202 |
question_embeddings = np.load(embeddings_path)
|
| 203 |
index = faiss.IndexFlatL2(question_embeddings.shape[1])
|
| 204 |
index.add(question_embeddings.astype('float32'))
|
|
|
|
|
|
|
| 205 |
df_all = load_hadith_corpora(PATHS)
|
| 206 |
if not df_all.empty:
|
| 207 |
tfidf_matrix = vectorizer.fit_transform(df_all["matn_clean"])
|
|
|
|
| 217 |
outputs = model(**inputs)
|
| 218 |
return outputs.last_hidden_state[:, 0, :].cpu().numpy()
|
| 219 |
|
|
|
|
|
|
|
|
|
|
| 220 |
def ask_gemini_with_search(query: str, lang: str = "ar"):
|
| 221 |
if not gemini_client: return None
|
| 222 |
model_id = "gemini-2.0-flash"
|
|
|
|
| 226 |
مهمتك: الإجابة على الأسئلة الشرعية والفتاوى بدقة بناءً على نتائج البحث الموثوقة.
|
| 227 |
|
| 228 |
تعليمات هامة جداً:
|
| 229 |
+
. أنت مختص في الفتاوي والأسألة الشرعية لا تدخل في أمور أخرى، إذا كان السؤال خارج نطاق الشريعة او أنه لا يبدو ك فتوى او سؤال ديني أو أستفسار ديني لا تجيب مثال إذا كان السؤال (ما هي عاصمة سوريا، كم سعر الدولار، هل أنت نموذج لغوي، هل يمكنك تعديل كود،) أعتذر بأدب وقول (أنا نموذج لغوي تعلمت على الأجابة على الأسألة الشرعية والدينية من مصادر أهل السنة والجماعة، أما سؤالك فيمكنك العثور على أجابة له في Google أو من Gemini أو أنماط أخرى).
|
| 230 |
+
. أذا تم الأستفسار عن أمر ديني أجب وأحرص على الشرح أذا كان أستفسار وليس سؤال، أما أذا سؤال لا تكثر من الحشو وأعط الزبدة مع الحفاظ على جمالية الجواب.
|
| 231 |
+
. المستخدم ليس دائماً على حق، ربما يسهو أو يخطأ، أذا اخطأ صحح له أتباعاً لمنهج أهل السنة والجماعة فقط، وحاول مساعدته في العثور على أجابة (دينية فقط) أما دون ذلك أجب بأعتذار كما ذكرت سابقاً.
|
| 232 |
+
. أحرص على عدم الأجابة على اسألة سياسية او علمية.
|
| 233 |
+
. أذا تم فتح نقاش معك قل أنا لا أصلح للنقاشات (أنا فقط أبحث وأجيب على اسألة فتوى او أستفسار) وشكراً
|
| 234 |
+
. ميولك يجب أن تكون لمنهج أهل السنة والجماعة فقط أما دون ذلك أعتذر بأدب وقل هداكم الله.
|
| 235 |
+
. أبدأ أجاباتك دائما بـ (الحمدلله والصلاة والسلام على رسول الله أما بعد : واختم جوابك بـ (والله أعلم).
|
| 236 |
. لغة الإجابة: المستخدم يسأل بلغة الكود ({lang}). يجب أن تكون إجابتك بالكامل بهذه اللغة ({lang}). لا تجب بالعربية إذا كان السؤال بغيرها.
|
| 237 |
. استخدم "بحث Google" دائماً للتأكد من المعلومات من مصادر مثل (إسلام ويب، الإسلام سؤال وجواب، ابن باز).
|
| 238 |
. الاختصار المفيد: لا تكثر من الحشو، وأعط الزبدة مع الدليل.
|
| 239 |
+
. اذكر دائماً أن مصادرك هي محرك بحث Google وبالتحديد موقعين أسلام ويب وإسلام سؤال وجواب .
|
| 240 |
"""
|
| 241 |
|
| 242 |
try:
|
|
|
|
| 263 |
print(f"[GEMINI ERROR] {e}")
|
| 264 |
return None
|
| 265 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
class SearchRequest(BaseModel):
|
| 267 |
query: str
|
| 268 |
top_k: int = 1
|
|
|
|
| 293 |
if guard:
|
| 294 |
error_msg = translate_error_detail("عذراً، السؤال غير مناسب.", target_lang)
|
| 295 |
raise HTTPException(status_code=400, detail=error_msg)
|
|
|
|
|
|
|
| 296 |
if not df_learned.empty:
|
| 297 |
row = df_learned[df_learned["question"] == q]
|
| 298 |
if not row.empty:
|
|
|
|
| 306 |
"score": 100
|
| 307 |
}]}
|
| 308 |
|
| 309 |
+
|
| 310 |
is_arabic_query = any("\u0600" <= c <= "\u06FF" for c in q)
|
| 311 |
if is_arabic_query and index is not None:
|
| 312 |
query_emb = get_embedding_for_query(q)
|
|
|
|
| 326 |
"score": int(similarity)
|
| 327 |
}]}
|
| 328 |
|
| 329 |
+
|
| 330 |
print(f"[INFO] Asking Gemini: {q} (Lang: {target_lang})")
|
| 331 |
gemini_answer = ask_gemini_with_search(q, lang=target_lang)
|
| 332 |
|
|
|
|
| 361 |
pd.DataFrame([req.dict()]).to_csv(FEEDBACK_FILE, mode='a', header=False, index=False)
|
| 362 |
return {"message": "تم حفظ التقييم."}
|
| 363 |
|
| 364 |
+
|
| 365 |
SOURCE_ALIAS = {
|
| 366 |
"bukhari": "صحيح البخاري", "muslim": "صحيح مسلم", "musnad": "مسند أحمد",
|
| 367 |
"صحيح البخاري": "صحيح البخاري", "صحيح مسلم": "صحيح مسلم", "مسند أحمد": "مسند أحمد"
|
|
|
|
| 448 |
base_score = sims[i] * 100
|
| 449 |
fuzz_score = fuzz.token_set_ratio(q_norm, row["matn_clean"])
|
| 450 |
source_bonus = 15 if row["source"] == "صحيح البخاري" else (10 if row["source"] == "صحيح مسلم" else 0)
|
|
|
|
|
|
|
|
|
|
| 451 |
grading_val = str(row.get("grading", "") or "")
|
| 452 |
grading_bonus = 5 if "صحيح" in grading_val else 0
|
|
|
|
|
|
|
| 453 |
final_score = base_score * 0.5 + fuzz_score * 0.5 + source_bonus + grading_bonus
|
| 454 |
candidates.append((row, final_score))
|
| 455 |
|