В прошлых статьях я рассказывал, как постепенно добавлял семантический поиск в Obsidian. Сначала появился локальный векторный индекс, потом Similar Notes, поиск потенциальных дублей и RAG. В какой-то момент всё это начало работать достаточно хорошо, и я уже собирался оставить поисковую часть в покое.
Но потом добрался до Rerank.
Идея простая. Сначала обычный семантический поиск находит заметки, близкие по смыслу к запросу. Затем вторая модель получает найденные фрагменты, сравнивает их с вопросом и пытается расставить документы по релевантности. Звучит полезно, особенно когда в хранилище много заметок на похожие темы.
Я уже примерно представлял, как это реализовать, но возник вопрос: насколько часто Rerank будет исправлять результаты обычного поиска? Ведь ради каждого уточнения придётся отправлять фрагменты заметок внешнему провайдеру, платить за запрос и ждать ответ второй модели.
Всё это я проверял во время разработки Veynrel, своего open-source плагина для Obsidian, который раньше назывался Vault Audit AI. Сейчас он объединяет проверку состояния хранилища (Health), семантический поиск и исследование связей между заметками (Discover), а также интервальное повторение на FSRS-6 (Recall). Плагин доступен на GitHub и в Obsidian Community Plugins.
Для начала я сравнил три Rerank-модели, затем собрал тестовое хранилище из 64 заметок и подготовил 28 поисковых запросов. Хотелось понять, насколько меняется качество выдачи, сколько это стоит и какую задержку добавляет второй этап.
На корпусе из 64 заметок результаты оказались неожиданными. На 24 вопросах, для которых в хранилище существовал ответ, Rerank улучшил nDCG@10 в 11 случаях, ухудшил в двух и оставил без изменений ещё 11. При этом обычный семантический поиск уже ставил релевантную заметку первой в 23 из 24 случаев. Rerank смог исправить первый результат только однажды.
После этого идея автоматически запускать вторую модель при каждом поиске стала выглядеть сомнительно. Ниже разберу, как проходили тесты, где Rerank помог и почему в итоге я сделал для него кнопку.
Позже, когда готовил иллюстрации к статье, я провёл ещё один тест на демонстрационном хранилище из 500 заметок. Там обнаружилось различие между качеством выдачи на уровне целых документов и доступностью ответа в выбранных фрагментах.
Почему обычного поиска по смыслу иногда недостаточно
Всю архитектуру повторять не буду, остановлюсь только на той части, которая относится к Rerank.
Основа поиска в Veynrel привычная. Заметки разбиваются на фрагменты, каждый фрагмент превращается в числовой вектор, а векторы сохраняются в локальном индексе. Когда пользователь вводит вопрос, embedding-модель преобразует его в вектор той же размерности. Дальше плагин находит ближайшие к нему фрагменты и группирует результаты по документам.
Это хорошо работает, когда вопрос и заметка сформулированы по-разному, но говорят об одном. Например, человек ищет «как продолжить обработку после сбоя», а в заметке написано «возобновление задачи с контрольной точки».
Сложнее, когда несколько документов относятся к одной области знаний, но отвечают на разные вопросы.
Допустим, у нас есть две заметки о восстановлении после сбоя. Первая описывает резервное копирование, вторая рассказывает о повторном выполнении незавершённых операций с сохранением промежуточного прогресса.
Обе близки по теме. Но если пользователь хочет узнать, как продолжить прерванную обработку, вторая заметка будет полезнее.
Причина в устройстве поиска. Вектор вопроса и векторы документов вычисляются независимо, после чего сравнивается их близость. Такая оценка не всегда хорошо отражает, насколько полно конкретный документ отвечает на поставленный вопрос.
Для этого и нужен Rerank.
В одном из распространённых подходов используется cross-encoder: вопрос и документ подаются модели вместе, чтобы она могла оценить их непосредственно в контексте друг друга. Конкретная архитектура зависит от выбранной модели, но задача остаётся общей: уточнить релевантность уже найденных кандидатов.
В Veynrel цепочка выглядит так:

У схемы есть ограничение. Rerank работает с результатами первого этапа. Если Semantic Search не нашёл нужную заметку среди 30 кандидатов, Rerank её не восстановит.
Есть и вопрос приватности. Для Rerank нужен читаемый текст документов, поэтому при использовании облачной модели выбранные фрагменты отправляются внешнему провайдеру.
С облачными embeddings ситуация похожая: содержимое заметок передаётся провайдеру при построении индекса. Разница в том, что Rerank снова отправляет фрагменты во время каждого уточнения. К этому добавляются стоимость запроса и сетевое ожидание.
Поэтому я решил сначала проверить, какую пользу даёт второй этап.
Сначала я сравнил три модели
Для первой проверки использовал OpenRouter. У него есть отдельный Rerank API, благодаря чему можно работать со специализированными моделями через один адаптер.
Выбрал три варианта:
cohere/rerank-v3.5
voyageai/rerank-3-lite
qwen/qwen3-reranker-8b
Для начала проверил, что каждая модель отвечает через production-адаптер Veynrel, а ответы проходят его валидацию. Затем подготовил четыре коротких вопроса на русском и английском языках. Для каждого заранее был известен релевантный документ.
Результаты:
Модель |
Правильный Top-1 |
Средняя задержка |
Стоимость 4 запросов |
|---|---|---|---|
Cohere Rerank 3.5 |
4/4 |
490 мс |
$0.004 |
Voyage Rerank 3 Lite |
4/4 |
521 мс |
$0.0000102 |
Qwen3 Reranker 8B |
4/4 |
662 мс |
$0.0002844 |
Все три модели поставили нужные документы на первое место.
Для оценки качества такой тест оказался слишком простым. Всего четыре вопроса, к тому же обычный Semantic Search тоже правильно находил релевантные документы. Зато удалось проверить совместимость моделей и сравнить расходы.
На этой выборке Voyage оказалась примерно в 392 раза дешевле Cohere при сопоставимой задержке. Правда, у моделей разные схемы тарификации, поэтому на других объёмах текста соотношение может заметно отличаться.
Для следующего эксперимента я выбрал Voyage Rerank 3 Lite. Она прошла проверку, а стоимость запросов позволяла провести более подробный тест практически бесплатно.
Второй эксперимент: 64 заметки и 28 вопросов
Я собрал синтетическое хранилище, в котором первому этапу поиска должно быть сложнее. Идея в том, чтобы добавить несколько близких по теме документов с разной полезностью для конкретного вопроса.
В итоге 64 Markdown-заметки превратились в 80 фрагментов через MarkdownChunker Veynrel.
Часть заметок была короткой, другие состояли из нескольких разделов. В некоторых длинных документах полезная информация находилась ближе к концу, чтобы проверить, какой фрагмент будет выбран для Rerank.
Темы тоже пришлось перемешать: базы данных, блокировки, обработка файлов, резервное копирование, архитектурные решения, организация знаний, изучение языков и другие.
Всего подготовил 28 вопросов. Для 24 в корпусе существовал хотя бы один содержательно релевантный документ. Ещё четыре вопроса были составлены так, чтобы подходящего ответа в хранилище не было.
Положительные вопросы разделил на шесть групп:
Группа |
Что проверялось |
|---|---|
Перефразирование |
Одинаковый смысл при заметно разной лексике |
Отрицания и исключения |
Например, локальная обработка без отправки данных в облако |
Похожие операции |
Близкие термины, разные технические задачи |
Разные языки |
Русский запрос к английскому документу и наоборот |
Несколько источников |
Несколько полезных заметок с разной полнотой ответа |
Длинные заметки |
Ответ в одном из внутренних разделов документа |
Чтобы сравнивать результаты, нужно было заранее определить, какие заметки полезны для каждого вопроса.
Я использовал шкалу релевантности от 0 до 3:
0: документ не относится к вопросу.
1: слабая или косвенная связь.
2: полезный, но неполный ответ.
3: прямой и полноценный ответ.
Для проверки наличия релевантного документа использовал оценки 2 и 3. После подготовки корпуса я построил семантический индекс через text-embedding-3-small на production-компонентах Veynrel. Результаты относятся к конкретному синтетическому хранилищу.
Для каждого вопроса Semantic Search возвращал до 30 документов-кандидатов. Исходный порядок сохранялся, а затем те же кандидаты передавались в Voyage Rerank. Модель не получала информацию о правильных ответах. Документы также не добавлялись вручную, чтобы улучшить результат.
Embedding-запросов ради Rerank не делалось.
Как я измерял качество и что такое nDCG@10
Для основного сравнения выбрал метрику nDCG@10, или Normalized Discounted Cumulative Gain. Представим, что поиск вернул десять заметок. Среди них есть одна, которая полностью отвечает на вопрос, две с полезной информацией и несколько почти бесполезных.
Хорошая выдача должна показывать наиболее полезные материалы ближе к началу списка. Разница между первым и вторым местом при этом важнее разницы между восьмым и девятым.
nDCG учитывает сразу две вещи: насколько релевантен каждый документ и на какой позиции он находится. Чем ниже расположен полезный документ, тем меньше его вклад в итоговую оценку. При этом полноценный ответ получает больший вес, чем заметка со слабой тематической связью.
Затем результат сравнивается с идеальной сортировкой по заранее заданной разметке. Поэтому значение находится в диапазоне от 0 до 1, где 1 означает идеальный порядок.
Число 10 в названии означает, что оцениваются только первые десять результатов.
Например, если первая заметка полностью отвечает на вопрос, а вторая содержит полезное дополнение, метрика будет высокой. Если это дополнение окажется на восьмом месте, оценка снизится, даже когда первый результат останется правильным.
Почему я выбрал именно nDCG?
Потому что в Obsidian пользователь может искать сразу несколько связанных заметок. Иногда нужен один конкретный документ, а иногда хочется собрать несколько источников по теме. Положение второго, третьего и остальных полезных результатов в таком случае тоже имеет значение.
Для контроля я использовал ещё две метрики:
Hit@1 показывает, оказался ли на первом месте релевантный документ.
MRR@10 усредняет обратную позицию первого релевантного документа по всем запросам. Для отдельного запроса первое место даёт 1, второе 0.5, третье примерно 0.33. Если в первой десятке релевантного документа нет, значение для этого запроса равно 0.
Так можно отдельно посмотреть, насколько часто поиск сразу находил полезный ответ и насколько хорошо он сортировал остальные материалы.
Четыре вопроса без ответа в хранилище не входили в агрегированные nDCG, Hit и MRR для положительных запросов.
Результаты на 64 заметках
Результаты на 24 положительных запросах:
Метрика |
Только embeddings |
После Rerank |
|---|---|---|
Релевантный документ на первом месте |
23/24 |
24/24 |
Релевантный документ в Top-3 |
24/24 |
24/24 |
MRR@10 |
0.97917 |
1.00000 |
nDCG@10 |
0.94235 |
0.97502 |
По nDCG@10 вышло 11 улучшений, 11 случаев без изменения метрики и 2 ухудшения. Средняя оценка выросла примерно на 0.0327. Хотелось понять, насколько заметны эти изменения для пользователя, поэтому я решил посмотреть, какие документы переместились.

Оказалось, что в шести из одиннадцати улучшений изменились только позиции слабых вспомогательных материалов с релевантностью 1. Полезные документы с оценкой 2 или 3 остались на своих местах.
В остальных пяти случаях положение релевантных заметок улучшилось. Из этих пяти только один раз Rerank исправил первый результат, подняв нужную заметку со второго места на первое.
Более чем в половине улучшившихся запросов прирост nDCG был связан только с перестановкой слабых вспомогательных материалов.
Это может быть полезно, особенно если человек просматривает несколько заметок по одной теме. Но первоначальное ожидание, что Rerank будет регулярно исправлять первый результат, в этом эксперименте не подтвердилось.
И ещё один нюанс: 11 случаев без изменения nDCG не обязательно означают абсолютно одинаковый порядок документов. Перестановки материалов с одинаковой оценкой релевантности могут не менять итоговую метрику.
Где Rerank помог
Самое заметное улучшение появилось на вопросе о возобновлении прерванной конвертации данных.
Я смоделировал ситуацию, когда ночью компьютер выключился во время обработки. Часть результатов успела безопасно сохраниться. Нужно продолжить работу, не повторяя уже завершённые операции.
В корпусе находились две близкие по теме заметки. Первая рассказывала о плановой остановке сервиса, незавершённых задачах и сохранении идентификаторов операций. Вторая описывала контрольные точки, проверку уже сохранённых результатов и продолжение обработки после аварийного завершения.
Semantic Search поставил первой заметку о плановом обслуживании. Voyage подняла документ с контрольными точками на первое место. nDCG@10 для этого вопроса выросла с 0.5788 до 0.9640.

Пользователь сразу получил документ, который лучше отвечал на вопрос. Менее заметные улучшения встречались и на запросах, связанных с техническими операциями. Например, когда несколько заметок использовали близкие термины, но описывали разные механизмы.
В таких случаях сравнение вопроса с содержимым документов помогало точнее расставить найденные материалы.
Где стало хуже
Один из запросов касался изучения иностранных языков. Человек узнаёт выражение при чтении, но во время разговора не может вспомнить его самостоятельно. Какие упражнения помогут перенести знакомые выражения в активную речь?
Обычный Semantic Search поставил первой заметку о тренировке активного воспроизведения. После Rerank она сохранила своё место.
Однако другая полезная заметка, где описывалась комплексная практика, опустилась с четвёртой позиции на восьмую. Выше оказались более слабые материалы о перечитывании и различении звуков.nDCG@10 снизилась с 0.9149 до 0.8872.

Вторая регрессия была связана с резервным копированием. Вопрос касался проверки того, что зашифрованный бэкап действительно позволит восстановить файлы после потери рабочего компьютера.
Главный документ остался первым, но заметка о практической проверке восстановления опустилась ниже более общего материала об изоляции резервной копии. В обоих случаях первый результат оставался полезным. Ухудшения затронули следующие позиции выдачи.
Я проверил, какие фрагменты получила модель. Содержимое нужных документов присутствовало в запросах к модели, поэтому потерей текста при подготовке Rerank эти регрессии не объясняются.
На четырёх многоязычных запросах nDCG не изменилась. Первоначальный поиск уже правильно находил полезные материалы, а ранжирование не улучшило их расположение относительно разметки.
Один из них выполнялся больше четырёх секунд.
По четырём вопросам трудно делать выводы о работе Rerank на разных языках. Но для конкретного набора это были расходы без изменения метрики.
Сколько это стоило
Во втором эксперименте было выполнено 28 Rerank-запросов и два пакетных embedding-вызова для корпуса и поисковых вопросов.
Общая подтверждённая стоимость составила $0.00243830. Из них на 28 вызовов Rerank пришлось $0.00223506, на два embedding-вызова $0.00020324. В среднем одно уточнение обошлось примерно в $0.00008. Это среднее по прогону, стоимость отдельных запросов различалась.
В первом коротком тесте один вызов Voyage стоил в среднем $0.00000255. Во втором эксперименте средняя стоимость Rerank выросла примерно в 31 раз. Стоимость зависит от количества кандидатов, размера текстов и тарифа модели: в первом тесте документов в запросе было меньше, а здесь на уточнение уходило до 30 кандидатов.
Задержки:
Показатель |
Время |
|---|---|
Средняя задержка |
675 мс |
Медиана |
516 мс |
p95 |
1042 мс |
Самый долгий запрос |
4020 мс |
Я измерял работу Rerank-провайдера: сетевой запрос и обработку ответа. Время построения индекса, подготовки тестового окружения и сверки расходов в эти значения не входит.
В моём эксперименте деньги оказались наименее заметным ограничением. Гораздо заметнее была задержка. Если человек сам нажал кнопку, лишние полсекунды терпимы. Но ждать вторую модель при каждом поиске быстро надоест.
Ещё я посмотрел на четыре вопроса, для которых в хранилище не было подходящих ответов.
Rerank всё равно возвращал упорядоченный список документов. Это ожидаемое поведение, ведь модель ранжирует переданных кандидатов. Высокий score в таком случае сам по себе не доказывает, что нужная информация есть в хранилище.
Третий эксперимент: 500 заметок
Когда статья уже была почти готова, я решил проверить поиск ещё на одном хранилище. У меня был демонстрационный vault на 500 заметок, который я использовал для скриншотов Veynrel. На нескольких пробных запросах Rerank заметно переставлял результаты, но по названиям заметок было трудно понять, стала ли выдача полезнее.
Возиться с разметкой для 500 заметок мне было лень, и сначала я хотел ограничиться парой пробных запросов. Но по ним было трудно понять, стала ли выдача полезнее. Поэтому я всё-таки организовал отдельный прогон, а разметку и её проверку поручил агентам.
Для проверки я использовал опубликованную версию 2.1.0. Все 500 Markdown-файлов попали в индекс и превратились в 1600 фрагментов. Содержимое исходного хранилища не менялось, проверка выполнялась на отдельной копии.
Первоначально планировал подготовить 80 разных вопросов с ответами и ещё 20 без ответа. После полного чтения заметок план пришлось сократить.
Хранилище оказалось слишком шаблонным. В заметках были названия технологий, общие описания, ссылки и вопросы для повторения. При этом конкретных технических объяснений для большинства задуманных поисковых задач там не было.
Название заметки могло совпадать с темой вопроса, но самого ответа в тексте не оказывалось. Добирать положительные вопросы за счёт такого совпадения я не стал.
В итоге осталось четыре самостоятельных вопроса об общих приёмах работы с заметками и 20 диагностических вопросов, для которых ответа в корпусе не было. Всего 24 разных поисковых намерения, поровну на русском и английском языках. Сами заметки оставались англоязычными.
Оценены все 12 000 пар «вопрос–документ». Разметку подготовил агент, затем её проверил отдельный LLM-агент без доступа к первоначальным оценкам, поисковым рангам и результатам моделей. Обнаруженное расхождение разрешили до получения результатов моделей, итоговые оценки зафиксировали. Независимой человеческой проверки разметки не было.
И здесь возникла проблема с документными метриками.
По этой разметке каждая из 500 заметок содержала полный ответ на каждый из четырёх общих положительных вопросов. Повторяющийся текст давал достаточно информации, поэтому все документы получили оценку релевантности 3. Hit@1, Hit@3 (попадание в Top-3), MRR@10 и nDCG@10 оказались равны 1 и до Rerank, и после.
При такой разметке любая перестановка документов сохраняет идеальную оценку. Получилось 0 улучшений, 4 случая без изменения метрики и 0 ухудшений. Эти единицы ничего не говорят о способности моделей различать сложные технические ответы: одинаковые оценки документов просто не позволяют заметить разницу в порядке.
Но Veynrel передаёт Rerank только один выбранный фрагмент на заметку. Ответ мог находиться в другом разделе того же документа.
Содержимое 120 фрагментов, выбранных для четырёх положительных вопросов, оценили отдельным агентским проходом без поисковых рангов и результатов моделей. В 93 находился полный ответ, в 14 частичный, ещё 13 относились к неподходящему разделу. У всех 27 неполных или неподходящих фрагментов полный ответ был в другом чанке той же заметки.
Все три модели получили одинаковые запросы, кандидаты и подготовленные тексты. Затем я посчитал, сколько фрагментов с полным ответом оказалось в первой десятке:
Полных ответных фрагментов в top-10 |
Исходный поиск |
Voyage |
Cohere |
Qwen |
|---|---|---|---|---|
Q001 |
10 |
10 |
10 |
10 |
Q002 |
5 |
10 |
9 |
10 |
Q003 |
4 |
10 |
10 |
10 |
Q004 |
10 |
10 |
10 |
10 |
Всего |
29/40 |
40/40 |
39/40 |
40/40 |
Q001–Q004 — это четыре положительных вопроса, а 40 в итоговой строке означает суммарное число позиций в четырёх выдачах top-10. Самостоятельных положительных вопросов по-прежнему всего четыре. Изменение затронуло два из них, на остальных двух все десять фрагментов уже содержали полный ответ.
Rerank не перечитывал заметки и не выбирал новые разделы. Он поднимал выше кандидатов, у которых уже подготовленный фрагмент содержал нужную информацию. Кандидаты с частичным ответом или неподходящим разделом уходили ниже.
Документная nDCG этого изменения не показала. На уровне целых заметок все результаты оставались одинаково полезными, а среди выбранных фрагментов полных ответов стало больше. Это отдельное наблюдение, его нельзя прибавить к 11 улучшениям из эксперимента на 64 заметках.

Весь дополнительный прогон стоил $0.01263, включая первоначальную индексацию, embeddings вопросов и 32 Rerank-вызова. Voyage обработала все 24 вопроса, Cohere и Qwen по четыре положительных вопроса для сравнения. Все вызовы прошли успешно. Средняя задержка Voyage на полном наборе составила 723 мс, медиана 603 мс, p95 1871 мс.
Количество файлов выросло, но содержательных поисковых задач в этом корпусе оказалось мало. Поэтому выводов о качестве на больших технических хранилищах я из него не делаю. Зато проверка показала, почему полезно отдельно оценивать целый документ и фрагмент, который фактически участвует в уточнении.
Какие хранилища могут получить больше пользы от Rerank
В корпус из 64 заметок я специально добавлял тематически близкие документы, чтобы усложнить поиск. Проверка хранилища на 500 заметок показала другое ограничение: большое число файлов может скрывать повторение одного и того же содержания.
Допустим, человек использует Obsidian как личный дневник. У него несколько сотен заметок: планы, короткие мысли, списки дел, конспекты книг. Темы практически не пересекаются, а запросы обычно конкретные. В таком хранилище обычные embeddings могут справляться самостоятельно. Если нужная заметка уже находится первой, вторая модель вряд ли заметно изменит повседневный опыт.
Теперь представим другое хранилище. Несколько тысяч технических заметок, документация проектов, конспекты по архитектуре, записи об ошибках, решения инцидентов и десятки похожих механизмов.
Например, описаны:
повторная доставка сообщений и идемпотентность;
дедупликация содержимого и дедупликация запросов;
восстановление базы из резервной копии;
восстановление очереди после сбоя;
optimistic locking и обычные блокировки.
Многие документы содержат одинаковые слова. Некоторые даже описывают похожие ситуации, но предлагают разные решения.
Именно здесь Rerank, скорее всего, нужнее. Первому этапу нужно найти подходящую группу документов, после чего второй этап может подробнее сравнить их с вопросом.
Ещё один пример: учебное хранилище, где одна тема раскрыта сразу в нескольких заметках. Есть короткое определение, подробный разбор, практический пример и список исключений. Все четыре документа могут быть релевантны, но в разной степени.
Если человек спрашивает, как применить механизм на практике, ему желательно сначала увидеть пример, а затем уже определение и дополнительные материалы.
Похожая ситуация возможна и в RAG-системах, где порядок найденных фрагментов влияет на то, какой контекст попадёт в ограниченное окно модели.
Однако рост хранилища сам по себе не гарантирует пользы от Rerank. Многое зависит от качества первого этапа, а прогонять через Rerank всю базу без предварительного отбора кандидатов слишком дорого.
Кроме того, Veynrel отправляет на уточнение один выбранный фрагмент на заметку. Для длинных документов качество выбора этого фрагмента тоже имеет значение.
Выходит, что Rerank полезнее всего там, где семантический поиск уже нашёл несколько подходящих кандидатов, но их порядок вызывает вопросы.
Насколько часто это происходит в хранилищах на несколько тысяч заметок, я пока не измерял. Для следующего эксперимента хотелось бы взять реальные данные с независимой разметкой, чтобы проверить эту гипотезу.
Что именно уходило наружу
Во время проверки запросов к провайдеру я обнаружил ошибку, которую легко было пропустить.
В Veynrel MarkdownChunker учитывает структуру заметок, включая заголовки и их иерархию. Если у заметки нет Markdown-заголовка, в некоторых случаях используется имя файла как запасной заголовок.
Для локального индекса это удобно. Но при подготовке Rerank я сначала использовал уже собранный chunk.text.
Из-за этого имя файла могло попасть прямо в тело внешнего запроса, хотя поля с путями в сериализаторе отсутствовали.
Например, файл Unique-headerless-name.md содержит одну строку:
Ordinary body.
А наружу мог отправиться такой текст:
Unique-headerless-name Ordinary body.
Имя добавил сам chunker, в оригинальном Markdown его не было.
Самое неприятное, что проверка актуальности при этом работала правильно. Идентификатор фрагмента и хеш содержимого (contentHash) совпадали с данными индекса. Технически плагин отправлял именно тот фрагмент, который ранее сохранил.
Проблема оказалась в служебном контексте, добавленном во время обработки.
Удалить первую строку было бы слишком просто и опасно. В реальных заметках первая строка часто является настоящим заголовком. Строковая замена по имени файла тоже могла затронуть пользовательский текст.
Поэтому пришлось учитывать происхождение заголовка: написан ли он пользователем или добавлен chunker’ом автоматически.
Теперь у фрагмента есть два представления. Первое сохраняет прежнее содержимое для индекса и совместимости с существующими данными. Второе формируется для отправки внешней модели.
Перед подготовкой исходящего текста по-прежнему проверяются исходные id и contentHash. Затем из тех же проверенных блоков собирается текст без автоматически добавленного filename breadcrumb.
Если безопасно сформировать текст не удаётся, операция не отправляет старый фрагмент на всякий случай.
Формат индекса менять не пришлось. Существующим пользователям не нужна переиндексация после обновления.

При этом исправление касается исходящих фрагментов Rerank и Decisions (экспериментальной функции сравнения двух фрагментов, о ней ниже). Настоящие заголовки, имена, ссылки и пути, которые пользователь написал внутри заметки, сохраняются. Другие функции Veynrel используют собственные маршруты обработки данных.
Это напоминание проверять содержимое HTTP-запросов при работе с облачными моделями. Даже если мы убрали поля с именами файлов, служебная информация всё ещё может оказаться внутри текста.
Почему в итоге я сделал кнопку «Уточнить результаты»
Сначала я собирался запускать Rerank автоматически после каждого семантического поиска. Технически это удобно: получаем кандидатов, отправляем модели, показываем уточнённый порядок.
Но результаты тестов заставили передумать.
В эксперименте на 64 заметках в 23 из 24 положительных вопросов первый полезный документ уже находился на первом месте. Rerank иногда улучшал следующие позиции, иногда оставлял метрику прежней, а дважды ухудшал выдачу.
Проблема в том, что у меня нет измерений, которые позволили бы заранее понять, когда второй этап поможет, или надёжно решать, какой запрос достаточно сложный для его запуска. Для этого нужна проверенная ручная разметка на большом количестве заметок.
Поэтому в Veynrel 2.1.0 я выбрал ручной режим для новых настроек.
Теперь пользователь вводит вопрос и получает обычные результаты. Если выдача кажется недостаточно точной, он нажимает «Уточнить результаты».
Только после этого плагин запускает Rerank.
Возникла ещё одна небольшая инженерная задача.
Если при нажатии кнопки заново выполнять Semantic Search, придётся повторять embedding-запрос и весь поиск по индексу. В этом нет необходимости, поскольку первоначальные кандидаты уже были найдены.
Поэтому сейчас при первом поиске Veynrel сохраняет в памяти до 30 кандидатов, но показывает пользователю первые 10.
Ручное уточнение использует этот сохранённый пул. Повторный embedding-запрос не выполняется.
После завершения Rerank можно переключаться между исходным и уточнённым порядком. Переключение локальное, обращений к API нет.

Но сохранённый пул создаёт новые вопросы.
Например, пользователь может изменить заметку между поиском и нажатием кнопки. Или поменять embedding-провайдера, поисковый запрос либо настройки Rerank. За это время может измениться и сам индекс.
Чтобы не отправлять устаревшие данные, поисковая сессия привязана к состоянию запроса, индекса, источников и настроек.
Перед отправкой фрагменты проверяются повторно. Если данные изменились, старая сессия становится недействительной и пользователю предлагается выполнить поиск заново.
Двойное нажатие на кнопку не создаёт два одновременных запроса. Поздний ответ от старого поиска не может заменить новую выдачу. Если Rerank завершился с ошибкой, исходные результаты остаются доступны.
Есть и ограничение: когда HTTP-запрос уже отправлен, закрытие окна или отмена ожидания не гарантируют, что провайдер остановит обработку и не спишет деньги.
Автоматический режим я тоже сохранил. Он доступен в настройках, а у пользователей, которые включили его раньше, прежнее поведение сохраняется после обновления.
В новых установках Rerank выключен по умолчанию. После настройки провайдера пользователь может запускать его вручную.
Что получилось в Veynrel 2.1.0
В итоге Semantic Search продолжает работать на embeddings, а Rerank используется для сортировки найденных кандидатов.
Для новых настроек стартовой моделью выбрана voyageai/rerank-3-lite. При желании можно указать другую поддерживаемую Rerank-модель OpenRouter. Ранее сохранённые модели и настройки пользователей остаются прежними.
В этом же релизе появилась экспериментальная функция Decisions, о которой я упоминал выше.
Она позволяет по явному запросу оценить смысловое пересечение двух выбранных фрагментов заметок. Модель определяет, передают ли они одну и ту же информацию, частично пересекаются или относятся к общей теме.
Я проверял её на небольшом тестовом наборе. Результаты пока неоднозначные: встречались ошибки, особенно когда контекста было недостаточно. Поэтому Decisions используется как подсказка при ручном анализе. Автоматического объединения или удаления заметок на основании её оценок нет.
У неё интересная техническая реализация, связанная со специализированной моделью и структурированными ответами. Если тема интересна, могу рассказать о Decisions, её устройстве, стоимости и результатах тестирования. Здесь всё-таки хотелось подробнее разобрать именно Rerank.
Сам Veynrel 2.1.0 уже опубликован. Перед выпуском я проверил обновление с предыдущей версии, сохранение настроек и семантического индекса, работу ручного уточнения через OpenRouter API и соответствие релизной сборки исходному коду.
Что дальше
После этих экспериментов мне стало интереснее проверить Rerank на содержательном техническом хранилище, где много похожих документов и несколько подходящих источников для каждого вопроса.
Хочется понять, насколько часто второй этап сможет исправлять первый результат в таких условиях. Ещё интереснее выяснить, можно ли надёжно определять сложные запросы, чтобы предлагать уточнение только тогда, когда от него ожидается польза.
Но пока я решил остановиться на ручном режиме. На корпусе из 64 заметок обычный семантический поиск уже справлялся хорошо. Вторая модель помогала в отдельных случаях, а иногда ухудшала порядок следующих документов.
В демонстрационном хранилище из 500 заметок Rerank поднял выше фрагменты с полными ответами на двух из четырёх вопросов, хотя документные метрики этого не отразили. Пользователь теперь может сравнить результаты и сам решить, нужен ли ему второй этап.
Попробовать
Veynrel доступен как Community Plugin для Obsidian. Исходный код открыт:
В протоколе эксперимента на 64 заметках есть корпус, разметка релевантности, исходные кандидаты и результаты по каждому запросу. Можно посмотреть, какие документы перемещались и где именно менялась оценка.
В отчёте по хранилищу на 500 заметок опубликованы методика и агрегированные результаты. Полные тексты заметок, запросы и разметка этого прогона остались локально.
Если кто-то использует Rerank в поиске по документации, личной базе знаний или RAG, мне особенно интересно, как часто он у вас исправляет первую позицию. И насколько заметна польза на хранилищах с тысячами похожих документов.
Выходит немного иронично. Я собирался добавить ещё один автоматический этап поиска, провёл несколько экспериментов и в итоге выпустил кнопку, которую пользователь может ни разу не нажать.
Кажется, для поисковой функции это нормальный результат. Не каждый новый этап обязан давать грандиозные улучшения, иногда это просто подтверждение того, что и до этого всё работало неплохо.