Реалізація Hybrid Search (векторний + повнотекстовий пошук) для RAG

Коли в базі тисячі документів, пошук за змістом часто не знаходить точний номер або рідкісний термін, а повнотекстовий запит втрачає синоніми та перефразування. Ми реалізуємо гібридний пошук, об'єднуючи векторний та повнотекстовий підходи, щоб ваш RAG знаходив і точні збіги, і семантично близькі документи. Наша команда впроваджує рішення під ключ з подальшою підтримкою, забезпечуючи надійний retrieval на ваших даних.
Часті запитання

При реалізації RAG-систем часто виникає дилема: як одночасно знайти документ за змістом і за точним номером? Hybrid Search — комбінація векторного (dense) і повнотекстового (sparse/BM25) пошуку з подальшим злиттям результатів — вирішує це завдання. На практиці hybrid search стабільно перевершує будь-який з методів окремо на більшості корпоративних датасетів. Наприклад, на одному з проєктів hybrid search (RRF) покращив MRR@5 на 12% відносно pure dense search при збереженні високого recall за точними термінами. Ми реалізуємо такі рішення під ключ, з гарантією якості retrieval на ваших даних. Замовте консультацію з впровадження hybrid search та отримайте оцінку вашого проєкту.

Чому не можна обійтися тільки dense search

Dense embedding усереднює семантику — це і сила, і слабкість. Запит «договір №ДА-2023-451» матиме високу косинусну схожість з договорами взагалі, але не з конкретним документом за номером. BM25 знайде точний збіг рядка «ДА-2023-451» миттєво.

  • Dense search погано працює для: точних номерів (договір, артикул, серійний номер), абревіатур і специфічних акронімів, рідкісних технічних термінів, запитів на пошук точної цитати.
  • BM25 погано працює для: перефразованих запитів (синоніми), семантично схожих концепцій з різними словами, міжмовних запитів, неточних описів («щось про оплату після поставки»).

Чому hybrid search кращий, ніж dense або BM25 окремо?

Поєднання двох підходів дає синергію: dense покриває семантику, BM25 — точні збіги. Нижче на реальному кейсі видно, що hybrid RRF (без реранкера) перевершує dense+reranker за MRR@5 (0.83 vs 0.80) та NDCG@5 (0.81 vs 0.77). При цьому hybrid+reranker дає вже 0.89/0.87. Іншими словами, реалізація гібридного пошуку (hybrid search implementation) дозволяє досягти балансу між семантичною близькістю та точним збігом ключових слів. За даними нашого A/B-тестування на 400 запитах, hybrid RRF перевершує dense+reranker в 1.04 рази за MRR@5. Для багатьох задач це скасовує необхідність у дорогому реранкері.

Алгоритми злиття результатів

Reciprocal Rank Fusion (RRF) — найбільш стійкий метод. RRF — метод злиття, запропонований Кормаком та ін. (2009) — детальніше на Wikipedia.

Код 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: просунутий sparse encoder

SPLADE (Sparse Lexical and Expansion Model) генерує sparse вектори з лексичним розширенням — модель вчиться «розширювати» запит синонімами та пов'язаними термінами. За даними бенчмарку 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 бенчмарків. Для російської мови рекомендуємо модель naver/efficient-splade-VI-BT-large-query або багатомовні варіанти.

Реалізація з 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")  # Багатомовний 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 на якість retrieval

З нашої практики: на проєкті з 12 000 документів корпоративної бази знань (договори, регламенти, FAQ) ми протестували 400 запитів різних типів. Результати:

Конфігурація MRR@5 NDCG@5 Точні терміни recall
Dense only (BGE-M3) 0.74 0.71 0.58
BM25 only 0.67 0.63 0.91
Hybrid RRF (k=60) 0.83 0.81 0.84
Hybrid RSF (α=0.6) 0.81 0.79 0.81
Dense + Reranker 0.80 0.77 0.61
Hybrid + Reranker 0.89 0.87 0.86

Hybrid RRF без reranker вже б'є dense+reranker. Комбінація hybrid+reranker — найкращий результат. Для порівняння, SPLADE як sparse encoder дає приріст 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.

Покроковий процес впровадження hybrid search

  1. Аудит поточної схеми retrieval: аналіз використовуваних ембендінгів, стека векторної БД та метрик якості.
  2. Вибір та налаштування sparse encoder: встановлення SPLADE або іншого sparse encoder під вашу мову та домен.
  3. Інтеграція подвійного пошуку: налаштування індексації dense та sparse векторів у Qdrant/Pinecone/Weaviate.
  4. Реалізація злиття: впровадження RRF або RSF з початковими параметрами (k=60, alpha=0.5).
  5. Тестування та оптимізація: прогін ваших запитів, підбір параметрів за метриками MRR/NDCG.
  6. Документація та передача: опис процесу, навчання команди, передача коду та конфігів.

Що ви отримаєте в результаті

  • Інтеграційний код hybrid search у вашу RAG-систему.
  • Конфігураційні файли для Qdrant/Pinecone.
  • Документація з налаштування та експлуатації.
  • Навчання команди (2-годинний вебінар).
  • Гарантія якості retrieval (фіксація метрик до/після).
  • Пост-проєктна підтримка 1 місяць.

Зв'яжіться з нами для оцінки вашого проєкту. Отримайте консультацію з впровадження hybrid search та підвищте якість retrieval вашої RAG-системи.

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

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

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