Гибридный поиск
Гибридный поиск объединяет полнотекстовый поиск и векторный поиск в едином ранжированном результате: каждый документ оценивается одновременно по релевантности текста и по близости эмбеддингов, а два ранжирования объединяются в одно. Это сочетает точность лексического сопоставления и полноту семантической близости и является распространённым строительным блоком для этапа извлечения данных в подходе Retrieval-Augmented Generation (RAG).
Общее описание гибридного поиска см. в разделе Понятие гибридного поиска.
Гибридный поиск — это не отдельный тип индекса. Он переиспользует два существующих индекса одной и той же таблицы:
- fulltext_relevance по текстовой колонке — даёт сигнал релевантности BM25;
- vector_kmeans_tree по колонке с эмбеддингами — даёт сигнал близости ближайших соседей (семантический).
Подготовка индексов
Создайте таблицу с текстовой колонкой и колонкой эмбеддингов, затем добавьте оба индекса.
CREATE TABLE documents (
id Uint64,
text Utf8,
embedding String,
PRIMARY KEY (id)
);
Добавьте индекс fulltext_relevance по текстовой колонке (для ранжирования по релевантности требуется именно этот тип, а не fulltext_plain):
ALTER TABLE documents
ADD INDEX ft_idx
GLOBAL USING fulltext_relevance
ON (text)
WITH (tokenizer=standard, use_filter_lowercase=true);
Добавьте индекс vector_kmeans_tree по колонке эмбеддингов:
ALTER TABLE documents
ADD INDEX vec_idx
GLOBAL USING vector_kmeans_tree
ON (embedding)
WITH (distance=cosine);
Подробности о каждом типе индекса — в разделах Полнотекстовые индексы и Векторные индексы.
Выполнение гибридного запроса
Гибридный запрос — это обычный SELECT по основной таблице (без VIEW), у которого ключ ORDER BY представляет собой единственный вызов HybridRank. HybridRank принимает по одному оценивающему выражению на каждую ветвь: FullTextScore для текстовой ветви и Knn-расстояние или сходство для векторной ветви.
PRAGMA ydb.KMeansTreeSearchTopSize = "10";
$queryText = "машинное обучение";
$queryVector = Knn::ToBinaryStringFloat([0.1, 0.2, 0.3, 0.4]);
SELECT id, text
FROM documents
ORDER BY HybridRank(
FullTextScore(text, $queryText),
Knn::CosineDistance(embedding, $queryVector))
LIMIT 10;
Оба входа происходят из одного пользовательского запроса: $queryText — это текст поиска (сопоставляется лексически), а $queryVector — его эмбеддинг, вычисленный приложением (сопоставляется семантически). YDB не вычисляет эмбеддинги самостоятельно, поэтому вектор передаётся извне — здесь он построен из литерала с помощью Knn::ToBinaryStringFloat, но в приложении это результат работы модели эмбеддингов.
YDB автоматически сопоставляет каждую ветвь с её индексом по оцениваемой колонке (text → полнотекстовый индекс релевантности, embedding → векторный индекс), извлекает пул кандидатов из каждой ветви и объединяет два ранжирования. Результат — топ LIMIT документов по объединённой оценке.
Обратите внимание, что, в отличие от полнотекстового и векторного поиска, гибридный запрос не использует VIEW IndexName: индексы выбираются по аргументам HybridRank, а чтение идёт по основной таблице.
PRAGMA ydb.KMeansTreeSearchTopSize управляет полнотой векторной ветви — см. KMeansTreeSearchTopSize. Как и при обычном векторном поиске, его следует задавать явно.
Настройка объединения
Способ объединения и его параметры передаются как именованные аргументы HybridRank. Наиболее употребительные:
Mode—"rrf"(по умолчанию, Reciprocal Rank Fusion) или"linear"(взвешенная сумма нормализованных оценок);Weights— кортеж весов ветвей, по одному значению на оценивающий аргумент, для смещения ранжирования в сторону одного из сигналов;K— константа RRF (по умолчанию60.0);Indexes/Limits— явные имена индексов и размеры пулов кандидатов по ветвям.
Для встроенных режимов вклад каждой ветви фиксирован формулой. Для полного контроля над объединением передайте вместо Mode пользовательскую лямбду:
RankLambda— лямбда, получающая ранги документа по ветвям (1-based позиция внутри каждой ветви) и возвращающая итоговую оценку;ScoreLambda— лямбда, получающая исходные оценки документа по ветвям (релевантность полнотекстового поиска или векторное расстояние/сходство) и возвращающая итоговую оценку.
Пользовательская лямбда заменяет встроенное объединение, поэтому её нельзя комбинировать с Mode, Weights, K или Normalize — перенесите все веса и константы в тело лямбды. Подробное описание и примеры — в разделе Синтаксис гибридного запроса (HybridRank).
Например, чтобы дать векторной ветви вдвое больший вес, чем текстовой, в режиме RRF:
$queryText = "машинное обучение";
$queryVector = Knn::ToBinaryStringFloat([0.1, 0.2, 0.3, 0.4]);
SELECT id, text
FROM documents
ORDER BY HybridRank(
FullTextScore(text, $queryText),
Knn::CosineDistance(embedding, $queryVector),
(1, 2) AS Weights)
LIMIT 10;
Полный список параметров и их семантика — в разделе Гибридный поиск (HybridRank).
По умолчанию каждая ветвь извлекает не более LIMIT * 10 кандидатов. Для настройки пулов используйте PRAGMA ydb.HybridSearchFactor или явный AS Limits. Кандидаты, найденные только одной ветвью, также могут попасть в результат. Фильтры, не задающие префикс, применяются после объединения и могут сократить результат ниже LIMIT; см. пулы кандидатов и фильтрация.
Ограничения
- Для таблицы должны существовать готовые индексы
fulltext_relevanceиvector_kmeans_treeпо соответствующим оцениваемым колонкам, с совместимыми векторными метриками; иначе запрос завершится с ошибкой. - Текущая реализация поддерживает только таблицы с одноколоночным первичным ключом.
- Префиксные полнотекстовые и векторные индексы требуют равенств для всех колонок префикса в
WHERE; см. выбор индексов и фильтрация. - Если подходит несколько полнотекстовых индексов, используйте явный
AS Indexes. Векторные ветви выбирают совместимый индекс с самым длинным полностью заданным префиксом; при одинаковой максимальной длине префикса также требуетсяAS Indexes. LIMITдолжен быть литералом, так как он задаёт размеры пулов кандидатов по ветвям. Чтобы использовать параметризованныйLIMIT, передайте явныйAS Limits.HybridRank(...)должен быть единственным ключомORDER BY— его нельзя отрицать, оборачивать в другое выражение или комбинировать с другими ключами сортировки.- Пользовательская лямбда объединения (
RankLambdaилиScoreLambda) заменяет встроенное объединение и не может комбинироваться сMode,Weights,KилиNormalize. Можно указать не более одного изRankLambdaилиScoreLambda.