Гибридный поиск и реранкинг

Два приёма, которые заметно поднимают качество retrieval: гибридный поиск и реранкинг.

Гибридный поиск объединяет семантический (векторы) и лексический (ключевые слова, BM25) поиск. Реранкинг переупорядочивает кандидатов более точной моделью.

Зачем гибрид

Векторный поиск ловит смысл, но иногда промахивается на точных терминах: артикулы, коды ошибок, имена («ORA-00942», «pgvector»). Лексический поиск (BM25) наоборот отлично находит точные слова, но не понимает синонимов. Гибрид берёт лучшее от обоих: смысл + точные совпадения.

ПодходСилён вСлаб в
Векторныйсмысл, синонимы, перефразточные коды, редкие термины
BM25 (слова)точные термины, артикулысинонимы, смысл
Гибриди то, и другоечуть сложнее настроить

Смешиваем два скора (запускаемый)

Идея гибрида: нормализуем оба скора в шкалу [0..1] и складываем с весом. Покажем на кандидатах с их векторным скором и числом ключевых попаданий.

docs = [
    ("Гайд по индексам в PostgreSQL",          0.62, 1),  # text, vec, keyword_hits
    ("PostgreSQL: ускоряем медленные запросы",  0.55, 3),
    ("Введение в реляционные базы данных",      0.48, 0),
]

def norm(values):
    lo, hi = min(values), max(values)
    if hi == lo:
        return [1.0 for _ in values]
    return [(v - lo) / (hi - lo) for v in values]

vec = norm([d[1] for d in docs])
kw = norm([d[2] for d in docs])
alpha = 0.5  # вес векторного скора
fused = []
for (text, _, _), v, k in zip(docs, vec, kw):
    score = alpha * v + (1 - alpha) * k
    fused.append((round(score, 3), text))
for score, text in sorted(fused, reverse=True):
    print(f"  {score:.3f}  {text}")

Вывод:

  0.750  PostgreSQL: ускоряем медленные запросы
  0.667  Гайд по индексам в PostgreSQL
  0.000  Введение в реляционные базы данных

Документ с большим числом ключевых попаданий поднялся наверх, хотя по чистому вектору был вторым. Так гибрид исправляет промахи семантики на точных терминах.

Реранкинг: второй, более точный проход

Векторный поиск быстрый, но грубый: он сравнивает запрос и чанк по отдельности. Реранкер (часто cross-encoder) смотрит на пару «запрос+чанк» вместе и оценивает релевантность точнее. Схема двухступенчатая: дёшево достаём, например, top-50 вектором, затем дорогой реранкер пересортировывает их и оставляет top-5.

candidates = [
    ("Установка и настройка PostgreSQL",      0.81),
    ("Резервное копирование базы PostgreSQL",  0.78),
    ("Копирование таблиц между базами",        0.69),
]
query_words = set("резервное копирование postgresql".split())

def rerank(cands):
    out = []
    for text, vec in cands:
        overlap = len(query_words & set(text.lower().split())) / len(query_words)
        final = 0.5 * vec + 0.5 * overlap  # имитация более точной оценки
        out.append((round(final, 3), vec, text))
    out.sort(reverse=True)
    return out

print("До реранкинга (по вектору):")
for text, vec in sorted(candidates, key=lambda c: c[1], reverse=True):
    print(f"  {vec:.2f}  {text}")
print("После реранкинга:")
for final, vec, text in rerank(candidates):
    print(f"  {final:.3f}  {text}")

Вывод:

До реранкинга (по вектору):
  0.81  Установка и настройка PostgreSQL
  0.78  Резервное копирование базы PostgreSQL
  0.69  Копирование таблиц между базами
После реранкинга:
  0.890  Резервное копирование базы PostgreSQL
  0.572  Установка и настройка PostgreSQL
  0.512  Копирование таблиц между базами

Реранкер поднял реально нужный документ с 2-го места на 1-е. Настоящий cross-encoder делает это умнее нашей имитации, но идея та же: дёшево отобрать кандидатов, дорого и точно их переупорядочить.

Итог

  • Гибридный поиск = векторы (смысл) + BM25 (точные слова).
  • Реранкинг — второй, более точный проход поверх дешёвых кандидатов.
  • Оба приёма повышают долю релевантного в верхушке выдачи.
Проверьте себя
1. Зачем нужен гибридный поиск?
AЧтобы вообще убрать векторы
BЧтобы сочетать смысл (векторы) и точные совпадения слов (BM25)
CЧтобы ускорить эмбеддинг
DЧтобы уменьшить базу
2. В чём идея реранкинга?
AУдалить половину базы
BДёшево достать много кандидатов, затем точной моделью переупорядочить и оставить лучших
CСжать векторы
DЗаменить LLM
3. В чём слабость чисто векторного поиска, которую закрывает BM25?
AОн не понимает синонимы
BОн хуже находит точные термины, коды и артикулы
CОн слишком быстрый
DОн не использует эмбеддинги
Поддержать проект