إعادة الترتيب (Reranking) في RAG هي مرحلة ثانية بعد الاسترجاع الأولي، تعيد فيها ترتيب أفضل 50-100 مقطع باستخدام cross-encoder يقرأ الاستعلام والمقطع معاً، وهي الطريقة الأرخص والأسرع تأثيراً لتحسين دقة RAG في 2026. عملياً، إضافة reranker جيد مثل Cohere Rerank 3.5 أو BGE-reranker-v2-m3 يرفع NDCG@10 بنسبة 15-35٪ على معظم المجموعات، ويكلف بضعة ميلي ثوانٍ إضافية إذا ضبطت الحدود جيداً. في هذا الدليل، أشارك ما تعلمته من نشر ثلاث خطوط أنابيب RAG إنتاجية، ومقارنة مباشرة بين النماذج التي تحتاج معرفتها الآن.
الاسترجاع bi-encoder سريع لكنه يشحن نتائج ذات صلة سطحية؛ الـ cross-encoder يقرأ الاستعلام والمقطع معاً ويعطي درجات دقيقة، وهو مطلوب لأي RAG جاد في الإنتاج.
Cohere Rerank 3.5 هو الخيار الأسهل للنشر السريع: p50 حوالي 220 مللي ثانية، وتكلفة $2 لكل 1000 استعلام (حتى 100 مستند لكل استعلام).
BGE-reranker-v2-m3 هو أفضل بديل مفتوح المصدر تحت رخصة Apache 2.0: يعمل على GPU واحد مثل A10G بزمن استجابة 80 مللي ثانية لدفعة من 50 زوجاً، مع دعم 100+ لغة.
ColBERTv2 يقدم توازناً بين السرعة والدقة عبر التفاعل المتأخر (late interaction) على مستوى الرمز، وهو مفيد للمجموعات الكبيرة جداً.
القاعدة الذهبية: أعد ترتيب 50-75 مرشحاً؛ ما بعد 100 تسطح المكاسب ويرتفع زمن الاستجابة والتكلفة خطياً.
لا يوجد "أفضل reranker" مطلق. الاختيار يعتمد على مجموعتك واللغات والميزانية. قِس على بياناتك أولاً باستخدام NDCG@10 وRecall@k.
لماذا تحتاج إعادة ترتيب أصلاً؟
في أول خط أنابيب RAG نشرته، ظننت أن embeddings ذكية بما يكفي. جلبت أفضل 5 مقاطع مباشرة من Qdrant ودفعتها للنموذج. النتيجة كانت مقبولة لـ 70٪ من الاستعلامات وسيئة تماماً للـ 30٪ الباقية، والـ 30٪ تلك كانت دائماً الاستعلامات المهمة: أسئلة طويلة، مصطلحات فنية، تسميات محددة.
المشكلة الجوهرية: bi-encoder (النموذج الذي أنشأ embeddings) يمثل كل مقطع كمتجه واحد ثابت بغض النظر عن الاستعلام. تشابه cosine بين متجهين لا يعرف إن كان المستخدم يسأل عن "كيفية إعداد pgvector" أم "كيفية تصحيح خطأ pgvector مع نافذة سياق". النتائج المسترجعة كلها تدور في نفس المنطقة الدلالية، لكن الأكثر صلة قد يكون في المركز الخامس أو الثامن.
هنا يدخل الـ reranker: نموذج يأخذ كل زوج (استعلام، مقطع) معاً ويقرأهما بانتباه متبادل، ثم يخرج درجة رقمية. هذه هي المرحلة التي تفرز الإشارة من الضوضاء. في مشروع دعم فني كنت أعمل عليه، إضافة Cohere Rerank رفعت NDCG@10 من 0.61 إلى 0.83 دون أن ألمس فهرس المتجهات — تحسين لا يمكن أن يحققه أي "ضبط للبرومبت". أفصّل الخط الكامل في دليل بناء أنظمة RAG الإنتاجية.
الفرق بين bi-encoder وcross-encoder
الفهم الواضح لهذا الفرق يوفر أياماً من التصحيح لاحقاً. إليك التمييز بالتحديد:
Bi-encoder (نماذج embeddings)
يشفر الاستعلام والمستند بشكل منفصل إلى متجهات ثابتة (مثلاً 1536 بُعداً).
التشابه = ضرب داخلي أو cosine بين المتجهين.
سريع جداً: يمكن تشفير مليون مستند مسبقاً وحفظها في قاعدة متجهية.
يعمل جيداً للاسترجاع الواسع، لكنه يفشل في التمييزات الدقيقة.
Cross-encoder (نماذج reranking)
يأخذ الاستعلام والمستند كسلسلة واحدة مدموجة ويعالجهما معاً.
يخرج درجة رقمية واحدة (احتمالية الصلة).
لا يمكن الحساب المسبق (يجب حساب كل زوج وقت الاستعلام).
دقة أعلى بكثير، لكن تكلفة حوسبة O(k) لكل استعلام حيث k = عدد المرشحين.
هذا هو السبب في أن كل خط أنابيب إنتاجي جدي يستخدم النمط الهجين: bi-encoder يجلب top-100 بسرعة، ثم cross-encoder يعيد ترتيبها إلى top-10. راجع صفحة BGE-reranker-v2-m3 على Hugging Face للمواصفات الرسمية.
Cohere Rerank 3.5: نقطة البداية المُدارة
إذا كنت تبدأ من الصفر بلا بنية GPU، ابدأ هنا. Cohere Rerank 3.5 هو أنضج API متاح لإعادة الترتيب في 2026، ويدعم أكثر من 100 لغة بما فيها العربية بجودة تفوق النماذج الأصغر مفتوحة المصدر.
الأرقام التي تهم
التكلفة: $2 لكل 1000 استعلام، حيث كل "استعلام" = استعلام واحد ضد ما يصل إلى 100 مستند.
زمن الاستجابة: p50 ≈ 220 مللي ثانية، p99 عادة تحت 500 مللي ثانية.
الحد الأقصى للسياق: 4096 رمزاً لكل مستند.
الترخيص: API مغلق. لا يمكن الاستضافة الذاتية.
نداء API بسيط
import cohere
co = cohere.ClientV2() # يقرأ COHERE_API_KEY من البيئة
query = "كيف أضبط pgvector لاسترجاع مليار متجه؟"
documents = [
"pgvector يستخدم HNSW أو IVFFlat لبناء الفهرس...",
"PostgreSQL 16 أضاف دعماً محسناً لـ...",
"لتوسيع pgvector إلى مليار متجه، فكر في...",
# ... حتى 100 مستند
]
response = co.rerank(
model="rerank-v3.5",
query=query,
documents=documents,
top_n=10,
)
for hit in response.results:
print(f"index={hit.index} score={hit.relevance_score:.4f}")
ملاحظة مهمة: Cohere Rerank يفشل نسبياً على الاستعلامات المليئة بالمعرِّفات (أسماء دوال، متغيرات، رموز غير طبيعية). إذا كان تطبيقك بحث في الكود، فكر في BGE أو Voyage بدلاً منه. راجع وثائق Cohere Rerank الرسمية للتفاصيل الحديثة.
BGE-reranker-v2-m3: البديل المفتوح المصدر
BGE-reranker-v2-m3 من BAAI هو أفضل reranker مفتوح المصدر متعدد اللغات في 2026. حوالي 568 مليون معلمة، مما يجعله قابلاً للتشغيل على GPU واحد بسعة 24 جيجابايت مثل A10G أو L4.
لماذا BGE؟
ترخيص Apache 2.0: استخدام تجاري مجاني بالكامل.
سرعة GPU: ~80 مللي ثانية لدفعة من 50 زوجاً على A10G. على CPU زمن الاستجابة يقفز إلى 350 مللي ثانية، وهو رفض عادة.
جودة مقاربة لـ Cohere: على معايير BEIR وMIRACL، الفجوة ضمن 2-3 نقاط NDCG.
خصوصية البيانات: لا شيء يترك بنيتك التحتية.
تحميل ونشر مع sentence-transformers
from sentence_transformers import CrossEncoder
import torch
# تحميل النموذج مرة واحدة عند بدء الخدمة
reranker = CrossEncoder(
"BAAI/bge-reranker-v2-m3",
device="cuda" if torch.cuda.is_available() else "cpu",
max_length=512,
)
query = "كيف أضبط pgvector لاسترجاع مليار متجه؟"
candidates = [
"pgvector يستخدم HNSW أو IVFFlat...",
"PostgreSQL 16 أضاف دعماً محسناً لـ...",
"لتوسيع pgvector إلى مليار متجه...",
]
# بناء أزواج (استعلام، مستند)
pairs = [[query, doc] for doc in candidates]
# التسجيل بالدفعات، activate_fn=None يعطي logits خام
scores = reranker.predict(pairs, batch_size=32, show_progress_bar=False)
# دمج مع الفهارس وترتيب
ranked = sorted(
zip(range(len(candidates)), scores),
key=lambda x: x[1],
reverse=True,
)
for idx, score in ranked[:10]:
print(f"index={idx} score={score:.4f}")
ColBERTv2 والتفاعل المتأخر
ColBERTv2 نموذج معماري مختلف تماماً. بدلاً من إنشاء متجه واحد لكل مستند، ينشئ متجهاً لكل رمز. عند وقت الاستعلام، يحسب أقصى تشابه بين كل رمز استعلام وأي رمز في المستند، ثم يجمع النتيجة (MaxSim). هذا هو "التفاعل المتأخر": التفاعل يحدث في وقت الاستعلام، لكن على مستوى الرمز بدلاً من المستند.
متى تختار ColBERT؟
مجموعة كبيرة جداً (10 ملايين+ مستند) حيث لا تحتمل زمن استجابة cross-encoder على top-100.
احتياج توازن بين سرعة bi-encoder ودقة cross-encoder.
تطبيقات البحث القانوني والبحث الأكاديمي حيث المطابقة على مستوى المصطلح مهمة.
العيوب
الفهارس أكبر بكثير، عادة 10x مقارنة بـ bi-encoder عادي.
البنية التحتية أعقد: يحتاج لخدمة مثل PLAID للاسترجاع الكفء.
Cohere/BGE يتفوقان عليه في NDCG على البيانات القياسية.
عملياً، لا أنصح بـ ColBERT للفرق الجديدة. عائد الاستثمار في التعقيد ليس هناك مقارنة بـ BGE أو Cohere. لكن لو كنت تدير 50 مليون مستند وتحتاج زمن استجابة أقل من 50 مللي ثانية، فهو الأداة الصحيحة.
النماذج الصاعدة: Voyage 2.5 وQwen3 وZerank
ظهرت ثلاثة نماذج جديدة في 2026 تستحق الانتباه، خاصة إن كنت تختار للمرة الأولى:
Voyage rerank-2.5 / rerank-2.5-lite
Voyage تقدم أفضل توازن بين الجودة والسرعة في الإنجليزية، مع متغيرات مضبوطة للكود والقانون والمالية. الإصدار "lite" يقطع زمن الاستجابة إلى النصف بخسارة صغيرة في الجودة على استعلامات خارج المجال. لتطبيقات إنجليزية بميزانية زمن استجابة صارمة، هو الاختيار الأفضل عادة.
Qwen3-Reranker-4B
أفضل نموذج مفتوح المصدر بدأ في الظهور في اختبارات 2026. Apache 2.0، دعم 100+ لغة، وسياق 32 ألف رمز (أوسع بكثير من BGE). حجم 4 مليار معلمة يعني احتياج GPU أقوى (A100 أو H100)، لكن الجودة تنافس Cohere على معظم المعايير.
ZeRank-1 / ZeRank-2
ZeroEntropy تقدم نماذج بـ 60٪ تكلفة أقل من Cohere مع الحفاظ على ~95٪ من الدقة. الميزة الفريدة: درجات معايرة (calibrated scores). يمكنك استخدام حد بسيط مثل score > 0.7 فوراً دون أسابيع من ضبط العتبات كما تحتاج BGE. هذا مهم جداً لأنظمة اتخاذ القرار التي تحتاج ثقة كمية.
مصفوفة المقارنة الكاملة
الميزة
Cohere Rerank 3.5
BGE-reranker-v2-m3
ColBERTv2
Voyage rerank-2.5
Qwen3-Reranker-4B
الترخيص
API مغلق
Apache 2.0
MIT
API مغلق
Apache 2.0
التكلفة
$2 / 1000 استعلام
بنية GPU فقط
بنية GPU فقط
سعر مقارب لـ Cohere
بنية GPU (H100)
زمن الاستجابة p50
~220ms
~80ms (A10G)
~15ms
~150ms (lite: ~75ms)
~120ms (H100)
الحد الأقصى للسياق
4096 رمز
8192 رمز
512 رمز
4096 رمز
32768 رمز
دعم اللغات
100+ لغة
100+ لغة
محدود (إنجليزي أساساً)
إنجليزي قوي، محدود غيره
100+ لغة
دعم العربية
ممتاز
جيد جداً
ضعيف
ضعيف
جيد جداً
درجات معايرة
لا
لا
لا
لا
لا
الأفضل لـ
البداية السريعة، المتعدد اللغات
الاستضافة الذاتية، الخصوصية
مجموعات ضخمة (10M+)
إنجليزي، ميزانية زمن استجابة صارمة
سياق طويل، مفتوح المصدر
مثال Python كامل: خط أنابيب هجين مع reranker
هذا مثال قابل للتشغيل يجمع BM25 (استرجاع كلمي) مع embeddings كثيفة (استرجاع دلالي) عبر Reciprocal Rank Fusion، ثم يعيد الترتيب بـ BGE. هذا هو النمط الذي أستخدمه في الإنتاج.
from rank_bm25 import BM25Okapi
from sentence_transformers import SentenceTransformer, CrossEncoder
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct, Distance, VectorParams
import numpy as np
# ---------- الإعداد لمرة واحدة ----------
embedder = SentenceTransformer("BAAI/bge-m3")
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3", device="cuda")
qdrant = QdrantClient(":memory:")
corpus = [
"pgvector يدعم HNSW وIVFFlat كخوارزميات فهرسة.",
"PostgreSQL 16 يقدم تحسينات كبيرة لأداء الاستعلامات المتجهية.",
"لتوسيع pgvector لمليار متجه، استخدم partitioning + HNSW.",
"Qdrant يفوق pgvector في الأداء عند 100 مليون+ متجه.",
# ... مقاطعك الحقيقية
]
# فهرسة BM25
tokenized = [doc.split() for doc in corpus]
bm25 = BM25Okapi(tokenized)
# فهرسة المتجهات في Qdrant
qdrant.recreate_collection(
collection_name="docs",
vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
)
embeddings = embedder.encode(corpus, normalize_embeddings=True)
qdrant.upsert(
collection_name="docs",
points=[
PointStruct(id=i, vector=vec.tolist(), payload={"text": corpus[i]})
for i, vec in enumerate(embeddings)
],
)
# ---------- وقت الاستعلام ----------
def hybrid_search(query: str, top_k_retrieval: int = 50, top_k_final: int = 10):
# 1) استرجاع BM25
bm25_scores = bm25.get_scores(query.split())
bm25_top = np.argsort(bm25_scores)[::-1][:top_k_retrieval]
# 2) استرجاع كثيف
query_vec = embedder.encode(query, normalize_embeddings=True)
dense_hits = qdrant.search(
collection_name="docs",
query_vector=query_vec.tolist(),
limit=top_k_retrieval,
)
dense_top = [hit.id for hit in dense_hits]
# 3) دمج RRF (Reciprocal Rank Fusion)
k = 60
scores = {}
for rank, doc_id in enumerate(bm25_top):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
for rank, doc_id in enumerate(dense_top):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
fused = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:top_k_retrieval]
candidate_ids = [doc_id for doc_id, _ in fused]
candidate_texts = [corpus[i] for i in candidate_ids]
# 4) إعادة الترتيب بـ cross-encoder
pairs = [[query, text] for text in candidate_texts]
rerank_scores = reranker.predict(pairs, batch_size=32)
reranked = sorted(
zip(candidate_ids, candidate_texts, rerank_scores),
key=lambda x: x[2],
reverse=True,
)[:top_k_final]
return reranked
results = hybrid_search("كيف أضبط pgvector لاسترجاع مليار متجه؟")
for doc_id, text, score in results:
print(f"[{score:.3f}] {text}")
لاحظ أن الاستدعاء الفعلي لـ reranker.predict يستقبل دفعة كاملة من الأزواج مرة واحدة، هذا حاسم للسرعة. لا تكرر الاستدعاء زوجاً زوجاً في حلقة Python.
كيف تُقيم reranker على بياناتك؟
لا تصدق أي معيار عام قبل أن تشغل تقييماً على بياناتك. الفكرة الأساسية بسيطة: ابنِ مجموعة تقييم صغيرة (100-300 استعلام) مع مقاطع "الجواب الصحيح" لكل استعلام، ثم قِس المقاييس التالية:
NDCG@10: Normalized Discounted Cumulative Gain: يقيس جودة الترتيب مع تخفيض للنتائج البعيدة. الأكثر شمولاً.
Recall@k: ما نسبة الأجوبة الصحيحة الظاهرة في أول k نتيجة؟
MRR (Mean Reciprocal Rank): متوسط 1/رتبة أول جواب صحيح.
Hit Rate@1: هل الجواب الأول هو الأفضل فعلاً؟ مهم لتطبيقات الدردشة.
سير عمل تقييم مقتضب
from typing import List, Dict
import numpy as np
def dcg_at_k(relevances: List[int], k: int) -> float:
r = np.array(relevances[:k])
if r.size == 0:
return 0.0
return float(np.sum(r / np.log2(np.arange(2, r.size + 2))))
def ndcg_at_k(relevances: List[int], k: int) -> float:
ideal = sorted(relevances, reverse=True)
idcg = dcg_at_k(ideal, k)
if idcg == 0:
return 0.0
return dcg_at_k(relevances, k) / idcg
# eval_set: قائمة من {"query": str, "gold_ids": set[int]}
def evaluate(rerank_fn, eval_set: List[Dict], k: int = 10) -> Dict[str, float]:
ndcgs, recalls, rr = [], [], []
for item in eval_set:
ranked = rerank_fn(item["query"]) # يعيد قائمة معرِّفات مرتبة
relevances = [1 if doc_id in item["gold_ids"] else 0 for doc_id in ranked[:k]]
ndcgs.append(ndcg_at_k(relevances, k))
recalls.append(sum(relevances) / max(len(item["gold_ids"]), 1))
# MRR
found = [i for i, r in enumerate(relevances, 1) if r == 1]
rr.append(1 / found[0] if found else 0)
return {
"ndcg@10": float(np.mean(ndcgs)),
"recall@10": float(np.mean(recalls)),
"mrr": float(np.mean(rr)),
}
شغّل هذا مرتين: مرة بدون reranker (أول 10 من الاسترجاع الهجين مباشرة)، ومرة مع كل reranker مرشح. الفرق في NDCG@10 هو ما يهم — لو كان أقل من 5٪، احتفظ بمالك ولا تضف reranker. للمزيد عن التقييم الأشمل للأنظمة الذكية، راجع دليل تقييم وكلاء الذكاء الاصطناعي.
أنماط الإنتاج: زمن الاستجابة والتكلفة والمراقبة
بعد سنة من تشغيل RAG في الإنتاج، هذه هي القرارات التي تصنع الفرق:
حجم دفعة إعادة الترتيب
الرقم السحري في 2026 هو 50-75 مرشح. أقل من 25 وأنت تترك جودة على الطاولة؛ أكثر من 100 وأنت تدفع زمن استجابة وتكلفة بلا مكاسب ملموسة. الاختبار السريع: قِس NDCG@10 على top-25، top-50، top-100. المنحنى يسطح تقريباً دائماً حول 50.
تخزين مؤقت للاستعلامات المتكررة
في تطبيق دعم فني، لاحظت أن 20٪ من الاستعلامات تشكل 60٪ من الحركة. تخزين مؤقت لنتائج reranker (المفتاح = hash الاستعلام + قائمة معرِّفات المرشحين) خفض تكاليف Cohere بنسبة 55٪. تفاصيل النمط في دليل التخزين المؤقت للـ Prompts.
خط أنابيب متعدد المراحل للاستعلامات المعقدة
للاستعلامات الأصعب (بحث قانوني، بحث فني عميق)، يعمل نمط ثلاث مراحل بشكل جيد:
استرجاع هجين بـ k=200 (BM25 + كثيف + RRF).
Cross-encoder rerank (BGE) لتخفيض إلى top-25.
LLM listwise rerank (Claude Haiku أو GPT-4o mini) لاختيار top-6 مع تفسير.
مراقبة زمن الاستجابة
راقب p50، p95، p99 بشكل منفصل لكل مرحلة (استرجاع، rerank، توليد). p99 للـ rerank هو ما يقتل تجربة المستخدم عادة — دفعة كبيرة عرضية أو GPU متأخر. راجع دليل مراقبة LLM في الإنتاج لإعداد لوحات OpenTelemetry مناسبة.
الأسئلة الشائعة
هل أحتاج إعادة ترتيب في تطبيقات RAG الصغيرة؟
إذا كان لديك أقل من 1000 مستند وتستعمل embeddings حديثة (bge-m3, text-embedding-3-large)، قد لا تحتاج reranker. جرّب أولاً بدونه. أضِفه فقط حين يظهر التقييم أن NDCG@10 أقل من 0.75 على مجموعة اختبارك.
كم من الوقت تضيف إعادة الترتيب لزمن الاستجابة؟
يعتمد على النموذج والبنية. Cohere API يضيف حوالي 220 مللي ثانية، BGE على A10G حوالي 80 مللي ثانية لدفعة من 50 مستنداً. هذا مقبول لمعظم تطبيقات الدردشة (اجمالي p50 < 2 ثانية) لكنه قد يكون كثيراً لتطبيقات البحث الفوري.
أيهما أفضل: BGE أم Cohere للعربية؟
Cohere Rerank 3.5 يتفوق قليلاً على BGE-reranker-v2-m3 في الاستعلامات العربية على معايير MIRACL، فجوة حوالي 3-5 نقاط NDCG. لكن BGE يوفر لك خصوصية بيانات كاملة وتكلفة أقل عند الحجم الكبير. اختبر على بياناتك قبل الالتزام.
هل يمكن ضبط reranker على بياناتي الخاصة؟
نعم لـ BGE وQwen3 المفتوحين. تحتاج مجموعة من الأزواج (استعلام، مستند صحيح، مستند خاطئ) بحجم 2000-10000 مثال. الأداء يمكن أن يرتفع 10-15٪ NDCG على مجالك. Cohere يقدم fine-tuning مُدار أيضاً لكن بسعر أعلى. راجع دليل الضبط الدقيق للنماذج للمبادئ العامة.
هل ColBERT ميت في 2026؟
لا، لكنه أصبح تخصصياً. لمجموعات فوق 10 ملايين مستند حيث cross-encoder على top-100 غالٍ جداً، ColBERTv2 مع PLAID لا يزال الخيار الأفضل. للتطبيقات العادية، BGE أو Cohere يفوزان في نسبة الجودة/التعقيد.
هل استخدام LLM كـ reranker (LLM-as-reranker) فكرة جيدة؟
مفيد كمرحلة ثالثة بعد cross-encoder، خاصة مع نموذج صغير وسريع مثل Claude Haiku 4.5. لا تستخدمه كبديل عن cross-encoder، فالتكلفة وزمن الاستجابة يقتلانك عند k=50. الاستخدام الصحيح: تخفيض من 25 إلى 6 مع تفسير.
دليل عملي للدفاع ضد حقن التعليمات في تطبيقات LLM لعام 2026: سبع طبقات دفاع في العمق، كشف بـ Prompt Guard 2، سياسات NeMo Guardrails، وحماية وكلاء استدعاء الأدوات مع أمثلة كود قابلة للاستخدام مباشرة.
دليل عملي لتقليل تكاليف API بنسبة 90% عبر Prompt Caching في Claude وOpenAI في 2026. يشرح الفروقات بين النهج الصريح لـ Anthropic والتلقائي لـ OpenAI، مع أمثلة كود، جدول مقارنة تفصيلي، وأخطاء شائعة تُبطل التخزين المؤقت بصمت.
دليل عملي لبناء طبقة مراقبة LLM في الإنتاج باستخدام OpenTelemetry GenAI Semantic Conventions: مخطط لوحة التحكم، تنبيهات SLO، أخذ العينات، إخفاء PII، ومثال Python كامل يوصل OpenAI بـLangfuse.