Hybrid Search עם Elasticsearch: שילוב BM25 ו-Vector kNN עבור RAG
תארו לעצמכם: Elasticsearch שלכם מטפל במאות אלפי מסמכים, אבל משתמשים מתלוננים שהחיפוש לא מוצא תשובות רלוונטיות. BM25 עובד מצוין עבור התאמות מדויקות אבל נכשל עם מילים נרדפות, טאוטולוגיות ושאילתות מורכבות בשפה הרוסית. התוצאה: לקוחות עוזבים, מפעילים מבזבזים זמן בחיפוש. הוספת מסד נתונים וקטורי נפרד? זה מגדיל עלויות תשתית, זמן אחזור רשת, ועוד מערכת לתמוך בה. הפתרון האופטימלי הוא להשתמש ב-kNN המובנה ב-Elasticsearch 8.x, המשלב חיפוש טקסט מלא וחיפוש וקטורי באינדקס אחד. עזרנו למספר צוותים ליישם hybrid כזה מבלי לשנות את התשתית שלהם, ועכשיו נראה לכם איך לעשות זאת. צרו קשר לבדיקת החיפוש הנוכחי שלכם — נציע את הדרך האופטימלית.
למה Elasticsearch kNN הוא הפתרון האופטימלי עבור Hybrid Search
נקודות כאב טיפוסיות ביישום RAG ללא בנייה מחדש: חיפוש מקוטע (BM25 מפספס סמנטיקה), כאוס תשתיתי (הקמת Pinecone או Weaviate לצד ES מגדילה עלות ומורכבות), זמן אחזור (מסדי נתונים וקטוריים חיצוניים מוסיפים עיכובי רשת p99 של 50+ אלפיות שנייה). Elasticsearch kNN פותר את כל השלוש: חיפוש היברידי (kNN + BM25) באמצעות מיזוג RRF בשאילתה אחת, תקורה מינימלית, אין צורך בשרתים חדשים.
Elasticsearch היא טכנולוגיה בוגרת עם 15+ שנים בשוק, בשימוש באלפי סביבות ייצור. תמיכה מובנית בשפה הרוסית באמצעות מנתח Snowball מספקת גזירה באיכות גבוהה: שאילתה עבור "договором" תמצא "договор", "договоры", "договорам". זה קריטי עבור חלק ה-BM25 של ה-hybrid. בנוסף, מחסנית ELK (Logstash, Kibana) מאפשרת לנטר אינדקסים ולהמחיש מדדי חיפוש ללא כלים נוספים.
אילו הגדרות HNSW נותנות את האיזון הטוב ביותר בין מהירות ואיכות?
לייצור, אנו ממליצים על HNSW עם פרמטרים m=16, ef_construction=100. זהו האיזון האופטימלי בין מהירות אינדוקס לדיוק חיפוש. num_candidates נמוך מדי (מתחת ל-100) מפחית recall; גבוה מדי מגדיל זמן אחזור. בפרויקטים שלנו אנו משתמשים בדמיון קוסינוס כמטריקת מרחק עבור embeddings.
איך אנחנו עושים זאת: מחסנית, תצורות, מקרה בוחן
מחסנית: Elasticsearch 8.11+, OpenAI text-embedding-3-small (1536-dim), Python 3.11, לקוח elasticsearch-py הרשמי.
מקרה בוחן מהפרקטיקה שלנו: העברת Elasticsearch קיים ל-RAG
הקשר: הלקוח שלנו — חברה עם 500K מסמכים משפטיים ב-Elasticsearch 8.x. משימה: הוספת שכבת RAG מבלי לשנות תשתית.
שלבים:
- הוספת שדה embedding (dense_vector, dims=1536) למיפוי הקיים.
- וקטוריזציה אצווה של מסמכים קיימים (יומיים, 500K × $0.02/1M = $10).
- Reindex עם השדה החדש (6 שעות).
- הוספת מיזוג RRF לשאילתות חיפוש.
- שכבת RAG על גבי שליפת ES.
תוצאות (לעומת BM25 טהור):
- NDCG@5: 0.64 → 0.81 (שיפור של 27%)
- Recall@10: 0.71 → 0.88
- Latency P95: 85ms → 140ms (היברידי)
- Faithfulness (RAGAS): 0.76 → 0.91
חיסכון בתשתית: אין צורך להקים שרת נפרד, חיסכון של $200 לחודש. מעבר מ-BM25 טהור ל-hybrid kNN+BM25 הניב שיפור של 27% ב-NDCG מבלי לשנות תשתית. ללקוח היה RAG עובד תוך שבועיים.
יצירת האינדקס ואינדוקס מסמכים
הוסיפו את שדה dense_vector לאינדקס הקיים ובצעו וקטוריזציה אצווה.
from elasticsearch import Elasticsearch
es = Elasticsearch("http://localhost:9200")
# Создание индекса с маппингом
index_config = {
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "russian", # Нативная поддержка русской морфологии
},
"source": {"type": "keyword"},
"doc_type": {"type": "keyword"},
"page": {"type": "integer"},
"date": {"type": "date"},
"embedding": {
"type": "dense_vector",
"dims": 1536,
"index": True,
"similarity": "cosine",
"index_options": {
"type": "hnsw",
"m": 16,
"ef_construction": 100,
}
}
}
},
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
}
}
es.indices.create(index="knowledge_base", body=index_config) from openai import OpenAI
from elasticsearch.helpers import bulk
openai_client = OpenAI()
def generate_actions(chunks: list):
texts = [c["text"] for c in chunks]
response = openai_client.embeddings.create(
model="text-embedding-3-small",
input=texts
)
embeddings = [e.embedding for e in response.data]
for chunk, embedding in zip(chunks, embeddings):
yield {
"_index": "knowledge_base",
"_source": {
"content": chunk["text"],
"source": chunk["source"],
"doc_type": chunk["doc_type"],
"page": chunk.get("page", 0),
"embedding": embedding,
}
}
bulk(es, generate_actions(document_chunks)) חיפוש היברידי: BM25 + kNN בפועל
Elasticsearch תומך בחיפוש היברידי באמצעות knn + query בבקשה אחת עם מיזוג RRF.
def hybrid_search_es( query: str, doc_type_filter: str = None, top_k: int = 5 ) -> list:
query_embedding = openai_client.embeddings.create( model="text-embedding-3-small", input=query ).data[0].embedding
filter_clause = []
if doc_type_filter:
filter_clause.append({"term": {"doc_type": doc_type_filter}})
body = {
"query": {
"bool": {
"must": {
"match": {
"content": {
"query": query,
"analyzer": "russian"
}
}
},
"filter": filter_clause,
}
},
"knn": {
"field": "embedding",
"query_vector": query_embedding,
"k": top_k * 3,
"num_candidates": 100,
"filter": filter_clause,
},
"rank": {
"rrf": {
"window_size": 50,
"rank_constant": 20,
}
},
"size": top_k,
"_source": ["content", "source", "doc_type"],
}
response = es.search(index="knowledge_base", body=body)
return [
{
"text": hit["_source"]["content"],
"source": hit["_source"]["source"],
"score": hit["_score"],
}
for hit in response["hits"]["hits"]
] יתרון המורפולוגיה הרוסית המובנית
Elasticsearch עם מנתח from elasticsearch import Elasticsearch es = Elasticsearch("http://localhost:9200") # Создание индекса с маппингом index_config = { "mappings": { "properties": { "content": { "type": "text", "analyzer": "russian", # Нативная поддержка русской морфологии }, "source": {"type": "keyword"}, "doc_type": {"type": "keyword"}, "page": {"type": "integer"}, "date": {"type": "date"}, "embedding": { "type": "dense_vector", "dims": 1536, "index": True, "similarity": "cosine", "index_options": { "type": "hnsw", "m": 16, "ef_construction": 100, } } } }, "settings": { "number_of_shards": 1, "number_of_replicas": 1, } } es.indices.create(index="knowledge_base", body=index_config) תומך בגזירת מילים רוסיות באמצעות Snowball. זה קריטי עבור חלק ה-BM25 של חיפוש היברידי — שאילתה עבור "договором" תמצא מסמכים עם "договор", "договоры", "договорам".
es.indices.analyze(
index="knowledge_base",
body={"analyzer": "russian", "text": "договоÑом аÑендÑ"}
) # tokens: ["договоÑ", "аÑенд"] â ÑÑеммиÑованнÑе ÑоÑмÑ"} מה כלול
- בדיקת אינדקס ES הנוכחי שלכם (מיפוי, shards, ביצועים)
- עיצוב סכמת dense_vector ובחירת מודל embedding
- כתיבת סקריפטים של וקטוריזציה אצווה ו-reindexing
- יישום חיפוש היברידי עם מיזוג RRF
- שילוב צינור RAG (עם LangChain או קריאות OpenAI ישירות)
- בדיקות: NDCG, Recall, זמן אחזור, faithfulness
- תיעוד והדרכת צוות (סדנה של שעתיים)
לוחות זמנים משוערים
| שלב | משך |
|---|---|
| ניתוח ועיצוב | 2–3 ימים |
| וקטוריזציה ו-reindexing | 2–5 ימים |
| פיתוח שאילתות היברידיות | 3–5 ימים |
| צינור RAG והערכה | 1–2 שבועות |
| סה"כ | 2–4 שבועות |
Elasticsearch kNN לעומת מסדי נתונים וקטוריים חלופיים
| מאפיין | Elasticsearch kNN | Pinecone / Qdrant |
|---|---|---|
| תשתית | כבר יש? אין צורך בחדשה | שירות נפרד |
| חיפוש היברידי | BM25 + kNN מובנה | BM25 נפרד + שרשור |
| גזירה רוסית | כן (Snowball) | לא (נדרש חיצוני) |
| Latency p99 | 140 ms (היברידי) | 50-100 ms (וקטורי בלבד) |
| NDCG@5 (הניסיון שלנו) | 0.81 לעומת 0.64 (BM25 טהור) | ~0.75-0.80 (דומה) |
Elasticsearch מנצח בעלות כוללת ופשטות אם ES כבר בייצור. לסטארטאפים ללא מורשת, Pinecone עשוי להיות מהיר יותר להקמה.
טעויות נפוצות ביישום ES kNN
- שימוש ב-num_candidates קטן מדי (מתחת ל-100) — recall יורד.
- אי הגדרת מנתח לטקסטים רוסיים — BM25 הופך לחסר תועלת.
- ניסיון להזין embedding במימד 768 לשדה עם dims=1536 — ES מחזיר שגיאה.
- שכחת RRF — ללא מיזוג, ה-hybrid לא עובד כמצופה.
עם 5+ שנות מומחיות ב-Elasticsearch ויותר מ-20 יישומי RAG מוצלחים, אנו מבטיחים רקורד מוכח. המומחים המוסמכים שלנו מבטיחים שהפרויקט שלכם יעמוד בסטנדרטים הגבוהים ביותר. קבלו ייעוץ לפרויקט שלכם — כבר יישמנו 10+ פרויקטים דומים. צרו קשר כדי לדון בפרטים ולקבל הערכה אישית.







