المستوى: متوسط — لازم تكون فاهم Embeddings و RAG basics قبل ما تكمل. لو لسه مبتدئ، ابدأ بمقال Embeddings و RAG في نفس القسم وارجع للمقال ده بعدها.
لو الـ RAG عندك بيرجّع 8 chunks من قاعدة الـ vectors، والإجابة الصح موجودة في chunk رقم 6، فالـ LLM غالبًا هيتجاهلها ويرد من الـ chunks اللي فوقها. المشكلة مش في الـ embeddings بتاعتك، المشكلة إنك واقف عند مرحلة retrieval بدون مرحلة تانية اسمها Reranking.
المشكلة باختصار
الـ embeddings models زي text-embedding-3-large أو bge-m3 بتشتغل بأسلوب bi-encoder: بتحوّل الـ query والـ document لـ vectors بشكل منفصل، وبتقيس الشبه بـ cosine similarity. ده سريع جدًا لكن مش دقيق بشكل كافٍ في الإنتاج.
تخيّل أمين مكتبة بيدوّر على كتاب معين. لو فتح الفهرس العام بسرعة وقالك «الكتب اللي عناوينها قريبة من سؤالك موجودة في الرفوف 12 لـ 18»، انت كده عندك 50 كتاب مرشح. لكن لسه محدش قرا الفصل الجواني. مرحلة الـ Reranking هي اللي بيقعد فيها أمين المكتبة يفتح كل كتاب من الـ 50 ويقرا أول صفحتين علشان يقولك «الإجابة بالظبط في الكتاب رقم 6». الفرق بين سرعة وحجم البحث، وبين دقة الترتيب النهائي.
الفكرة العملية: ضيف stage تاني بياخد top 50 chunk من الـ vector search، ويعيد ترتيبهم باستخدام cross-encoder بيقرا الـ query والـ chunk سوا في نفس forward pass.
الفرق العلمي بين bi-encoder و cross-encoder
في الـ bi-encoder الموديل بيشوف الـ query لوحده والـ document لوحده. كل واحد بيتحوّل لـ vector مستقل (768 أو 1024 بُعد عادةً)، وبعدين بيتقاس الشبه بدالة بسيطة هي cosine similarity بين الـ vectorين. الميزة الكبيرة: تقدر تحسب الـ document embeddings مرة واحدة وتخزّنها في ANN index زي pgvector أو Qdrant، وتعمل البحث في 30ms حتى لو عندك مليون chunk.
الـ cross-encoder بشكل تاني خالص. بيدخّل الـ query والـ document مع بعض في نفس forward pass: [CLS] query [SEP] document [SEP]. الموديل بيقرا الاتنين كنص واحد ويطلع score من 0 لـ 1 يقيس الـ relevance المباشرة. ده أدق بـ 25-40% على benchmark الـ MS MARCO حسب ورقة Nogueira & Cho (arXiv:1901.04086)، لكن بطيء بطبيعته: مينفعش تحسب score مع كل document في قاعدتك. لو عندك مليون document، 1M forward pass على cross-encoder ≈ 50 دقيقة لكل query واحد. مستحيل في الإنتاج.
الحل المعتمد: pipeline من stage-اتنين. الأول bi-encoder يجيب أعلى 50 chunk مرشح في 30ms. التاني cross-encoder يعيد ترتيب الـ 50 دول بس في 200ms. هيكل two-stage retrieval ده هو نفس اللي بتشتغل بيه Google search و Bing من سنين.
كود Python شغّال — Cohere Rerank + Anthropic SDK
import cohere
from anthropic import Anthropic
co = cohere.Client("CO_API_KEY")
client = Anthropic()
def retrieve_with_rerank(query: str, candidate_chunks: list[str], top_k: int = 5):
# Stage 1: bi-encoder retrieval (افترض إن candidate_chunks جاي من pgvector ANN)
# candidate_chunks المفروض top 50 من الـ vector search
# Stage 2: cross-encoder reranking
response = co.rerank(
model="rerank-multilingual-v3.0",
query=query,
documents=candidate_chunks,
top_n=top_k,
)
reranked = [candidate_chunks[r.index] for r in response.results]
return reranked
# استخدم النتيجة في prompt لـ Claude
top_chunks = retrieve_with_rerank(user_query, candidates_from_pgvector)
context = "\n\n".join(top_chunks)
msg = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[
{"role": "user", "content": f"<context>{context}</context>\n\n{user_query}"}
],
)
print(msg.content[0].text)