هذا المقال يتطلب مستوى محترف — أو متوسط متقدم لو عندك خبرة سابقة في RAG و embeddings. وقت القراءة: 9 دقائق تقريباً.
لو الـ RAG بتاعك بيرجّعلك إجابة معقولة في 78% من الأسئلة لكن بيفشل في الأسئلة اللي فيها رقم منتج (SKU-7281) أو UUID أو اسم خطأ تقني (ECONNRESET)، المشكلة مش في الـ embedding model. Dense search لوحده مش بيشوف الكلمات الحرفية كويس. الحل اسمه Hybrid Search، وبيرفع الاسترجاع من 78% لـ 96% على BEIR benchmark بإضافة طبقة BM25 جنب الـ embeddings.
Hybrid Search: لمّا البحث الدلالي وحده مش كفاية
المشكلة باختصار
الـ Dense Embeddings (زي bge-large أو text-embedding-3) بتترجم النص لـ vector بـ 1024 بُعد، وبتبحث عن أقرب vectors في الفضاء. ده ممتاز لما بتسأل "ازاي أتعامل مع timeout"، حتى لو المستند مكتوب فيه "كيفية حل مشاكل انتهاء المهلة"، الموديل بيلاقيها. لكن لما بتسأل "إيه معنى ECONNRESET"، الـ embedding ما بيهتمش بحرفية الكلمة. هي مجرد token بين آلاف، وبتترجم لـ vector عام.
BM25 على العكس. خوارزمية إحصائية كلاسيكية بتحسب وزن كل كلمة بناءً على Term Frequency و Inverse Document Frequency. الكلمة النادرة (ECONNRESET) بتاخد وزن عالي. الكلمة الشائعة (the, of) بتاخد وزن صفر تقريباً. النتيجة: BM25 بيلاقي الـ exact match في ميلي ثانية، لكن بيفشل لو السؤال صياغته مختلفة عن المستند.
المثال للمبتدئ — أمين المكتبة بطريقتين
تخيّل مكتبة فيها أمينَين. الأول، اسمه "أبو دلالة"، عنده ذاكرة ممتازة في موضوعات الكتب. لو سألته "عايز كتاب عن إدارة الفريق"، هيرجعلك كتب فيها "leadership" و"team building" و"organizational behavior". لكن لو سألته "فين كتاب رقم 9789774xyz"، هيتلكّك. الثاني، اسمه "أبو رقم"، حافظ كل الأرقام والـ ISBN، لكن لو سألته "عايز كتاب عن الإدارة"، مش هيلاقيلك حاجة لو الكلمة دي مش مذكورة في العنوان حرفياً. Hybrid Search بياخد رد الاتنين ويدمجهم. كل سؤال بيوصل لأمينَين في نفس الوقت، وبعدين فيه راوتر تالت بيقرّر مين رد أصح.
الشرح العلمي — Reciprocal Rank Fusion
الدمج مش متوسط حسابي للسكورات. السبب: BM25 بيرجّع scores من 0 لـ 25 ممكن، Dense cosine similarity من 0 لـ 1. متوسطهم بيظلم Dense. الحل اسمه RRF: Reciprocal Rank Fusion، اللي قدّمه Cormack وزملاؤه في SIGIR 2009.
الفكرة: انسى الـ scores، خد الـ rank بس. لو مستند طلع أول في BM25 (rank=1) وتاني في Dense (rank=2)، السكور النهائي = 1/(60+1) + 1/(60+2) = 0.0328. الـ 60 ده اسمه constant k، ومجرّب على Microsoft TREC وأثبت إنه أفضل قيمة افتراضية. كل ما المستند طلع متقدم في الاتنين، كل ما السكور أعلى. الميزة الكبيرة: RRF بيتجاهل المقياس الأصلي للسكور، فبيبقى عادل بين خوارزميتين بـ scales مختلفة.
الكود الشغّال
السكربت ده بيشتغل على 1000 مستند عربي مختلط بكلمات تقنية إنجليزية، بيبني BM25 index و dense index، وبيدمجهم بـ RRF. الـ stack: rank_bm25 + sentence-transformers + numpy. مفيش vector DB خارجي علشان نركّز على الفكرة نفسها.