البحث الهجين وإعادة الترتيب
استراتيجيات إعادة الترتيب
3 دقيقة للقراءة
إعادة الترتيب تأخذ نتائج الاسترجاع الأولية وتعيد ترتيبها باستخدام نماذج أكثر تطوراً لدقة محسنة.
لماذا إعادة الترتيب؟
الاسترجاع الأولي سريع لكن غير دقيق. إعادة الترتيب تضيف الدقة:
الاسترجاع الأولي (سريع، مركز على الاستدعاء)
├── استرجع أعلى 100 مرشح
└── باستخدام تضمينات bi-encoder
إعادة الترتيب (أبطأ، مركز على الدقة)
├── سجل كل مرشح مقابل الاستعلام
└── باستخدام cross-encoder أو LLM
└── أرجع أعلى 10
| المرحلة | السرعة | الجودة | الاستخدام |
|---|---|---|---|
| الاسترجاع | سريع (~10ms) | استدعاء جيد | شبكة واسعة |
| إعادة الترتيب | أبطأ (~100ms) | دقة عالية | اختر الأفضل |
إعادة الترتيب بـ Cross-Encoder
Cross-encoders تعالج أزواج الاستعلام-المستند معاً لتسجيل صلة أفضل:
from sentence_transformers import CrossEncoder
class CrossEncoderReranker:
def __init__(self, model_name: str = "cross-encoder/ms-marco-MiniLM-L-6-v2"):
self.model = CrossEncoder(model_name)
def rerank(
self,
query: str,
documents: list[str],
top_k: int = 5
) -> list[tuple[str, float]]:
"""
أعد ترتيب المستندات حسب الصلة بالاستعلام.
يرجع:
قائمة (مستند، درجة) مرتبة حسب الصلة
"""
# أنشئ أزواج استعلام-مستند
pairs = [(query, doc) for doc in documents]
# سجل جميع الأزواج
scores = self.model.predict(pairs)
# رتب حسب الدرجة تنازلياً
doc_scores = list(zip(documents, scores))
doc_scores.sort(key=lambda x: x[1], reverse=True)
return doc_scores[:top_k]
# الاستخدام
reranker = CrossEncoderReranker()
results = reranker.rerank(
query="كيف أتعامل مع مصادقة API؟",
documents=retrieved_docs,
top_k=5
)
نماذج cross-encoder الشائعة:
| النموذج | الحجم | السرعة | الجودة |
|---|---|---|---|
| ms-marco-MiniLM-L-6-v2 | 23M | سريع | جيدة |
| ms-marco-MiniLM-L-12-v2 | 33M | متوسط | أفضل |
| bge-reranker-large | 560M | بطيء | الأفضل |
Cohere Rerank
API إعادة ترتيب تجاري بجودة ممتازة:
import cohere
co = cohere.Client(api_key="your-key")
def cohere_rerank(
query: str,
documents: list[str],
top_k: int = 5
) -> list[dict]:
"""إعادة الترتيب باستخدام API Cohere."""
response = co.rerank(
model="rerank-english-v3.0",
query=query,
documents=documents,
top_n=top_k
)
return [
{
"document": documents[r.index],
"score": r.relevance_score,
"index": r.index
}
for r in response.results
]
# الاستخدام
results = cohere_rerank(
query="تنفيذ OAuth 2.0",
documents=retrieved_docs,
top_k=5
)
التفاعل المتأخر ColBERT
ColBERT يوفر إعادة ترتيب سريعة من خلال التفاعل المتأخر:
from colbert import Searcher
from colbert.infra import ColBERTConfig
class ColBERTReranker:
def __init__(self, index_path: str):
config = ColBERTConfig(
doc_maxlen=512,
query_maxlen=64
)
self.searcher = Searcher(index=index_path, config=config)
def rerank(self, query: str, doc_ids: list[int], top_k: int = 5):
"""إعادة الترتيب باستخدام التفاعل المتأخر ColBERT."""
scores = []
for doc_id in doc_ids:
score = self.searcher.score(query, doc_id)
scores.append((doc_id, score))
scores.sort(key=lambda x: x[1], reverse=True)
return scores[:top_k]
كيف يعمل ColBERT:
- حساب تضمينات الرموز للمستندات مسبقاً
- في وقت الاستعلام، حساب تضمينات رموز الاستعلام
- التفاعل المتأخر: MaxSim بين رموز الاستعلام والمستند
- أسرع بكثير من cross-encoder الكامل
إعادة الترتيب المعتمدة على LLM
استخدم LLMs لإعادة الترتيب بدون تدريب:
from openai import OpenAI
client = OpenAI()
def llm_rerank(
query: str,
documents: list[str],
top_k: int = 5
) -> list[tuple[str, int]]:
"""إعادة الترتيب باستخدام GPT-4."""
# تنسيق المستندات مع الفهارس
doc_list = "\n".join([f"[{i}] {doc[:500]}" for i, doc in enumerate(documents)])
prompt = f"""بالنظر للاستعلام والمستندات أدناه، رتب المستندات حسب الصلة.
أرجع فقط فهارس المستندات بترتيب الصلة، الأكثر صلة أولاً.
الاستعلام: {query}
المستندات:
{doc_list}
تنسيق الإرجاع: فهارس مفصولة بفواصل (مثال: "3, 1, 4, 2, 0")
الترتيب:"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0
)
# تحليل الاستجابة
indices = [int(i.strip()) for i in response.choices[0].message.content.split(",")]
return [(documents[i], idx) for idx, i in enumerate(indices[:top_k])]
خط أنابيب إعادة الترتيب
خط أنابيب استرجاع + إعادة ترتيب كامل:
class RAGPipelineWithReranking:
def __init__(self, retriever, reranker):
self.retriever = retriever
self.reranker = reranker
def search(self, query: str, retrieve_k: int = 20, final_k: int = 5):
"""
استرجاع من مرحلتين مع إعادة الترتيب.
المعاملات:
query: استعلام البحث
retrieve_k: العدد للاسترجاع أولياً
final_k: العدد للإرجاع بعد إعادة الترتيب
"""
# المرحلة 1: استرجاع سريع
candidates = self.retriever.search(query, k=retrieve_k)
# المرحلة 2: إعادة الترتيب
documents = [c["content"] for c in candidates]
reranked = self.reranker.rerank(query, documents, top_k=final_k)
return reranked
# الاستخدام
pipeline = RAGPipelineWithReranking(
retriever=HybridRetriever(documents),
reranker=CrossEncoderReranker()
)
results = pipeline.search("كيف أنفذ OAuth؟", retrieve_k=50, final_k=5)
اختيار معيد الترتيب
| معيد الترتيب | زمن الاستجابة | الجودة | التكلفة |
|---|---|---|---|
| Cross-encoder (صغير) | ~50ms | جيدة | مجاني |
| Cross-encoder (كبير) | ~200ms | أفضل | مجاني |
| Cohere Rerank | ~100ms | ممتازة | $$ |
| ColBERT | ~30ms | جيدة | مجاني |
| LLM (GPT-4) | ~500ms | ممتازة | $$$ |
البداية
│
▼
زمن الاستجابة حرج (<100ms)؟
│
├─ نعم → ColBERT أو cross-encoder صغير
│
▼
الجودة أولوية قصوى؟
│
├─ نعم → Cohere Rerank أو cross-encoder كبير
│
▼
ميزانية محدودة؟
│
├─ نعم → cross-encoder مفتوح المصدر
│
▼
الافتراضي → ms-marco-MiniLM-L-6-v2 (أفضل توازن)
نصيحة الأداء: استرجع 3-5x مرشحين أكثر مما تحتاج، ثم أعد الترتيب. النقطة المثلى عادة استرجاع 20-50 مرشح لأعلى 5 نتائج.
التالي، لنستكشف تقنيات تعزيز الاستعلام لتحسين الاسترجاع قبل أن يبدأ حتى. :::