Дистракторы в RAG: почему, увеличивая k, вы покупаете конкурентов, а не ответы
Позиции со второй по десятую в выдаче ретривера — это дистракторы. Данные Chroma, повтор с сфокусированным контекстом и подсчёт токенов одной сессии поддержки показывают, как их отсечь.
Представьте ретривер, который на вопрос возвращает десять чанков. Ответ есть ровно в одном. Задумайтесь на секунду, что такое остальные девять. Их природу диктует само ранжирование: из всего корпуса это отрывки, ближе всего стоящие к вопросу, но всё же на него не отвечающие.
Ничего здесь не сломано. Ретривер сделал ровно то, что от него требовалось. Эти девять чанков — не случайно просочившийся шум, а его продукт.
Цифры ниже взяты из опубликованных чужих измерений, и у каждой указан источник. Связывающий их аргумент — это утверждение о RAG в продакшене: увеличение k в основном покупает не ответы. Оно покупает ответу конкурентов, и это самые дорогие токены, какие только могут попасть в контекстное окно.
Имя для того, что мы отгружаем
В отчёте Chroma о Context Rot, опубликованном 14 июля 2025 года, проведено различие, которое большая часть текстов о ретриве игнорирует. Нерелевантный контент к вопросу отношения не имеет. Дистрактор связан с темой, но на вопрос не отвечает.
Ведут они себя совсем по-разному. На восемнадцати моделях даже один дистрактор снижал точность по сравнению с базовым вариантом, где была только «иголка». С четырьмя дистракторами вред накапливался, и эффект рос с длиной входа. В одной паре «стог — иголка» два из четырёх написанных вручную дистракторов попадали в галлюцинированные ответы гораздо чаще двух других. Семейства моделей тоже разошлись: модели Claude при неоднозначности чаще воздерживались от ответа, а модели GPT уверенно отвечали — и ошибались.
Теперь сравните это определение со списком из десяти результатов. Позиции со второй по десятую подходят под него идеально, и отсортированы они так, что самые убедительные стоят ближе к началу.
Отсюда следствие: улучшение эмбеддингов делает только хуже. Весь смысл более сильного ретривера — подвинуть отрывки ближе к вопросу. Для первой позиции это и есть цель. Для остальных девяти это значит, что каждый почти-попадающий отрывок острее всего, что подняла бы прошлогодняя модель. Деньги уходят в качество дистракторов, а называется это полнотой.
Привычное исправление повторяет ту же ошибку на уровень выше. Реранкер на кросс-энкодере оценивает релевантность внимательнее первого прохода, но оценивает он всё равно релевантность, а отрывок может быть точно по теме и при этом не содержать никакого ответа. Перетасовка девяти почти-попаданий их не убирает. Помогает только другой вопрос: отвечает ли отрывок, а не совпадает ли он?
Скрытая половина стоимости
Есть и вторая плата, которая в счёте не появляется никогда.
С несфокусированным окном одному вызову приходится делать две работы: найти нужный отрывок, а потом рассуждать над ним. Ретрив тихо переехал внутрь модели, где его нельзя залогировать, настроить или увидеть, как он ломается, и где он борется за то же внимание, что и рассуждение, за которое вы на самом деле платили.
Chroma оценила этот разрыв. В их прогонах LongMemEval каждый вопрос подавали дважды. Одна версия шла с полным входом, в среднем 113 000 токенов. Другая — со сфокусированным, в среднем 300 токенов, где был только нужный отрывок. Все семейства моделей справлялись хуже с полным входом. Самый большой разрыв показали модели Claude, в основном потому, что отказывались отвечать, как только возникала неоднозначность.
Тот же вопрос, та же модель, ответ есть в обоих окнах. Изменилось только то, что окружало ответ.
Окно побольше не спасёт
В сентябре 2025 года Anthropic опубликовала пост о context engineering, где названа архитектурная причина. В трансформере каждый токен смотрит на все остальные, что даёт n² попарных связей для n токенов, и с ростом n модель держит эти связи всё слабее. Остаётся ограниченный бюджет внимания, так что дополнительный контекст окупается всё хуже.
Этот спад — склон, а не обрыв, поэтому его и не замечают. Ничто не выбрасывает ошибку. Точность просто утекает.
Данные Chroma показывают, где потери круче всего. Все восемнадцать моделей лучше справлялись с текстом, перемешанным в бессмыслицу, чем с текстом в естественном порядке. Ещё одна закономерность: чем меньше формулировка вопроса походила на формулировку отрывка с ответом, тем быстрее падала точность с ростом входа, хотя на коротких входах те же пары работали нормально.
Практический вывод неприятный. Аккуратно организованный корпус не помогает, а сильнее всего от длинных окон страдают те пользователи, кто спрашивает своими словами, а не словами документации.
Четыре вида токенов и как каждый из них подводит
| Вид | Примеры | Как подводит |
|---|---|---|
| Статические | Системный промпт, файлы правил, описания инструментов | Отправляются на каждом ходе, так что цена растёт с числом ходов. Размывают контекст. |
| Извлечённые | Чанки, подтянутые под каждый запрос | Оплачиваются за каждую выборку. Приносят почти-попадания. |
| Накопленные | История чата, выводы инструментов | Растут только в одну сторону. Ломаются поздно, часто через несколько ходов после отравления. |
| Вынесенные наружу | Заметки, файлы, сводки от субагентов | Хранить дёшево. Устаревают без всякого предупреждения. |
Документация векторных баз подаёт строку с извлечёнными токенами как проблему полноты и никогда не считает стоимость остальных трёх. Команды усердно тюнят ретрив, а преамбула тем временем незаметно множится у него за спиной.
Повтор со сфокусированным контекстом
Один эксперимент позволяет отличить дефект контекста от дефекта модели, и никакой новой инфраструктуры для него не нужно.
Возьмите из продакшена тридцать проваленных случаев. Для каждого вручную соберите минимальный контекст, в котором есть ответ и больше ничего. Потом прогоните один и тот же вопрос по обеим версиям. Обычный путь выглядит так:
chunks = index.query(question, k=10) # one may answer, the rest merely resemble
prompt = SYSTEM + "\n\n".join(c.text for c in chunks) + question
А цикл сравнения — так:
for case in failing_cases:
full = assemble_context(case.question)
focused = case.gold_passage
log(case.id, ask(full), ask(focused)) # the gap is your context tax
Сравните два столбца. Если сфокусированная версия проходит там, где полная падает, никакое обновление модели не поможет.
После этого заведите учёт. Логируйте токены на каждом ходе по четырём корзинам: статические, извлечённые, результаты инструментов и история. Потом посмотрите на распределение на том ходе, где качество обваливается. Очень немногие команды хоть раз видели эту цифру для своего агента, и сильнее всего обычно удивляет доля статики.
Арифметика одной сессии поддержки
Цифры Chroma — это базовая линия. Дальше идёт простой подсчёт.
Возьмём агента поддержки с преамбулой на 6000 токенов, медианной сессией в двенадцать ходов и ретривом top-8 примерно по 700 токенов на чанк.
За сессию преамбула даёт 72 000 оплаченных токенов с информацией, которая никогда не меняется. Ретрив добавляет по 5600 за ход, то есть 67 200 в сумме. Из них семь чанков из восьми выбраны за то, что похожи на вопрос, а не за то, что на него отвечают. Итого около 59 000 токенов рукотворного отвлечения, и так в каждой сессии у каждого пользователя.
Сравните это со сфокусированным промптом примерно на 300 токенов, который на тех же самых вопросах показал результат лучше, чем вход на 113 000 токенов. Расходы — лишь половина вреда. Эти токены ещё и тянут точность вниз.
Что поменять в первую очередь
Относитесь к каждому токену как к месту, на которое нужна лицензия.
Статический контекст должен оправдывать своё место тем, что меняет поведение на каждом ходе, где он присутствует, а присутствует он на всех. То, что управляет одной задачей из пятидесяти, место в навыке, который подгружается по триггеру, а не в постоянно включённой преамбуле.
Извлечённый контекст должен оправдывать своё место тем, что способен ответить. Поэтому ранжируйте по тому, несёт ли чанк ответ, а не по сходству, и уменьшайте k, пока повтор не перестанет показывать улучшения. Дешёвый способ — второй проход, где каждый кандидат показывается маленькой модели с одним вопросом «да или нет»: содержит ли отрывок ответ. Всё, что получило «нет», отбрасывается. Обычно так уходит большая часть выборки.
Каждый токен оплачивается на каждом ходе и расходует один и тот же бюджет внимания. Поэтому спрашивать, релевантен ли документ, — неправильная проверка. Важно другое: перевешивает ли он то, что вытесняет.