Типичная картина в плохо работающем RAG: нужный фрагмент находится, но оказывается седьмым, а в промпт уходят первые пять. Индекс в порядке, эмбеддер в порядке, данные на месте — виноват порядок. И это единственная поломка поиска, которую можно починить, не трогая ни хранилище, ни модель-эмбеддер, ни нарезку текста.
Инструмент называется переранжированием.
#Короткий ответ
Переранжирование — это второй проход по уже найденным кандидатам. Первый этап (полнотекстовый поиск, векторный или их слияние) отбирает три-четыре десятка кандидатов быстро и грубо. Второй этап берёт каждую пару «запрос — кандидат» и оценивает релевантность отдельной моделью, которая читает запрос и текст вместе, а не сравнивает два заранее посчитанных вектора.
Разница в этом слове — «вместе». Эмбеддер кодирует документ один раз, ничего не зная о будущем запросе. Cross-encoder видит и то и другое одновременно, поэтому улавливает соответствие, которого не видно при сравнении независимых векторов: отрицания, роли участников, оговорки.
#Как устроен двухэтапный поиск
запрос
├─ этап 1: отбор кандидатов (быстро, приблизительно)
│ полнотекстовый bm25 → 40 штук
│ векторный KNN → 40 штук
│ слияние рангов → ~60 уникальных
└─ этап 2: переранжирование (медленно, точно)
cross-encoder оценивает 60 пар «запрос — текст»
сортировка по оценке
→ в промпт уходят первые 5–6
Асимметрия этапов — не компромисс, а суть подхода. Первый этап обязан быть дешёвым, потому что просматривает всё хранилище. Второй может быть дорогим, потому что просматривает шесть десятков записей. Заменить первый этап вторым нельзя: cross-encoder по всему архиву — это чтение каждого документа на каждый запрос.
#Что это даёт и чем платит
| Что меняется | Направление | Замечание |
|---|---|---|
| Порядок в топе | улучшается заметно | самый сильный эффект — когда кандидатов много, а мест мало |
| Полнота выдачи | не меняется вовсе | чего нет среди кандидатов, того не будет и после |
| Задержка ответа | растёт | добавляется на каждый запрос, а не на индексацию |
| Место на диске | не меняется | индекс не трогается |
| Стоимость внедрения | одна модель | без переиндексации и миграций |
Вторая строка — главное ограничение и главное недоразумение. Переранжирование не исправляет плохой отбор: если нужный фрагмент не попал в сорок кандидатов, никакая пересортировка его не вернёт. Прежде чем ставить rerank, проверьте, что нужное вообще находится хоть на каком-то месте, — иначе вы чините не ту поломку. Как отладить сам отбор, разобрано в разборе гибридного поиска и в материале про стратегии чанкинга.
#Цена в задержке — и почему она решающая
Здесь честная часть. Cross-encoder делает столько же проходов, сколько у вас кандидатов: шестьдесят кандидатов — шестьдесят прогонов модели. На процессоре с небольшой многоязычной моделью это заметно на глаз; на слабой машине — заметно неприятно.
Конкретных миллисекунд здесь не будет: они зависят от модели, длины фрагментов, числа кандидатов и вашего железа, и любая цифра из чужого блога вас обманет. Методика замера простая и занимает пять минут:
- Замерьте время ответа ассистента без rerank на десяти реальных вопросах, запишите медиану.
- Включите rerank с тем же числом кандидатов, повторите те же десять вопросов.
- Разница медиан — ваша цена. Соотнесите её с тем, сколько запросов реально выиграли по качеству.
Второй шаг обязательно на тех же вопросах: сравнение с разными наборами не значит ничего.
Практическое правило: переранжирование окупается там, где ответ и так не мгновенный. Если ассистент отвечает секунду, лишние полсекунды — треть времени ожидания. Если ответ идёт восемь секунд, потому что локальная модель генерирует текст, лишние полсекунды не заметит никто. Что именно составляет задержку до первого символа, разобрано в справке про латентность и TTFT.
#Как это сделано в Persona
Переранжирование в продукте есть, и оно выключено по умолчанию — это стоит сказать прямо, потому что «есть функция» и «функция работает у вас» разные вещи.
Механика: после слияния списков функция _rerank в app/memory_vec.py проверяет ключ настройки reranker_enabled, и только при явно выставленной единице подгружает cross-encoder из пакета fastembed (модель Xenova/bge-reranker-base, многоязычная, считается на процессоре). Если пакета нет, ключ не выставлен или модель не загрузилась — список возвращается в исходном порядке, без ошибки и без сообщения.
Три следствия, о которых честно стоит знать:
- Пакет не входит в зависимости. Как и расширение для векторов, cross-encoder — необязательная часть; отсутствие его не ломает ничего.
- Отдельного переключателя на странице настроек нет. Ключ выставляется как настройка движка памяти, а не отдельной галочкой.
- Переранжирование применяется только в векторно-гибридной ветке. В режиме по умолчанию — поиске по ключевым словам — этот шаг не вызывается вовсе.
Логика та же, что и с векторами: необязательная надстройка, которая при отсутствии условий тихо не срабатывает. Плата за это — неочевидность: понять, работает ли rerank, по качеству выдачи нельзя, надо смотреть конфигурацию.
#Чем ещё можно поправить порядок
Cross-encoder — не единственный способ, и не всегда лучший для персональной памяти.
Учёт свежести и важности. Для памяти о человеке недавнее и важное почти всегда предпочтительнее старого и проходного, независимо от текстового сходства. В Persona есть такая пересортировка: она комбинирует убывание по времени с момента последнего обращения, оценку важности записи и релевантность из слияния, нормируя все три составляющие по набору кандидатов. Она тоже опциональна и по умолчанию выключена.
Разнообразие. Жадный проход, который выбирает следующий фрагмент с поправкой на непохожесть на уже выбранные, спасает от выдачи из пяти перефразов одного факта. Стоит он почти ничего — только сравнение текстов между собой.
Фильтр эха. Дешёвый и недооценённый приём: выбросить из кандидатов те записи, которые по сути повторяют сам вопрос, а не отвечают на него. В переписке один и тот же вопрос задают многократно, и без фильтра он вытесняет из выдачи собственно ответ.
Три приёма выше стоят на порядки дешевле cross-encoder и часто дают на персональных данных сопоставимый эффект. Начинать разумно с них.
#Частые вопросы
#Чем cross-encoder отличается от модели-эмбеддера?
Эмбеддер кодирует текст независимо от запроса, поэтому его результат можно посчитать заранее и сохранить. Cross-encoder обрабатывает запрос и документ вместе и потому не кэшируется — он работает только во время запроса. Отсюда и разница в скорости, и разница в точности. Какие эмбеддеры отвечают за первый этап и чем они различаются на русском — в обзоре embed-моделей.
#Сколько кандидатов подавать на переранжирование?
Столько, сколько вы готовы оплатить временем: стоимость линейна по числу кандидатов. Разумный старт — несколько десятков, из которых в промпт уходят единицы. Меньше двадцати обычно не имеет смысла: если правильный ответ и так в первой пятёрке, переранжировать нечего.
#Заменяет ли rerank слияние результатов?
Нет, они на разных этапах. Слияние собирает кандидатов из нескольких источников по рангам — разбор в материале про RRF. Переранжирование упорядочивает уже собранный список по содержанию. Схема «слияние, потом rerank» стандартна.
#Нужен ли GPU?
Для небольших многоязычных cross-encoder — нет, они считаются на процессоре. Но именно на процессоре цена в задержке максимальна, поэтому решение «ставить или нет» стоит принимать после замера на своём железе, а не по факту «модель маленькая».
#Может ли переранжирование ухудшить выдачу?
Может, и это не редкость: модель, обученная преимущественно на английском, на русском тексте способна переставить релевантное вниз. Поэтому замер делается до постоянного включения, а не после.
#Что даёт больше — rerank или улучшение отбора?
Почти всегда улучшение отбора. Переранжирование работает с тем, что уже нашлось, а самая частая поломка — что нужное не находится вовсе. Общая схема слоёв, где видно место каждого этапа, описана в статье о том, как работает память у ИИ.
#Вывод
Ставьте переранжирование, если нужный фрагмент у вас находится, но не попадает в первую пятёрку, и если ответ ассистента и без того идёт секунды. Не ставьте, если проблема в том, что фрагмент не находится вообще — сначала чините отбор. И перед постоянным включением обязательно замерьте оба показателя на одних и тех же десяти вопросах: и прирост попаданий, и добавленную задержку. Без второго числа решение принимается вслепую.