متطلب مستوى: محترف. هذا المقال مبني على فرضية إن عندك RAG شغّال بـ vector store، وعارف الفرق بين embedding model و chunking strategy، وعايز ترفع جودة النتائج خطوة جدية ما بتتحلش بـ embedding أكبر.
Reranking: الطبقة اللي ناقصة في معظم تطبيقات RAG العربية
لو فتحت dashboard الـ RAG عندك ولقيت 38% من الأسئلة بترجّع chunks فيها كلمات قريبة من السؤال بس الإجابة الصح فعلاً في chunk تاني نزل في المركز السابع، انت مش محتاج embedding model أكبر. انت محتاج طبقة Reranking.
المشكلة بالظبط
Vector search بيقيس التشابه الدلالي (semantic similarity)، مش الصلة بالإجابة (relevance). الفرق دقيق، وكل ما الـ corpus يكبر، الفرق ده بيكبر معاه. سؤال زي "إيه فرق commit عن merge في git" بيرجّع chunks فيها "git commit" و "git merge" بكثرة، حتى لو في chunk تاني فيه شرح الفرق فعلاً بكلمات تانية متستخدمش نفس الـ tokens.
المثال للمبتدئ: مكتبة بأمين ذكي وأمين أذكى
تخيّل مكتبة كبيرة. زائر بيسأل: "كتاب يعلّمني الطبخ المصري لمبتدئ". أمين المكتبة الأول (Vector Search) بيمشي على الرفوف، بيشوف كل كتاب فيه كلمة "طبخ" أو "مصري" أو "مبتدئ"، وبيجيبلك أول 10 كتب لقاهم. فيهم كتاب أكاديمي عن تاريخ المطبخ المصري، وفيهم كتاب وصفات بس متقدّم. الكتاب الصح موجود، بس في المركز السابع.
أمين المكتبة التاني (Reranker) بياخد الـ 10 كتب اللي جابهم الأول، يفتح كل واحد فيهم، يقرا عناوين الفصول، يقارن بدقة بسؤال الزائر، ويرتّبهم من جديد. الكتاب الصح بيطلع رقم 1.
التعريف العلمي الدقيق
الفرق ده بالظبط بين Bi-encoder و Cross-encoder. الـ Bi-encoder (اللي بتستخدمه في الـ embeddings) بيعمل embedding للسؤال لوحده وللـ chunk لوحده، ثم بيقيس cosine similarity بينهم. ده سريع جدًا لأنه بيحسب embeddings الـ corpus مرة واحدة وبيخزّنها في vector store. لكنه بيخسر التفاعل المباشر بين السؤال والـ chunk.
الـ Cross-encoder بياخد السؤال + الـ chunk مع بعض في input واحد للنموذج، والنموذج بيرجّع score دقيق للصلة بناءً على attention مشترك بين كل token في السؤال وكل token في الـ chunk. ده أبطأ من الـ Bi-encoder بحوالي 100x، لكنه أدق بفارق كبير. ده الأساس النظري في ورقة Nogueira and Cho 2019 "Passage Re-ranking with BERT" واللي بنى عليها كل reranker حديث.
الكود الفعلي: 12 سطر بيرفع الجودة بشكل ملموس
في المثال ده هنستخدم cohere-rerank-v3.5 اللي بيدعم العربي. تقدر تستبدله بـ BAAI/bge-reranker-v2-m3 (مفتوح المصدر) لو عايز تشغّله محليًا.
import cohere
co = cohere.Client(api_key="...")
def rag_with_rerank(question, candidate_chunks):
# السؤال جاي من vector search ومعاه top 50 chunk تقريبي
rerank = co.rerank(
model="rerank-v3.5",
query=question,
documents=candidate_chunks,
top_n=5,
)
# خد بس الـ 5 الأعلى score الحقيقي
return [candidate_chunks[r.index] for r in rerank.results]