- BrainTools - https://www.braintools.ru -
Если вы когда-нибудь грузили в LLM список и просили выбрать лучшее или отсортировать — вы, скорее всего, получали посредственный результат. Я проверил это на 164 постах своего телеграм-канала, сравнив пять разных методов сортировки. Оказалось, что разница между «дёшево и плохо» и «дёшево и хорошо» — в правильном алгоритме, а не в модели.
В этой статье разберём, почему наивные подходы не работают, как алгоритм из Xbox Live помогает ранжировать контент, и какой метод даёт лучшую корреляцию с реальными данными.
Допустим, вам нужно отсортировать большой список по субъективному критерию: интересность статей, приоритет фичей, качество резюме. Классический поиск или эмбеддинги здесь не помогут — нужна семантическая оценка.
Первый порыв — скормить весь список в LLM и попросить отсортировать. Но тут возникают проблемы:
Ограничение контекста — в окно влезает не всё
Деградация внимания [1] — модель внимательнее читает начало списка
Нестабильность — один и тот же запрос может дать разные результаты
Лимиты вывода — получить обратно 500 отсортированных ID непросто
Я решил протестировать разные подходы на реальных данных и измерить, какой из них лучше предсказывает виральность постов.
Самый простой подход — отправить все элементы в одном запросе и попросить вернуть отсортированный список ID.
prompt = """Отсортируй эти посты по интересности для разработчика.
Верни только список ID постов в порядке от САМОГО интересного к наименее интересному.
Посты:
[ID:123] Текст поста 1...
[ID:456] Текст поста 2...
Плюсы: один API-вызов, минимальная стоимость.
Минусы: позиционное предпочтение (positional bias) — LLM внимательнее читает начало списка. Элементы из начала получают преимущество независимо от содержания.
Каждый элемент оценивается отдельно по шкале от 1 до 100. Затем сортируем по оценкам.
prompt = """Оцени этот пост по шкале от 1 до 100: насколько он интересен для разработчика.
Учитывай:
- Техническую глубину и полезность
- Практическую применимость
- Новизну информации
Пост:
{post_text}"""
Плюсы: нет positional bias, получаем объяснение для каждого элемента.
Минусы: O(n) вызовов API, абсолютные оценки нестабильны — «75 баллов» в разных запросах может означать разное.
Модификация Score: заставляем модель сначала выписать плюсы и минусы, а потом ставить оценку.
class ScoreReasonedResult(BaseModel):
pros: str # Сначала анализируем плюсы
cons: str # Потом минусы
score: int # И только потом оценка
Гипотеза была в том, что принудительный «reasoning» улучшит качество оценок. Спойлер: не особо.
Здесь начинается интересное. Вместо абсолютных оценок используем относительные сравнения.
TrueSkill — алгоритм, разработанный Microsoft для матчмейкинга в Xbox Live. Он позволяет оценить «силу» игрока, даже если тот никогда не играл против конкретного соперника — достаточно истории матчей.
Применяем к сортировке:
Каждому элементу присваиваем начальный рейтинг (μ=25, σ=8.33)
Формируем случайные батчи по 10 элементов
Просим LLM отсортировать батч
Обновляем рейтинги всех участников батча по результатам «матча»
Повторяем [2] несколько раундов
Финальная сортировка по μ − 3σ
from trueskill import Rating, rate
# Инициализация
ratings = {post.id: Rating() for post in posts}
# После каждого "матча" (сортировки батча)
teams = [[ratings[pid]] for pid in sorted_ids]
ranks = list(range(len(sorted_ids))) # 0 = первое место
new_ratings = rate(teams, ranks=ranks)
Почему батчи эффективнее пар:
Пары: один вызов → информация о 2 элементах
Батчи по 10: один вызов → информация о 10 элементах
Для 164 постов с парами нужно ~1230 вызовов. С батчами по 10 и 5 раундами — около 85 вызовов. В 15 раз дешевле при сопоставимом качестве.
Классический подход: сравниваем элементы попарно, обновляем рейтинги после каждого сравнения.
prompt = """Какой пост ИНТЕРЕСНЕЕ для разработчика?
[A] {post_a}
[B] {post_b}
Ответь одной буквой: A или B"""
Плюсы: теоретически чище, нет positional bias внутри батча.
Минусы: очень дорого. Для покрытия всех элементов нужно ~15 сравнений на каждый, итого 1230+ вызовов API.
У каждого элемента два числа:
μ (mu) — средний рейтинг, наше лучшее предположение о «силе»
σ (sigma) — неопределённость, насколько мы уверены в оценке
Изначально все элементы равны: μ=25, σ=8.33.
После каждого «матча» система обновляет оба параметра:
Победитель: μ растёт, σ падает (мы увереннее в его силе)
Проигравший: μ падает, σ тоже падает
Ничья: μ сближаются, σ падают у обоих
Финальная сортировка идёт по μ − 3σ — это консервативная оценка. Элемент с высоким μ, но большим σ (мало сравнений) не попадёт в топ, пока система не станет в нём уверена.
Визуально это выглядит как нормальное распределение для каждого элемента. После 5-10 «матчей» распределения перестают пересекаться, и мы получаем стабильный рейтинг.
Я прогнал все 5 методов на 164 постах своего телеграм-канала, используя модель gpt-4.1-mini. Критерий — «вероятность репоста» (shareability).
|
Метод |
API-вызовов |
Токенов |
Стоимость |
Время |
|---|---|---|---|---|
|
Bulk |
1 |
14.5K |
$0.006 |
6 сек |
|
Score |
164 |
81K |
$0.056 |
1.2 мин |
|
Score + Reasoning |
164 |
103K |
$0.080 |
1.3 мин |
|
TrueSkill Batch |
17 |
106K |
$0.045 |
1.8 мин |
|
TrueSkill Pairwise |
1230 |
659K |
$0.271 |
1.8 мин |
Bulk в 45 раз дешевле Pairwise. Но дешевизна бесполезна, если результат плохой.
Главный вопрос: насколько хорошо модель предсказывает, какими постами реально поделятся?
У меня были данные о фактических репостах (forwards) для каждого поста. Я посчитал корреляцию Спирмена между предсказанным рангом и реальными репостами:
|
Метод |
Корреляция (ρ) |
p-value |
Интерпретация |
|---|---|---|---|
|
Score |
-0.50 |
<0.001 |
Сильная |
|
TrueSkill Batch |
-0.46 |
<0.001 |
Умеренная |
|
Score + Reasoning |
-0.43 |
<0.001 |
Умеренная |
|
TrueSkill Pairwise |
-0.42 |
<0.001 |
Умеренная |
|
Bulk |
+0.27 |
<0.001 |
Инверсия! |
Отрицательная корреляция — это хорошо: меньше ранг (= выше в рейтинге) → больше реальных репостов.
Bulk показал положительную корреляцию — модель ранжирует посты в обратном порядке! Посты с большим количеством репостов оказываются внизу рейтинга. Вероятная причина — positional bias: модель просто ставит выше то, что прочитала первым.
Score оказался лучшим предсказателем при средней стоимости. Дороже Bulk в 9 раз, но реально работает.
TrueSkill Batch — хороший компромисс: дешевле Score, корреляция близкая. Особенно полезен, когда данные добавляются инкрементально — не нужно пересчитывать всё с нуля.
TrueSkill Pairwise — дорогой и не лучший. Тысяча сравнений накапливает случайный шум.
Bulk — только для быстрой разведки на маленьких данных (<20 элементов). Для продакшена не годится.
Score — когда нужны объяснения для каждого элемента и важна максимальная точность. Хорош для приоритизации фичей, где важно понять «почему».
TrueSkill Batch — лучший выбор для большинства задач. Особенно если:
Данных много (100+ элементов)
Данные добавляются постоянно (можно добавлять новые элементы без полного пересчёта)
Бюджет ограничен
Score + Reasoning — гипотеза не подтвердилась. Принудительный reasoning не улучшил качество, только увеличил расход токенов.
TrueSkill Pairwise — только если критически важно избежать любого positional bias и бюджет не ограничен.
Мои интуитивные фавориты оказались в середине рейтинга. Посты, которые я считал лучшими, модель оценила как средние. И судя по корреляции с реальными репостами — модель была права.
Я прогнал сортировку для разных критериев: «интересно разработчикам», «интересно домохозяйкам», «полезно для бизнеса». Топы получились совершенно разные.
Для разработчиков в топе — технические посты про исследования OpenAI, SGR-паттерн, Claude Code.
Для домохозяек TrueSkill вытащил посты про work-life balance, FOMO, медитацию. Bulk и Score с этой аудиторией справились плохо — выдавали технические посты, которые явно не для этой ЦА.
Субъективно, TrueSkill Batch выдаёт более «логичные» группировки. Топ-10 выглядит как осмысленная подборка, а не случайный набор. Вероятно, потому что относительные сравнения точнее абсолютных оценок.
Код эксперимента выложен на GitHub. Основные компоненты:
sorting-demo/
├── sorters/
│ ├── bulk_sort.py # Метод Bulk
│ ├── score_sort.py # Метод Score
│ ├── score_reasoned_sort.py # Score + Reasoning
│ ├── trueskill_sort.py # TrueSkill Batch
│ └── trueskill_pairwise_sort.py # TrueSkill Pairwise
├── config.py # Промпты и настройки
├── llm_client.py # OpenAI клиент с трекингом токенов
└── dashboard-react/ # React-дашборд для визуализации
Библиотека trueskill для Python:
pip install trueskill
Базовый пример TrueSkill Batch:
from trueskill import Rating, rate
import random
def trueskill_sort(items, compare_batch_fn, batch_size=10, rounds=5):
"""
items: список элементов для сортировки
compare_batch_fn: функция, которая принимает батч и возвращает
отсортированные ID (через LLM)
"""
ratings = {item.id: Rating() for item in items}
items_dict = {item.id: item for item in items}
for _ in range(rounds):
# Перемешиваем и разбиваем на батчи
ids = list(ratings.keys())
random.shuffle(ids)
for i in range(0, len(ids), batch_size):
batch_ids = ids[i:i + batch_size]
if len(batch_ids) < 2:
continue
batch = [items_dict[id] for id in batch_ids]
sorted_ids = compare_batch_fn(batch) # LLM сортирует батч
# Обновляем рейтинги
teams = [[ratings[id]] for id in sorted_ids]
ranks = list(range(len(sorted_ids)))
new_ratings = rate(teams, ranks=ranks)
for id, (new_rating,) in zip(sorted_ids, new_ratings):
ratings[id] = new_rating
# Сортируем по μ - 3σ (консервативная оценка)
return sorted(ratings.items(),
key=lambda x: x[1].mu - 3 * x[1].sigma,
reverse=True)
Не сгружайте LLM большие списки с просьбой отсортировать. Positional bias делает результат бесполезным.
Относительные сравнения лучше абсолютных оценок. LLM (и людям) проще сказать «A лучше B», чем «A — это 73 из 100».
TrueSkill Batch — оптимальный баланс цены и качества. Дешевле чем Score, точнее чем Bulk.
Score — лучший предсказатель, если бюджет позволяет. Корреляция -0.50 с реальными данными — это уровень приличной ML-модели.
Reasoning этап перед оценкой не помогает. По крайней мере, для задачи ранжирования контента.
Демо-дашборд для сравнения методов: artwist-polyakov.github.io/sorting-demo [3]
Статья основана на идеях из публикации Thariq Shihipar [4] (разработчик Claude Code, Anthropic) о применении TrueSkill для LLM-сортировки.
P.S. Пишу про AI-автоматизацию для бизнеса, Claude Code и подобные эксперименты в телеграм-канале «Поляков считает» [5].
Автор: artwistru
Источник [6]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/24570
URLs in this post:
[1] внимания: http://www.braintools.ru/article/7595
[2] Повторяем: http://www.braintools.ru/article/4012
[3] artwist-polyakov.github.io/sorting-demo: http://artwist-polyakov.github.io/sorting-demo
[4] из публикации Thariq Shihipar: https://www.thariq.io/blog/sorting/
[5] телеграм-канале «Поляков считает»: https://t.me/+oooCvS_tbL5jOTAy
[6] Источник: https://habr.com/ru/articles/987538/?utm_source=habrahabr&utm_medium=rss&utm_campaign=987538
Нажмите здесь для печати.