בעת יישום מערכות RAG, דילמה נפוצה היא כיצד למצוא מסמך גם לפי משמעות וגם לפי מספר מדויק. חיפוש היברידי—שילוב של אחזור וקטורי (dense) וטקסט מלא (sparse/BM25) ואחריו מיזוג תוצאות—פותר בעיה זו. בפועל, חיפוש היברידי משיג ביצועים טובים יותר באופן עקבי מאשר כל אחת מהשיטות בנפרד על רוב מערכי הנתונים הארגוניים. לדוגמה, בפרויקט אחד חיפוש היברידי (RRF) שיפר את MRR@5 ב-12% יחסית לחיפוש וקטורי טהור תוך שמירה על דיוק גבוה למונחים מדויקים. אנו מיישמים פתרונות כאלה במפתח פתוח, עם אחריות לאיכות אחזור על הנתונים שלך. עלות פרויקט נעה בדרך כלל בין $5,000 ל-$10,000 בהתאם לגודל מערך הנתונים ומורכבותו. בקש ייעוץ על יישום חיפוש היברידי וקבל הערכת פרויקט.
מדוע חיפוש וקטורי בלבד אינו מספיק
ייצוגים וקטוריים (embeddings) צפופים ממצעים את הסמנטיקה—זה גם חוזק וגם חולשה. שאילתה כמו "חוזה מס' DA-2023-451" תהיה בעלת דמיון קוסינוס גבוה לחוזים בכלל, אך לא למסמך הספציפי לפי מספר. BM25 מוצא את המחרוזת המדויקת "DA-2023-451" באופן מיידי.
- חיפוש וקטורי מתפקד גרוע עבור: מספרים מדויקים (חוזה, SKU, מספר סידורי), קיצורים וראשי תיבות ספציפיים, מונחים טכניים נדירים, שאילתות לחיפוש ציטוט מדויק.
- BM25 מתפקד גרוע עבור: שאילתות מנוסחות מחדש (מילים נרדפות), מושגים דומים סמנטית עם מילים שונות, שאילתות חוצות שפות, תיאורים מעורפלים ("משהו על תשלום לאחר משלוח").
מדוע חיפוש היברידי עדיף על פני dense או BM25 בלבד
שילוב שתי הגישות יוצר סינרגיה: dense מכסה סמנטיקה, BM25 מכסה התאמות מדויקות. המקרה האמיתי שלהלן מראה שחיפוש היברידי RRF (ללא reranker) עולה על dense+reranker ב-MRR@5 (0.83 לעומת 0.80) וב-NDCG@5 (0.81 לעומת 0.77). בינתיים, hybrid+reranker משיג 0.89/0.87. במילים אחרות, יישום חיפוש היברידי משיג איזון בין דמיון סמנטי להתאמת מילות מפתח מדויקת. לפי בדיקת A/B שלנו על 400 שאילתות, חיפוש היברידי RRF עולה על dense+reranker בפקטור של 1.04 ב-MRR@5. עבור משימות רבות, זה מבטל את הצורך ב-reranker יקר.
אלגוריתמי מיזוג
Reciprocal Rank Fusion (RRF)—השיטה החזקה ביותר. RRF היא שיטת מיזוג שהוצעה על ידי Cormack et al. (2009)—ראה עוד בויקיפדיה.
קוד RRF
from collections import defaultdict
def reciprocal_rank_fusion(
dense_results: list[tuple], # [(doc_id, score), ...]
sparse_results: list[tuple],
k: int = 60 # RRF константа (обычно 60)
) -> list[tuple]:
"""
RRF score = sum(1 / (k + rank_i)) по всем спискам
k=60 стандартное значение (Cormack et al.)
"""
scores = defaultdict(float)
for rank, (doc_id, _) in enumerate(dense_results, 1):
scores[doc_id] += 1 / (k + rank)
for rank, (doc_id, _) in enumerate(sparse_results, 1):
scores[doc_id] += 1 / (k + rank)
return sorted(scores.items(), key=lambda x: -x[1])Relative Score Fusion (RSF)—שילוב מנורמל:
קוד RSF
def relative_score_fusion(
dense_results: list[tuple],
sparse_results: list[tuple],
alpha: float = 0.5 # Вес dense
) -> list[tuple]:
"""Нормализует оценки в [0,1] и взвешивает"""
scores = defaultdict(float)
# Нормализация dense
if dense_results:
max_d = max(s for _, s in dense_results)
min_d = min(s for _, s in dense_results)
for doc_id, score in dense_results:
norm = (score - min_d) / (max_d - min_d + 1e-8)
scores[doc_id] += alpha * norm
# Нормализация sparse
if sparse_results:
max_s = max(s for _, s in sparse_results)
min_s = min(s for _, s in sparse_results)
for doc_id, score in sparse_results:
norm = (score - min_s) / (max_s - min_s + 1e-8)
scores[doc_id] += (1 - alpha) * norm
return sorted(scores.items(), key=lambda x: -x[1]) השוואת אלגוריתמי מיזוג
| פרמטר | RRF | RSF |
|---|---|---|
| עיקרון | סכום של דרגות הפוכות | סכום משוקלל של ציונים מנורמלים |
| רגישות לקני מידה | נמוכה (משתמש רק בדרגה) | גבוהה (דורש נורמליזציה) |
| כוונון | פרמטר אחד k | פרמטר alpha |
| חוסן | גבוה | בינוני (תלוי ב-alpha) |
| k/alpha מומלץ | k=60 (אמפירי) | alpha=0.5 (ברירת מחדל) |
SPLADE: מקודד ספארס מתקדם
SPLADE (Sparse Lexical and Expansion Model) מייצר וקטורים ספארסיים עם הרחבה לקסיקלית—המודל לומד "להרחיב" את השאילתה עם מילים נרדפות ומונחים קשורים. לפי מדד BEIR, SPLADE עולה על BM25 פי 1.2–1.5 ב-NDCG@10.
from fastembed import SparseTextEmbedding
sparse_model = SparseTextEmbedding(
model_name="prithivida/Splade_PP_en_v1"
)
def encode_sparse(text: str) -> dict:
"""Возвращает sparse вектор {token_id: weight}"""
output = list(sparse_model.embed([text]))[0]
return {
"indices": output.indices.tolist(),
"values": output.values.tolist(),
}SPLADE עולה על BM25 ברוב מדדי BEIR. עבור רוסית, אנו ממליצים על המודל from collections import defaultdict def reciprocal_rank_fusion( dense_results: list[tuple], # [(doc_id, score), ...] sparse_results: list[tuple], k: int = 60 # RRF константа (обычно 60) ) -> list[tuple]: """ RRF score = sum(1 / (k + rank_i)) по всем спискам k=60 стандартное значение (Cormack et al.) """ scores = defaultdict(float) for rank, (doc_id, _) in enumerate(dense_results, 1): scores[doc_id] += 1 / (k + rank) for rank, (doc_id, _) in enumerate(sparse_results, 1): scores[doc_id] += 1 / (k + rank) return sorted(scores.items(), key=lambda x: -x[1]) או גרסאות רב-לשוניות.
יישום עם Qdrant (דוגמה מעשית)
from qdrant_client import QdrantClient
from qdrant_client.models import (
SparseVector,
Prefetch,
FusionQuery,
Fusion,
NamedVector,
NamedSparseVector,
)
from fastembed import TextEmbedding, SparseTextEmbedding
dense_model = TextEmbedding("BAAI/bge-m3") # Multilingual dense
sparse_model = SparseTextEmbedding("prithivida/Splade_PP_en_v1")
client = QdrantClient(url="http://localhost:6333")
def hybrid_search(query: str, top_k: int = 5) -> list[dict]:
# Dense embedding
dense_vec = list(dense_model.embed([query]))[0].tolist()
# Sparse embedding
sparse_output = list(sparse_model.embed([query]))[0]
sparse_vec = SparseVector(
indices=sparse_output.indices.tolist(),
values=sparse_output.values.tolist(),
)
results = client.query_points(
collection_name="hybrid_docs",
prefetch=[
Prefetch(query=dense_vec, using="dense", limit=50),
Prefetch(query=sparse_vec, using="sparse", limit=50),
],
query=FusionQuery(fusion=Fusion.RRF),
limit=top_k,
with_payload=True,
)
return [
{"text": r.payload["text"], "source": r.payload["source"], "score": r.score}
for r in results.points
]
מקרה מעשי: השפעת alpha על איכות האחזור
מהניסיון שלנו: בפרויקט עם 12,000 מסמכים במאגר ידע ארגוני (חוזים, תקנות, שאלות נפוצות), בדקנו 400 שאילתות מסוגים שונים. תוצאות:
| תצורה | MRR@5 | NDCG@5 | דיוק מונחים מדויקים |
|---|---|---|---|
| dense בלבד (BGE-M3) | 0.74 | 0.71 | 0.58 |
| BM25 בלבד | 0.67 | 0.63 | 0.91 |
| חיפוש היברידי RRF (k=60) | 0.83 | 0.81 | 0.84 |
| חיפוש היברידי RSF (α=0.6) | 0.81 | 0.79 | 0.81 |
| dense + Reranker | 0.80 | 0.77 | 0.61 |
| חיפוש היברידי + Reranker | 0.89 | 0.87 | 0.86 |
חיפוש היברידי RRF ללא reranker כבר עולה על dense+reranker. השילוב של חיפוש היברידי+reranker מניב את התוצאה הטובה ביותר. לשם השוואה, שימוש ב-SPLADE כמקודד ספארס נותן שיפור MRR@5 של כ-0.03–0.05 לעומת BM25 עם אותה שיטת מיזוג.
כיצד לכוונן מיזוג RRF על מערך הנתונים שלך?
k אופטימלי עבור RRF: k=60 הוא ערך חזק אמפירית. k קטן מדי (10–20) נותן משקל גדול למיקומים הראשונים. גדול מדי (100+) מאזן את ההבדלים בין מיקומים. על נתונים אמיתיים, בדוק k∈{20, 40, 60, 80} על קבוצת אימות. עבור RSF, כוונן alpha מ-0.3 עד 0.7 בצעדים של 0.1.
תהליך יישום חיפוש היברידי שלב אחר שלב
- ביקורת על תכנית האחזור הנוכחית: ניתוח ה-embeddings שבשימוש, מחסן הווקטורים (vector DB), ומדדי איכות.
- בחירה והגדרה של מקודד ספארס: התקנת SPLADE או מקודד ספארס אחר המתאים לשפה ולתחום שלך.
- שילוב חיפוש כפול: הגדרת אינדוקס של וקטורים צפופים וספארסיים ב-Qdrant/Pinecone/Weaviate.
- יישום מיזוג: פריסת RRF או RSF עם פרמטרים ראשוניים (k=60, alpha=0.5).
- בדיקה ואופטימיזציה: הרצת השאילתות שלך, כוונון פרמטרים באמצעות מדדי MRR/NDCG.
- תיעוד ומסירה: תיאור התהליך, הדרכת הצוות, מסירת קוד וקבצי הגדרות.
מה כלול בפרויקט
- קוד אינטגרציה לחיפוש היברידי לתוך מערכת ה-RAG שלך.
- קבצי הגדרות עבור Qdrant/Pinecone.
- תיעוד על התקנה ותפעול.
- הדרכת צוות (סמינר מקוון של שעתיים).
- אחריות לאיכות אחזור (מדדים נמדדים לפני/אחרי).
- תמיכה חודש לאחר סיום הפרויקט.
צור קשר לקבלת הערכת פרויקט. קבל ייעוץ על יישום חיפוש היברידי ושפר את איכות האחזור של מערכת ה-RAG שלך.







