Гайд · TNWS AI
Как объединить BM25 и vector search в Vespa для поиска по смыслу и словам
Индекс text, BM25 rank feature, nearestNeighbor, объединение кандидатов и измерение качества гибридного поиска.
Какую задачу решаем и где применимо
Русскоязычный поисковый запрос: «как объединить BM25 и vector search Vespa». Материал проверен по официальной документации Vespa, актуальной на 19 сентября 2026 года. Для Vespa, где текстовое поле text индексируется с index: enable-bm25, а tensor-поле embedding снабжено HNSW и тем же документным source_id. Гибридный сценарий полезен, когда семантические перефразы и буквальные термины встречаются одновременно.
Vespa позволяет объединять query operators в YQL и в rank-profile использовать bm25(text) вместе с closeness(field, embedding). Сложение score без нормировки/подбора весов может перекосить ранжирование: величины не являются совместимыми вероятностями. or формирует кандидатов из двух ветвей, тогда как and потребовал бы пересечения и мог потерять редкий точный артикул.
До начала эксперимента подготовьте маленький набор документов с известными source_id и один отрицательный пример. Сохраните версию schema, embedding-модели, набор входных вопросов и ожидаемые IDs: так результат можно воспроизвести после обновления системы, а не оценивать по одному удачному ответу. Все значения для маленького tensor в шаблоне — иллюстрация контрактов, не показатели качества модели.
Пошаговое выполнение
- Включите BM25 для индексируемого
textи убедитесь, что поле доступно для текстовой ветки. - Оставьте существующий semantic rank-profile как контрольный baseline.
- Создайте гибридный профиль, экспериментально определив вес каждой ветки на размеченных вопросах.
- В YQL соедините
userQuery()с nearestNeighbor черезor, передайте текстовый query и embedding отдельно. - Сравните nDCG@10 и полноту точных терминов на том же наборе, что использован для чистого векторного поиска.
Эти шаги предполагают доступ к собственному тестовому приложению Vespa. Для Vespa Cloud и самостоятельной установки способы деплоя отличаются; не угадывайте имена кнопок консоли. Используйте показанный интерфейс schema, YQL или CLI, сверяя конкретное окружение и permissions с документацией. При деплое сохраняйте предыдущее определение, чтобы сопоставить результаты и откатить неудачное изменение без смешения двух причин.
Готовый шаблон
rank-profile hybrid {
inputs { query(q) tensor<float>(x[3]) }
first-phase {
expression: bm25(text) + 0.3 * closeness(field, embedding)
}
}
select source_id,text from article where userQuery() or {totalTargetHits:50}nearestNeighbor(embedding,q)
# Параметры запроса: query="SKU-42 возврат", ranking=hybrid,
# input.query(q)=[0.10,0.20,0.30], hits=10
Шаблон адаптируйте к своей размерности и именам документов. Если он содержит ручной трёхмерный вектор, он полезен для smoke-теста схемы и синтаксиса, но не заменяет настоящий embedding вопроса. Секреты провайдера, токены Vespa и данные закрытых клиентов не вставляйте ни в публичный prompt, ни в статью, ни в логи запроса.
Конкретный вход и ожидаемый выход
Вход: вопрос Как вернуть товар SKU-42?; документ A содержит буквальный код SKU-42, документ B объясняет возврат синонимами, документ C похож по теме, но про другой товар. Ожидается, что A и B доступны среди кандидатов и проверяются вручную; порядок решают размеченные кейсы и веса, а не утверждение, будто коэффициент 0.3 универсален.
Важно не смешивать retrieval и ранжирование. or задаёт набор возможных документов, профиль определяет их порядок; если слишком мал totalTargetHits, векторная ветка не доставит семантически нужные документы независимо от идеального веса. Если текстовое поле не индексирует BM25, формула не заменит пропущенную схему. Для смешанного русского/латинского ввода сохраните кейсы с артикулами, сокращениями, опечатками и синонимами. Выберите веса на обучающей части gold set и подтвердите на отложенной части, иначе подгоните конфигурацию под примеры. Не показывайте в RAG нерелевантный документ только потому, что суммарный score высок: используйте пороги, разметку и проверку цитат по источнику.
Сравнение сценариев
| Режим | Находит лучше | Слабое место |
|---|---|---|
| BM25 | буквальные коды и слова | перефразирование |
| nearestNeighbor | смысловые перефразы | точные редкие токены |
| гибридный OR | оба типа кандидатов | требует настройки весов и оценки |
Вывод: сопоставляйте эти варианты на своих вопросах и зафиксированном корпусе. Изменение конфигурации имеет смысл только если оно улучшает оговорённую метрику и укладывается в допустимую задержку.
Проверка результата
-
textиндексируется с enable-bm25. - Кандидаты из обеих ветвей видны в контрольных вопросах.
- Веса подобраны не на тестовом наборе.
- nDCG@10 и p95 сравниваются с обеими baseline-ветвями.
Успех означает прохождение всех перечисленных пунктов на положительных и отрицательных кейсах. Снимите фактические IDs, число hits и latency до и после изменения; если есть LLM-ответ, проверьте цитаты вручную. Пустой результат из-за ошибки типов, времени ожидания, прав доступа или отсутствующей записи нельзя выдавать за низкую релевантность. Удобно хранить отдельный отчёт по каждым case_id, иначе невозможно обнаружить регрессию на редких формулировках.
Типичные ошибки: чего не делать
- Складывать score как калиброванные вероятности.
- Соединять ветви через
and, когда нужен union. - Выбирать вес по одному красивому примеру.
Не исправляйте нерелевантную выдачу произвольным повышением top-k: сперва отделите ошибку модели, схемы, фильтра и ранжирования. Не отключайте проверку tenant ради красивого теста. Не делайте вывод о производительности на крошечном демонстрационном корпусе; проверяйте рабочий объём, конкурентную нагрузку и p95, а также расход памяти/скорость обновлений там, где они существенно меняются.
FAQ
Почему нужен OR?
Он объединяет кандидатов из текстовой и векторной ветви.
Универсален ли коэффициент 0.3?
Нет, это иллюстрация, веса подбирают по своим данным.
BM25 заменяет embedding?
Нет, они покрывают разные типы релевантности.
Официальные источники
Частые вопросы
Почему нужен OR?
Он объединяет кандидатов из текстовой и векторной ветви.
Универсален ли коэффициент 0.3?
Нет, это иллюстрация, веса подбирают по своим данным.
BM25 заменяет embedding?
Нет, они покрывают разные типы релевантности.
Читайте также
Как фильтровать векторный поиск Vespa по tenant без утечки в RAG
YQL-фильтр вместе с nearestNeighbor, серверный tenant, pre-filtering, отрицательный ACL-тест и проверка полного top-k.
Как хранить chunks для RAG в Vespa с проверяемыми source_id
Схема chunk-документов, parent ID, versioned embeddings, готовый prompt и тест цитат по исходным фрагментам.
Как измерить Recall@10 ANN-поиска Vespa через exact baseline
Два сопоставимых nearestNeighbor-запроса с approximate:false, формула Recall@10 и ограничения exact baseline.
Комментарии
Пока тихо. Скажите первое слово