- BrainTools - https://www.braintools.ru -

RAG — это про замеры, а не про код. История одного бота, где почти всё «правильное» сделало хуже

Есть жанр статей про RAG, который выглядит так: подключаем векторную базу, берём эмбеддер, сверху реранкер, для верности — гибридный поиск с BM25, и вот у нас продакшн-ready система. Двадцать строк кода, стрелочки на схеме, всё летает.

Я прошёл этот путь до конца на реальных данных. И почти каждое «правильное» улучшение из этого списка на моих данных сделало хуже. BM25 уронил метрику. Реранкер уронил метрику. Дважды, в двух разных конфигурациях. Единственное, что реально помогло, — не код, а несколько дней возни с данными и замерами.

Это не статья «RAG не работает». RAG работает. Это статья о том, что настоящая работа в RAG — не в коде, который пишется за день, а в eval-харнессе, чистке корпуса и честных замерах, которые показывают, что из индустриальных дефолтов вам подходит, а что вредит. И проверить это можно только на своих данных — «так принято» тут не аргумент.

Откуда взялась задача

Мой знакомый сделал бота для модерации групп. В отличии от большинства современных ботов построенных на текстовых фильтрах и списках, его бот решает большинство задач на основе локальной ИИ-модели. Если интересно, то можно почитать вот тут [1]. Бот уже висит в нескольких группах и с задачей своей справляется. Я, после 20 лет работы в веб-разработке осознал, что с развитием ИИ мой труд уже скоро станет невостребован и решил заняться темой ИИ и агентов как будущей профессией. Сначала написал сервис для генерации графики на основе разложения слабого интента на оси неопределенности и закрытие их вопросами для клиента. Потом, просматривая вакансии на hh наткнулся на такую нишу, как RAG-системы. Ну и загорелся. Изучать новое лучше на живых задачах, поэтому вместо сборок туториалов я решил сделать что то, что реально решает проблему. Тема скама мне достаточно интересна, за несколько лет работы с криптой и проектами в этой нише я сталкивался с ним ни раз. Увы, несмотря на развитие технологий, большинство бирж и проектов решают эту задачу по старинке: набирают штат дешевой рабочей силы из разных часовых поясов, которая отвечает пользователям, ставят кучу ботов для закрытия простых дыр и пытаются бороться информированием. Полное исследование — на отдельной странице [2], здесь коротко. Главная проблема в том, что скам пользователей уже давно эволюционировал и если раньше постили ссылки на конкурсы прямо в группах, то сейчас весь скам идет в личных сообщениях и атакующий может даже не состоять в группе. Вторая проблема в том, что большинство людей по ряду причин не читают закрепы, регулярные напоминания тонут в потоке новых сообщений и основные усилия должны быть направлены на адресное предупреждение и скорость, а не количество сообщений.

Я собрал публичные экспорты 11 крупных чатов криптобирж — около 3.9 млн сообщений за полгода — и прогнал по ним классификатор, который ищет одну конкретную вещь: сообщения от людей, у которых что-то случилось. Заморозили средства, завис вывод, потерян доступ к аккаунту, не проходит верификация.

Таких — тысячи. И вот ключевая цифра: в большинстве чатов публичный ответ от админа получают от 19% до 42% обратившихся, медиана около 30%. Две трети людей с проблемой не получают публичного ответа вообще. Не потому что модерация плохая — а потому что поток превышает пропускную способность тех, кто на смене.

Отсюда идея модуля, о котором эта статья: раз админы не успевают, пусть человеку, написавшему «не могу вывести», бот сам подкинет ссылку на прошлый ответ админа на такую же проблему. Не заменить поддержку — подстраховать. Нашлось похожее — показали, не нашлось — молчим, админ подхватит. Единственное “но” — бот должен писать только новым пользователям или тем, кто не писал давно. В противном случае это будет авто-ответ на все вопросы, что очень быстро убьет любой чат, потому как если человек получил ответ на свой вопрос, желание общаться дальше у него пропадает.

Технически это классический RAG: база вопросов-ответов, векторный поиск по вопросам, отдаём привязанные ответы. Казалось бы, тот самый «день кода». На деле день ушёл на код, а две недели — на то, чтобы понять, работает ли он.

Чтобы что-то улучшать, нужно это измерять. А чтобы измерять — нужен честный эталон

Первое, что я сделал, — eval-линейка. Набор вопросов, к каждому размечены правильные ответы из корпуса. Метрика — hit@3: попал ли правильный ответ в топ-3, которые увидит пользователь.

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

Я размечал по одному правильному ответу на вопрос — брал самый подходящий. Логично [3] для обучающего датасета, где нужен один эталон. Но для замера ретрива это ошибка [4]: в корпусе на один вопрос часто есть несколько взаимозаменяемых ответов. Бот выдаёт годный ответ, а метрика засчитывает промах — потому что id другой.

Когда я стал разбирать «промахи» глазами, оказалось: бот находит правильную тему, ставит верный по смыслу ответ в топ, но с id, которого нет в моей разметке. Метрика показывала 38%, а реальная цифра была заметно выше — просто эталон был кривой.

Мораль, которую стоит повесить на стену: прежде чем чинить систему, убедись, что твоя метрика не врёт. Я чуть не пошёл улучшать поиск, который на самом деле работал, — спотыкалась разметка, а не модель.

Ещё одна ловушка того же класса. Классические precision@k и recall@k не умеют учитывать право системы промолчать. А у меня молчание — штатный и желательный режим: нет ответа в базе — не выдумывай. Пришлось завести свои метрики: silence (зря промолчал, хотя ответ был), noise (выдал мимо) и none_ok (правильно промолчал на вопрос без ответа). Без них картина была бы слепой ровно в том месте, которое для этого бота важнее всего.

Диагностика: где именно теряется качество

Когда эталон стал честным, я добавил в замер диагностику, которая, по-хорошему, должна быть в любом RAG-проекте, а её почти нигде нет. Для каждого вопроса — на какой позиции стоит правильный ответ в сыром топ-50, до всякого порога.

Это разделяет две совершенно разные болезни, которые обычно сваливают в одну:

  • Правильный ответ есть в кандидатах, но стоит не в топ-3 (позиции 4-20) → проблема ранжирования → теоретически чинит реранкер.

  • Правильного ответа нет в топ-50 вообще → проблема полноты поиска → реранкер бессилен, нужен другой эмбеддер или лексический поиск.

Без этого разделения улучшать RAG — гадание. Ты не знаешь, тебе нужен реранкер, BM25 или новый эмбеддер, и берёшь то, про что чаще пишут в блогах.

Цифры на моих данных:

метрика

значение

hit@3 (что видит пользователь)

74%

hit@10

81%

hit@20

84%

hit@50 (потолок recall)

86%

правильного нет в топ-50

14%

Читается так: ранжирование у меня работает прилично — почти всё, что найдено, лежит близко к верху. А потолок в 86% означает, что в 14% случаев правильного ответа нет даже в топ-50, и это упор в эмбеддер, а не в порядок выдачи. Больше 74% я подниму, только если поменяю сам эмбеддер, — но об этом в конце.

Поворот первый: порог решает больше, чем любая модель — и он бесплатный

Прежде чем тянуться к тяжёлой артиллерии, я посмотрел на самое дешёвое — порог отсечения по близости. Ниже порога — система молчит.

Оказалось, что 26 процентных пунктов hit@3 терялись на пороге. Правильный ответ был в топ-3, но его близость — чуть ниже отсечки, и его глушило. Сырой hit@3 — 75%, а в проде при пороге 0.55 — 49%. Разница уходила не в поиск, а в одну неправильно выставленную константу.

И тут важный вывод, который сломал мне интуицию [5]: порог RAG нельзя выбрать «объективно». Он зависит от роли бота в продукте.

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

  • Если бот — подстраховка (как у меня: админ всё равно ответит), то промах = молчание = нулевой вред, человек просто ждёт админа как раньше. А вот выдать уверенно неправильное — плохо: собьёт с толку, подорвёт доверие.

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

Это было единственное изменение за весь проект, которое улучшило результат. Всё остальное — ниже.

Поворот второй: BM25 сделал хуже, потому что сигнал был не с той стороны

Гибридный поиск — вектор плюс лексический BM25 — это индустриальный дефолт. «Вставил и забыл», как мне справедливо заметили. BM25 хорошо ловит точные токены: тикеры, коды сетей, номера — то, что векторный поиск смазывает. У меня в промахах как раз мелькали LTC, USDC, base chain — казалось бы, идеальный случай для BM25.

Я построил FTS5-индекс, прикрутил слияние через RRF, прогнал замер. Результат:

режим

hit@3

чистый вектор

74%

вектор + BM25 (гибрид)

55%

BM25 не помог — он уронил метрику на 19 пунктов.

Почему — я понял за пять минут одним запросом к базе ещё до полного замера. BM25 ловит точные токены. Но эти токены (USDC, LTC) есть в вопросах пользователей, а мой корпус — это вопросы-образцы, на которые ответили админы, и они сформулированы обобщённо: «не могу вывести», «аккаунт заблокирован». Точных тикеров в корпусе почти нет. Проверка SELECT ... WHERE question LIKE '%USDC%' вернула пусто.

То есть лексический сигнал, который ищет BM25, был не на той стороне: он в запросе, а не в индексе. BM25 не находил точные совпадения (их негде найти) — он подмешивал лексически похожие, но семантически неверные записи и вытеснял правильные ответы вниз. На позиции 1 у чистого вектора было 35 правильных ответов, у гибрида — 25. BM25 разбавил хорошее шумом.

Вывод не «BM25 плохой». BM25 отличный — когда лексический сигнал есть в корпусе. Вывод в том, что «стандарт индустрии» проверяется smoke-тестом на своих данных за пять минут, а не переносится на веру. Я потратил бы день на прод-интеграцию того, что структурно не могло сработать.

Поворот третий: реранкер тоже сделал хуже — и это было предсказуемо

Ладно, BM25 не зашёл. Но реранкер-то — это святое. Cross-encoder берёт пары «запрос + кандидат» вместе и переранжирует точнее, чем bi-encoder. Он должен вытащить те правильные ответы из позиций 4-20 в топ-3.

Проверил две модели, два способа сравнения (запрос с вопросом кандидата и с ответом), поверх гибрида и поверх чистого вектора. Лучший результат:

режим

hit@3

чистый вектор

74%

вектор + реранкер

64%

гибрид + реранкер

57%

Реранкер во всех конфигурациях сделал хуже. На чистом векторе — уронил с 74% до 64%.

Механику видно на позициях: у чистого вектора 35 правильных ответов на позиции 1, после реранкера — 30. Реранкер утащил 5 правильных ответов с первого места вниз, а поднял снизу меньше, чем испортил сверху.

И это, задним числом, было предсказуемо. Есть общее правило, которое стоит запомнить: реранкер опасен, когда базовый поиск уже хорош. У меня 35 из 51 правильного ответа уже на позиции 1 — верхушка почти идеальна. Чтобы реранкер помог, он должен поднимать низ, не трогая верх. А универсальный реранкер на 118 млн параметров понимает мой узкий домен (короткий крипто-жаргон, смесь языков) хуже, чем эмбеддер, дообученный на моих же данных. Поэтому его перестановки в среднем вредят: downside больше upside. Когда базовый поиск слабый — реранкер спасает. Когда сильный — он рискует сломать то, что уже работает.

Что в итоге сработало

Свожу весь путь улучшений в одну таблицу — она и есть главный результат:

режим

hit@3

чистый вектор + калибровка порога

74%

вектор + реранкер

64%

гибрид (вектор + BM25)

55%

гибрид + реранкер

57%

Чистый векторный поиск с правильно выставленным порогом побеждает все «улучшения». Единственное, что реально помогло за весь проект, — калибровка порога под роль продукта. Ноль строк модного кода. BM25, реранкер, две модели, два способа сравнения — всё проиграло.

74% при потолке recall в 86% — для короткого многоязычного крипто-жаргона это честный, добротный результат. Не выдающийся, но и упор здесь не в ранжирование (оно работает), а в recall эмбеддера. Поднять выше можно только сменой или дообучением эмбеддера — и вот это был бы следующий шаг, если бы я строил систему на продажу. Не реранкер, не гибрид, а базовая модель, которая находит. Плюс — курирование корпуса: половина «нет в топ-50» оттого, что конкретного ответа в базе просто нет, только роутер «обратитесь в поддержку». Никакая модель не найдёт то, чего в корпусе нет.

Что из этого забрать

Если убрать конкретику про крипточаты, остаётся несколько вещей, которые, кажется, переносятся на любой RAG-проект:

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

Разделяйте recall и ранжирование. hit@3 против hit@20 одним взглядом показывает, что чинить: полноту поиска или порядок выдачи. Без этого выбор инструмента — гадание.

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

«Best practice» — это гипотеза, а не факт. BM25 и реранкер — прекрасные инструменты, которые на моих данных навредили. Проверять их надо на своих данных, и часто это дело пяти минут. Отрицательный результат, пойманный замером, ценнее, чем слепое следование стандарту, — он экономит дни интеграции того, что не работает.

Данные важнее модели. Самый большой рычаг оказался не в коде, а в том, что лежит в корпусе и как размечен эталон. Отсюда и заголовок: RAG — это про замеры, а не про код. Код здесь — двадцать строк. Всё остальное — данные и честность к цифрам.


Это первая из цикла статей. Здесь — про то, почему RAG оказался про данные и замеры. Во второй разберу, как вся система собрана технически: детектор просьб о помощи, типизатор, архитектура. Ссылка появится здесь.

Автор: noroots

Источник [6]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/33567

URLs in this post:

[1] тут: https://modera.me/

[2] на отдельной странице: https://modera.me/research

[3] Логично: http://www.braintools.ru/article/7640

[4] ошибка: http://www.braintools.ru/article/4192

[5] интуицию: http://www.braintools.ru/article/6929

[6] Источник: https://habr.com/ru/articles/1063026/?utm_campaign=1063026&utm_source=habrahabr&utm_medium=rss

www.BrainTools.ru

Rambler's Top100