البحث الهجين وإعادة الترتيب
استراتيجيات إعادة الترتيب
إعادة الترتيب تأخذ نتائج الاسترجاع الأولية وتعيد ترتيبها باستخدام نماذج أكثر تطوراً لدقة محسنة.
لماذا إعادة الترتيب؟
الاسترجاع الأولي سريع لكن غير دقيق. إعادة الترتيب تضيف الدقة:
الاسترجاع الأولي (سريع، مركز على الاستدعاء)
├── استرجع أعلى 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 نتائج.
التالي، لنستكشف تقنيات تعزيز الاستعلام لتحسين الاسترجاع قبل أن يبدأ حتى. :::