""" قياس زمن كل جزء من pipeline البحث + مقارنة كاشف النية (محلي مقابل Groq API). الاستخدام: PYTHONPATH=. .venv/bin/python scripts/benchmark.py PYTHONPATH=. .venv/bin/python scripts/benchmark.py "صوت الرعد" "جذر كلمة هزيم" """ import os import sys import time QUERIES = sys.argv[1:] or [ "صوت يصدر عن السحاب", "جذر كلمة هزيم", "شخص يحب القراءة", ] SEP = "─" * 52 DSEP = "═" * 52 def ms(seconds: float) -> str: return f"{seconds * 1000:.0f} ms" def benchmark(query: str) -> dict: from app.intent_detector import detect_intent, detect_intent_groq print(f"\n{SEP}") print(f" الاستعلام: {query}") print(SEP) # 1. Intent Detection — مقارنة الطريقتين t0 = time.perf_counter() local_result = detect_intent(query) t_local = time.perf_counter() - t0 t0 = time.perf_counter() groq_result = detect_intent_groq(query) t_groq = time.perf_counter() - t0 groq_error = groq_result["intent"] == "ERROR" match = (not groq_error) and local_result["intent"] == groq_result["intent"] print(" 1. Intent Detection") print(f" محلي (Embeddings): {ms(t_local):>8} → {local_result}") if groq_error: print(f" Groq API: تعذّر → {groq_result['word']}") else: print(f" Groq API: {ms(t_groq):>8} → {groq_result}") print(f" متطابقان؟ {'✓' if match else '✗'}") # يكمل الـ pipeline بناءً على النتيجة المحلية فقط (سلوك التطبيق لا يتغير) intent_result = local_result if intent_result["intent"] == "ROOT": from app.root_search import search_root t0 = time.perf_counter() root = search_root(intent_result["word"] or query, db=None) t_root = time.perf_counter() - t0 print(f" 2. Root Search (lexicon) {ms(t_root):>10}") print(f" → {root.get('type')} | {root.get('stem', '—')}") print(f"{SEP}") print(f" الإجمالي (محلي) {ms(t_local + t_root):>10}") else: from app.search import get_searcher searcher = get_searcher() t0 = time.perf_counter() candidates = searcher.search(query) t_faiss = time.perf_counter() - t0 print(f" 2. FAISS Search {ms(t_faiss):>10} ({len(candidates)} نتيجة)") from app.reranker import get_reranker reranker = get_reranker() t0 = time.perf_counter() results = reranker.rerank(query, candidates) t_rerank = time.perf_counter() - t0 print(f" 3. Reranker (BGE) {ms(t_rerank):>10} ({len(results)} نتيجة)") total = t_local + t_faiss + t_rerank print(f"{SEP}") print(f" الإجمالي (محلي) {ms(total):>10}") print(f" أفضل نتيجة: {results[0].word if results else '—'}") print(SEP) return {"t_local": t_local, "t_groq": t_groq, "match": match, "groq_error": groq_error} if __name__ == "__main__": print("\n⏳ تحميل النماذج (مرة واحدة فقط)...") load_start = time.perf_counter() from app.search import get_searcher from app.reranker import get_reranker get_searcher() get_reranker() print(f"✅ جاهز ({ms(time.perf_counter() - load_start)})\n") if not os.getenv("GROQ_API_KEY", "").strip(): print("⚠️ تحذير: GROQ_API_KEY غير موجود في .env — نتائج Groq ستظهر كأخطاء أدناه.\n") stats = [benchmark(q) for q in QUERIES] valid = [s for s in stats if not s["groq_error"]] matches = sum(1 for s in valid if s["match"]) avg_local = sum(s["t_local"] for s in stats) / len(stats) avg_groq = sum(s["t_groq"] for s in valid) / len(valid) if valid else 0 print(f"\n{DSEP}") print(" ملخص المقارنة") print(DSEP) print(f" عدد الاستعلامات: {len(stats)}") print(f" تطابق التصنيف: {matches}/{len(valid)}" + (" (0 بسبب فشل Groq)" if not valid else "")) print(f" متوسط الزمن — محلي: {ms(avg_local)}") print(f" متوسط الزمن — Groq: {ms(avg_groq) if valid else '—'}") print(DSEP)