ДокументацияПрактические руководства (Cookbooks)Переранжирование поиска (Re-ranking)

Переранжирование поиска (Re-ranking)

Builds 30-passage BM25 shortlists for 40 CLERC legal queries, then uses one TypeSafe question per query-candidate pair to raise top-1 accuracy from 5% to 18% and top-10 accuracy from 38% to 62%.

Формирует шортлисты из 30 отрывков с помощью BM25 для 40 юридических запросов CLERC, а затем использует один вопрос TypeSafe на каждую пару «запрос–кандидат», повышая точность top-1 с 5% до 18%, а точность top-10 — с 38% до 62%.

У вас есть тысячи документов, и вам нужно найти тот единственный, который отвечает на конкретный вопрос. Как его найти?

Сначала используйте быстрый метод, например поиск по ключевым словам, чтобы сократить тысячи кандидатов до небольшого списка наиболее вероятных. Мы называем это быстрым поиском (fast search).

Быстрый поиск отлично справляется с этой задачей, но он не может определить, какой именно кандидат из шортлиста является правильным. Здесь на помощь приходит переранжирование (re-ranking). Оно оценивает каждого кандидата из шортлиста напрямую относительно запроса и ставит наилучший вариант на первое место.

Оба этапа выполняются ниже на 3 565 фрагментах судебных решений из датасета CLERC: BM25 формирует шортлист быстрого поиска из 30 кандидатов для каждого из 40 запросов, после чего TypeSafe переранжирует каждый шортлист. Благодаря переранжированию правильный фрагмент оказывается на первом месте в 18% запросов по сравнению с 5% при использовании только быстрого поиска.

В процессе вы узнаете:

  • Что делает быстрый поиск и почему одного его недостаточно
  • Что такое переранжирование и как оно встраивается после этапа быстрого поиска
  • Как TypeSafe оценивает кандидата относительно запроса и насколько это улучшает результат

Попробуйте сами

Открыть запрос, кандидата и вопрос переранжирования в песочнице TypeSafe

Как найти один документ среди тысяч?

У вас есть массив документов и запрос — фрагмент текста, описывающий то, что вы ищете. Где-то в этом массиве находится единственный документ, отвечающий на него.

Поочередная проверка каждого документа относительно запроса технически работает: по одному сравнению на документ, однако миллионы документов означают миллионы сравнений на каждый запрос. Производительность можно кардинально повысить с помощью двухэтапного подхода:

  1. Сократить массив до короткого списка вероятных кандидатов с помощью метода, достаточно быстрого для обработки всего массива.
  2. Применить более точный этап к этому шортлисту, чтобы найти точный правильный ответ.

Анимированная диаграмма: массив документов сужается до шортлиста быстрого поиска, затем переранжирование упорядочивает шортлист так, что правильный ответ поднимается наверх

В этом руководстве данная схема тестируется на датасете судебных решений в разделе Пример переранжирования ниже.

Что такое быстрый поиск?

Быстрый поиск — это любой метод, способный сравнить запрос со всеми документами в большом корпусе и быстро вернуть ранжированный шортлист. К распространенным методам относятся поиск по ключевым словам (например, BM25) и плотные векторные эмбеддинги (dense embeddings), сравнивающие фрагменты по смыслу. Системы часто комбинируют оба подхода.

На первом этапе здесь используется только BM25. BM25 ранжирует фрагменты по пересечению слов. Простота этого шага позволяет сосредоточиться на переранжировании, ради которого и создано это руководство. Выбор метода быстрого поиска вторичен: переранжирование работает только с теми фрагментами, которые попали в шортлист.

Что такое переранжирование?

Переранжирование берет шортлист, уже сформированный быстрым поиском, и упорядочивает его более качественно. Вместо сравнения запроса со всем корпусом сразу оно сравнивает запрос с каждым кандидатом из шортлиста по отдельности и сортирует шортлист по этой оценке.

Диаграмма: ранжированный шортлист слева, стрелка «переранжирование» и упорядоченная версия справа, где правильный ответ перемещается из середины наверх

Оценка может формироваться языковой моделью. Передайте ей запрос и одного кандидата вместе и спросите, насколько хорошо кандидат отвечает на запрос. Переранжирование позволяет найти наилучшее совпадение в шортлисте, даже если его формулировки отличаются от слов в запросе.

Переранжирование с помощью TypeSafe

Системе переранжирования требуется сопоставимая оценка для каждой пары «запрос–кандидат». Универсальная языковая модель может выдавать такие оценки или ранжировать весь шортлист целиком. Однако для независимой оценки пар необходимо определять шкалу баллов и составлять промпт так, чтобы модель применяла единый стандарт к каждому кандидату. Повторные вызовы все равно могут давать разные баллы для одной и той же пары, а генерация текста универсальной моделью увеличивает время и стоимость задачи, для которой требуется всего одно число.

Что возвращает TypeSafe

С TypeSafe запрос на оценку может оставаться простым вопросом «да/нет»:

TEXT THEME={NULL} api.wedstack.ru/v1
Could this candidate passage be from the cited precedent?

Простого ответа «да» или «нет» было бы недостаточно для ранжирования 30 кандидатов. Вместо этого Noul возвращает число от 0 до 1, называемое noul. Noul — это оценка TypeSafe того, насколько вероятен ответ «да».

Критерии вопроса определяют, что считать истиной, а что — ложью. TypeSafe применяет их к каждой паре «запрос–кандидат» и возвращает значение noul напрямую. Это значение и служит баллом, по которому приложение выполняет сортировку. Нет необходимости придумывать шкалу баллов для универсальной модели, а TypeSafe спроектирован так, чтобы выполнять такое повторяющееся скорирование быстрее, дешевле и стабильнее.

В упрощенном псевдокоде один вызов оценки TypeSafe выглядит так:

PYTHON THEME={NULL} api.wedstack.ru/v1
question = Noul(
    instructions="Is this candidate the cited case?",
    criteria=NoulCriteria(
        true="The candidate states the specific rule the query cites.",
        false="The candidate is only on a similar topic.",
    ),
)
response = client.system_one(state={...}, questions={"is_cited_source": question})
response.answers["is_cited_source"].noul  # -> 0.87

TypeSafe оценивает запрос и одного кандидата вместе относительно этого вопроса и возвращает noul.

Вы можете использовать это для переранжирования шортлиста, задавая один и тот же вопрос по каждому кандидату из него, а затем сортируя шортлист по убыванию полученного значения noul.

PYTHON THEME={NULL} api.wedstack.ru/v1
nouls = {candidate: ask_typesafe(query, candidate) for candidate in shortlist}
reranked = sorted(shortlist, key=lambda c: nouls[c], reverse=True)  # highest noul first

На диаграмме ниже показано, как один запрос на каждого кандидата формирует оценки, используемые для упорядочивания шортлиста.

MERMAID ACTIONS={TRUE} THEME={NULL} api.wedstack.ru/v1
flowchart LR
    q["фрагмент запроса<br/><i>один отрывок судебного решения,<br/>цитата удалена</i>"]
    sl["шортлист быстрого поиска<br/><i>30 кандидатов</i>"]
    quest["<b>один Noul</b><br/>может ли этот кандидат быть<br/>из цитируемого прецедента?<br/><i>критерии задают true и false</i>"]

    %% direction LR inside an LR chart keeps each state beside its noul, two columns,
    %% so the fan-out is four rows tall instead of eight
    subgraph fan["один запрос на кандидата · запросы изолированы"]
        direction LR
        d1["state<br/>{query, кандидат 1}"] --> n1["noul<br/>0.87"]
        d2["state<br/>{query, кандидат 2}"] --> n2["noul<br/>0.41"]
        dx["⋮"] --> nx["⋮"]
        d30["state<br/>{query, кандидат 30}"] --> n30["noul<br/>0.12"]
    end

    sort["сортировка по noul,<br/>по убыванию"]
    out["переранжированный шортлист<br/><i>те же 30, лучший порядок</i>"]

    q --> fan
    sl --> fan
    quest --> fan
    fan --> sort --> out

    %% the elision is not a node - drop its box so it reads as "and so on"
    classDef elide fill:none,stroke:none
    class dx,nx elide
    linkStyle 2 stroke:none

Пример переранжирования

Быстрый поиск и переранжирование выполняются на датасете CLERC, предназначенном для оценки информационного поиска в юридической сфере. В этом примере используются 3 565 фрагментов судебных решений и 40 запросов.

Настройка

На первом шаге устанавливаются пакеты, необходимые для работы руководства:

  • bm25s и datasets формируют шортлист быстрого поиска.
  • typesafe-sdk и cooksafe отвечают за переранжирование и кэширование API.
  • matplotlib строит графики с результатами.
BASH THEME={NULL} api.wedstack.ru/v1
pip install bm25s datasets matplotlib "typesafe-sdk>=0.5.7" cooksafe --extra-index-url https://pypi.typesafe.ai/

Следующий блок инициализирует клиент TypeSafe и константы, используемые в руководстве: модель TypeSafe и размер шортлиста, который быстрый поиск передает модулю переранжирования. Для вызова TypeSafe требуется TYPESAFE_API_KEY.

PYTHON THEME={NULL} api.wedstack.ru/v1
import hashlib
import json
import os
import random
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path

import msgspec
from cooksafe import JsonCache
from IPython.display import display
from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient

TYPESAFE_MODEL = "jev-1.12"
PRICE = (
    0.042,
    0.00,
)  # $ per 1M tokens (input, output); TypeSafe jev-1.12 as of 2026-08
N_ROWS = 170  # CLERC rows pooled into the shared corpus
N_QUERIES = 40  # rows we evaluate
TOP_K = 30  # candidates the shortlist hands to the re-ranker, per query

client = TypeSafeClient(
    api_key=os.environ.get(
        "TYPESAFE_API_KEY", "cache-only"
    ),  # keyless kernels replay the cache
    base_url=os.environ.get("TYPESAFE_ENDPOINT"),
    timeout=120.0,
)
json_cache = JsonCache(Path("json_cache.json"))

Ранжирование фрагментов с помощью быстрого поиска

Используемый здесь датасет представляет собой корпус судебных решений США, объединенный из 170 строк. Каждая строка устроена следующим образом:

  • Query (запрос): фрагмент судебного решения с удаленной цитатой.
  • Gold (эталон): фрагмент, на который указывала удаленная цитата, — единственный правильный ответ на запрос.
  • Candidates (кандидаты): все остальные фрагменты в корпусе, с которыми запрос мог бы быть ошибочно сопоставлен.

Из 170 строк 40 отобраны для оценки в качестве запросов. Остальные 130 выступают исключительно в роли кандидатов.

Следующая ячейка строит шортлист по описанной выше методике:

  1. Загрузить корпус.
  2. Проранжировать его для каждого запроса с помощью BM25.

Здесь пока нет TypeSafe — это исключительно этап быстрого поиска.

PYTHON EXPANDABLE THEME={NULL} api.wedstack.ru/v1
output

Быстрый поиск редко ставит правильный фрагмент на первое место

На графике показано, куда быстрый поиск помещает правильный фрагмент среди 3 565 кандидатов.

Быстрый поиск надежно сужает корпус до шортлиста, содержащего правильный ответ: он присутствует в шортлисте для 100% из 40 запросов. Однако этот фрагмент крайне редко оказывается на первом месте — всего в 5% случаев.

Переранжирование ниже лишь меняет порядок 30 кандидатов, уже отобранных в шортлист. Оно не может добавить фрагмент, который быстрый поиск не выбрал. В данном случае шортлист содержит правильный фрагмент для всех 40 запросов, поэтому переранжирование может сосредоточиться на выводе каждого из них на более высокую позицию.

Переранжирование с помощью TypeSafe

Переранжирование оценивает каждого кандидата из шортлиста относительно его запроса, а затем сортирует по полученному баллу. Вопрос, который TypeSafe задает по каждой паре: мог ли кандидат быть тем фрагментом, на который указывала удаленная из запроса цитата?

Следующая ячейка выполняет следующие действия:

  1. Определяет этот вопрос.
  2. Задает его один раз для каждого кандидата в каждом шортлисте (40 запросов умножить на 30 кандидатов — всего 1 200 вызовов, выполняемых параллельно, а не последовательно).
  3. Сортирует каждый шортлист по баллу, возвращенному TypeSafe, формируя переранжированный результат.
PYTHON EXPANDABLE THEME={NULL} api.wedstack.ru/v1
PLAINTEXT api.wedstack.ru/v1
1200 TypeSafe calls used 1,536,002 input and 25,200 output tokens, costing $0.0645.
output

Переранжирование поднимает правильный ответ к началу списка

График сравнивает быстрый поиск и связку «быстрый поиск + переранжирование» на трех пороговых уровнях. Переранжирование приближает правильный фрагмент к началу списка на каждом из них:

  • Top 1 — с 5% до 18%
  • Top 5 — с 15% до 35%
  • Top 10 — с 38% до 62%

Указанное количество токенов и стоимость охватывают все 1 200 вызовов TypeSafe, использованных для переранжирования 40 шортлистов.

Каждая строка CLERC содержит один правильный фрагмент и 20 отрицательных. В этом руководстве фрагменты из 170 строк объединены в один общий корпус. Для каждого из 40 тестовых запросов BM25 выбирает 30 кандидатов из всего корпуса, а не только из 20 отрицательных примеров конкретной строки. Затем TypeSafe сопоставляет запрос с каждым выбранным кандидатом и переранжирует эти 30 фрагментов.

Для наглядности в этом примере задавался один вопрос на пару. В реальном приложении для одной и той же пары можно задать сразу несколько вопросов в одном вызове. Подробнее см. в руководстве по параллельным вопросам и описании паттерна Speculative Fan-Out.


Что дальше

Те же строительные блоки рассматриваются в других разделах документации TypeSafe:

  • Noul — как TypeSafe превращает вопрос «да/нет» в оценку.
  • Speculative Fan-Out — как задавать несколько вопросов к одному документу в одном вызове.
  • Построчный поиск (Line-by-line Search) — еще один способ поиска по корпусу по смыслу, а не по ключевым словам.