Як впровадити RAG на Elasticsearch kNN: гібридний пошук BM25 та вектори

Коли стандартний повнотекстовий пошук перестає справлятися з синонімами та складними запитами, користувачі не знаходять потрібне, а бізнес втрачає час і клієнтів. Ми розробляємо гібридний пошук на базі Elasticsearch kNN, об'єднуючи BM25 і векторні представлення в одному індексі. Наша команда реалізує проєкт під ключ — від аудиту до впровадження та подальшої підтримки, забезпечуючи надійне рішення, яке масштабується разом із вашим бізнесом.
Часті запитання

Як впровадити RAG з Elasticsearch kNN: об'єднаний пошук BM25 та вектори

Уявіть: ваш Elasticsearch обробляє сотні тисяч документів, але користувачі скаржаться, що пошук не знаходить релевантні відповіді. BM25 чудово працює з точними збігами, але пасує перед синонімами, тавтологією та складними запитами. Результати: клієнти йдуть, оператори витрачають час на пошук. Додавати окрему векторну базу? Це зростання вартості інфраструктури, мережеві затримки та ще одна система для підтримки. Оптимальне рішення — використовувати вбудований kNN в Elasticsearch 8.x, об'єднавши повнотекстовий та векторний пошук в одному індексі. Ми допомогли кільком командам впровадити такий гібрид без зміни інфраструктури, і зараз розповімо, як це зробити. Зв'яжіться з нами для аудиту вашого поточного пошуку — ми запропонуємо оптимальний маршрут.

Чому Elasticsearch kNN — оптимальне рішення для гібридного пошуку?

Типові болі при впровадженні RAG без перебудови: розрізнений пошук (BM25 пропускає семантику), інфраструктурний хаос (підіймати Pinecone або Weaviate паралельно з ES — збільшувати вартість та складність), латентність (зовнішні векторні БД додають мережеві затримки p99 до 50+ мс). Elasticsearch kNN вирішує всі три: hybrid search (kNN + BM25) через RRF fusion в одному запиті, мінімальний оверхед, не потрібні нові сервери. Ми гарантуємо сумісність з існуючою інфраструктурою та забезпечуємо підтримку сертифікованими фахівцями з 5+ років досвіду.

Elasticsearch — зріла технологія з 15+ років на ринку, використовується в тисячах продакшенів. Вбудована підтримка аналізатора Snowball дає якісний стемінг: запит «договором» знайде «договір», «договори», «договорам». Це критично для BM25-частини гібрида. Крім того, ELK-стек (Logstash, Kibana) дозволяє моніторити індекси та візуалізувати метрики пошуку без додаткових інструментів.

Які налаштування HNSW дають найкращий баланс швидкості та якості?

Для продакшену ми рекомендуємо HNSW з параметрами m=16, ef_construction=100. Це оптимальний баланс між швидкістю індексації та точністю пошуку. Занадто маленьке num_candidates (менше 100) знижує recall, занадто велике — збільшує latency. У наших проектах використовуємо cosine similarity як метрику відстані для ембендінгів.

Як ми це робимо: стек, конфіги, кейс

Стек: 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. Батчева векторизація існуючих документів (2 дні, 500K × $0.02/1M = $10).
  3. Reindexing з новим полем (6 годин).
  4. Додавання RRF fusion у пошукові запити.
  5. RAG-шар поверх ES retrieval.

Результати (vs чистий BM25):

  • NDCG@5: 0.64 → 0.81
  • Recall@10: 0.71 → 0.88
  • Latency P95: 85мс → 140мс (hybrid)
  • Faithfulness (RAGAS): 0.76 → 0.91

Економія на інфраструктурі: не потрібно підіймати окремий сервер за $200/міс — у 3 рази дешевше, ніж рішення з Pinecone + окремий ES. Перехід від pure BM25 до hybrid kNN+BM25 дав +27% до NDCG без зміни інфраструктури. Клієнт отримав працюючий RAG за 2 тижні.

Створення індексу та індексація документів

Додаємо поле 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))

Hybrid Search: BM25 + kNN на практиці

Elasticsearch підтримує об'єднаний пошук через knn + query в одному запиті з RRF fusion.

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 з analyzer russian підтримує стемінг російських слів через Snowball. Це критично для BM25 частини об'єднаного пошуку — запит «договором» знайде документи з «договір», «договори», «договорам».

es.indices.analyze(
    index="knowledge_base",
    body={"analyzer": "russian", "text": "договором аренды"}
)  # tokens: ["договор", "аренд"] — стемовані форми

Що входить в роботу

  • Аудит поточного індексу ES (маппінг, шарди, продуктивність)
  • Проектування схеми dense_vector та вибір embedding-моделі
  • Написання скриптів батчевої векторизації та реіндексації
  • Реалізація об'єднаного пошуку з RRF fusion
  • Інтеграція RAG-пайплайну (з LangChain або прямими викликами OpenAI)
  • Тестування: NDCG, Recall, latency, faithfulness
  • Документація та навчання команди (2 години воркшопу)
  • Ми маємо 5+ років досвіду в пошукових системах і реалізували понад 10 проектів RAG.

Терміни орієнтовно

Етап Тривалість
Аналіз та проектування 2–3 дні
Векторизація та реіндексування 2–5 днів
Розробка об'єднаних запитів 3–5 днів
RAG-пайплайн та оцінка 1–2 тижні
Разом 2–4 тижні

Порівняння Elasticsearch kNN з альтернативними векторними базами

Характеристика Elasticsearch kNN Pinecone / Qdrant
Інфраструктура Вже є? Не потрібно нової Окремий сервіс
Об'єднаний пошук Вбудований BM25 + kNN Через окремий BM25 + конкатенація
Russian stemmer Так (Snowball) Ні (потрібен зовнішній)
Latency p99 140 мс (гібрид) 50-100 мс (тільки вектор)
NDCG@5 (наш досвід) 0.81 vs 0.64 (pure BM25) ~0.75-0.80 (аналогічно)
Вартість інфраструктури $0 + існуючий ES $200/міс за сервер

Elasticsearch виграє за комплексною вартістю та простотою, якщо ES вже в продакшені. Для стартапів без легасі — Pinecone може бути швидшим у запуску. Наш досвід показує: Elasticsearch kNN у 3 рази дешевший за інфраструктуру, ніж Pinecone + окремий ES.

Типові помилки при впровадженні ES kNN
  • Використовувати занадто маленьке num_candidates (менше 100) — падає recall.
  • Не налаштовувати analyzer для російських текстів — BM25 марний.
  • Намагатися подати embedding розмірності 768 в поле з dims=1536 — ES поверне помилку.
  • Забути про rrf — без fusion об'єднаний пошук не працює як очікується.

Отримайте консультацію по вашому проекту — ми вже реалізували 10+ подібних проектів. Зв'яжіться з нами, щоб обговорити деталі та отримати індивідуальну оцінку. Гарантія якості та сертифікована підтримка.

Напрямки AI-розробки

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1543
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1394
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1374
  • Розробка логотипу компанії B2B Advance
    Розробка логотипу компанії B2B Advance
    816
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1142