חיפוש היברידי עם Elasticsearch: שילוב BM25 ו-Vector kNN עבור RAG

כאשר חיפוש טקסט מלא סטנדרטי נכשל בטיפול במילים נרדפות ובשאילתות מורכבות, משתמשים לא מוצאים את מה שהם צריכים, והעסק מאבד זמן ולקוחות. אנחנו בונים חיפוש היברידי על Elasticsearch kNN, המשלב BM25 וייצוגים וקטוריים באינדקס אחד. הצוות שלנו מספק את הפרויקט במפתח מלא—מבדיקת היתכנות ועד הטמעה ותמיכה שוטפת—ומבטיח פתרון אמין שגדל עם העסק שלך.

תחומי פיתוח AI

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    776
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1022
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113

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 מבלי לשנות תשתית.

שלבים:

  1. הוספת שדה embedding (dense_vector, dims=1536) למיפוי הקיים.
  2. וקטוריזציה אצווה של מסמכים קיימים (יומיים, 500K × $0.02/1M = $10).
  3. Reindex עם השדה החדש (6 שעות).
  4. הוספת מיזוג RRF לשאילתות חיפוש.
  5. שכבת 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+ פרויקטים דומים. צרו קשר כדי לדון בפרטים ולקבל הערכה אישית.