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

Data-Feeling-School-RAG-Challenge или по ту сторону баррикад

Не буду одинок, если предположу, что большинство читателей при встрече с чат ботом любой ценой отказывается от его услуг, и ищет способы выхода на живого человека. Причин тому много и основная из них это выдача чат ботом информации далекой от ожидаемой. А какова цена создания системы, которая мало мальски отвечает ожидаемо на задаваемые пользователем вопросы?
В этой заметке я опишу свой опыт создании RAG системы в рамках соревнования на платформе kaggle https://www.kaggle.com/competitions/data-feeling-school-rag-challenge/overview , которое мне помогло понять базовые сложности такой системы. Суть соревнования заключалась в создании RAG системы формирования ответов на возможные вопросы, опирающейся на базу документов некой абстрактной компании. Не смотря на то , что задача выглядит довольно просто, поскольку база документов весьма скромная, а вопросов всего 130, тем не менее «наивный» RAG вряд ли позволит занять высокие позиции, поскольку подводные камни были. Вишенкой на торте были и метрика - косинусное сходство с эмбеддингами правильных ответов, а это означало и необходимость поиска LLM с высокой детерминированностью ответов. Именно эта метрика давала возможность поупражняться в поиске ответов, максимально приближенных к ожидаемым. Для понимания, даже простая перестановка слов в ответе изменяла метрику, не говоря уже про более серьезные отклонения ответа от ожидаемого.

Каких то ограничений явно не было озвучено. LLM можно было подключать любые, но попытка использовать большое контекстное окно снижала бы шансы на победу. Для себя я определил ограничение: модель я буду использовать только локальную, и наличие собственной RTX-4090 позволяло мне выбирать LLM из нескольких моделей. Это позволило без серьезных потерь для бюджета вволю поэкспериментировать.

Опыта, кроме прохождения курсов по LLM , на которых затрагивались вопросы создания RAG у меня совершенно не было поэтому , засучив рукава, с энтузиазмом взялся за дело.

Код получился не самым компактным, поэтому здесь воздержусь от его копирования. А с решением можно ознакомиться на https://github.com/IgorSharygin63/Data-Feeling-School-RAG-Challenge

Архитектура решения
54be56b41bfbf8a346503ec3785235d5.png

Ключевые особенности архитектуры

  • Гибридный подход к поиску что сочетало преимущества семантического (векторного) и лексического (BM25) методов

  • Двухуровневая фильтрация (retrieval + reranking) позволила повысить точность отбора релевантных документов

  • Динамический выбор примеров что адаптировало поведение LLM под конкретный тип запроса

  • Сведения о сотрудниках я счел необходимым внести в базу данных , что позволило упростить работу с рядом вопросов, ответы на которые было проще получить через SQL-запросы

Ключевым звеном RAG (Retrieval-Augmented Generation) системы является компонент поиска (Retriever), вот на него и понадобилось достаточно много внимания.

В итоге пришел к многоуровневой системе ретриверов:

  • EnhancedBM25Retriever — классический BM25 с нормализацией запросов и восстановлением оригинального текста

  • EnhancedVectorRetriever — векторный поиск на базе FAISS с фильтрацией по порогу похожести и метаданными similarity_score

  • RerankedEnsembleRetriever — ансамблевый ретривер, комбинирующий BM25 и векторный поиск через RRF (Reciprocal Rank Fusion) с настраиваемыми весами каналов

  • FallbackRetriever — страховочный механизм, активирующийся при недостаточном количестве результатов от основного ансамбля

Учитывая конечную метрику и train датасет, акцент был смещен в сторону поиска лексического сходства. Пару слов про train. Организаторы сделали его самостоятельным, то есть train и test это два совершенно разных датасета по тематике и соответственно семантике и лексике. Все чем мог быть полезен train, это формирование общей концепции поведения системы. В частности бросалось в глаза компактность ответов, высокое лексическое сходство ответа с информацией для “обучения” и ряд других особенностей. Настройку ретриверов помогло сделать расширенное логирование, когда можно было для каждого вопроса оценить что выбрал каждый из ретриверов и на чем остановился реранкер.
Справедливости ради, идеала я не добился. На некоторых вопросах отсеивались те чанки, которые не прошли дальше по оценочной сетке но которые влияли на точность ответа. Возможно, будь train датасет более объемным, можно было бы поиграть параметрами и получить нужные настройки, но основная цель все таки была выйти в лидеры соревнования и риск хотелось минимизировать. Но в реальной системе на мой взгляд более жизнеспособно решение, в котором для различных вопросов используются или различные схемы ретриверов или различные настройки и реализуется маршрутизация по каким нибудь критериям.

Теперь про выбор LLM. Стабильность ответов удалось добиться с моделью mistral-small-24b-instruct-2501. Модель неприхотлива, требует 14 гигабайт видео памяти и отличается стабильностью ответов и хорошим принятием примеров в контексте. Другие модели позволяли себе вольно трактовать контекст, игнорировать примеры и не отличались стабильностью. ИИ в лице GPT5.2 на вопрос, почему это происходит, мне предложила такую версию:
“Все зависит от сочетания трёх вещей: как модель обучали, как её запускают (декодирование/параметры) и как устроен её контекст/инструкции”.

В практическом плане можно сформулировать рекомендацию для задач, близких к аналитике и поиску по базе знаний, выбирать более стабильные модели и жёстче контролировать параметры генерации, а более вариативные LLM оставлять для креативных задач, где разнообразие формулировок — не баг, а вполне себе фича.


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

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

Вот пример такого промпта, удаление из которого строк - разделителей заметно изменяло ответ:

57da134dcf5f699c1e021e76e828d530.png

На каком-то этапе работы над RAG стало ясно, что одного хорошего retriever-а недостаточно. Даже если контекст подобран аккуратно, LLM всё равно может «стрелять мимо», игнорировать важные детали или, наоборот, уверенно галлюцинировать. Мне захотелось добавить над моделью ещё один слой управления — промпт-инженерию с примерами, но не в виде жёстко прошитых демонстраций, а как более гибкий механизм. В итоге LLM‑модуль отвечает не только за вызов модели, но и за осмысленную сборку промпта.

Ключевая идея модуля — двухшаговый режим работы. Сначала модель сама выбирает, какие примеры ей будут полезны для данного вопроса, а уже потом на их основе отвечает пользователю. На первом шаге используется отдельный prompt (EXAMPLE_SELECTION_PROMPT_TEMPLATE), куда передаётся текущий вопрос и отформатированный список всех примеров. Цепочка prompt → llm → StrOutputParser возвращает текст с выбранными примерами; по сути, LLM играет роль «редактора» тренировочного набора, подбирая релевантные демонстрации под конкретную задачу.

После этого выбранные примеры аккуратно встраиваются в основной RAG‑prompt. Функция augment_prompt_with_examples отвечает за то, чтобы разместить их в правильном месте: строго после системной инструкции, но до контекста и вопроса. Сами примеры обрамляются заголовком, разделителями и небольшой мини-инструкцией: что из них нужно перенять, а чего избегать. Если в шаблоне явно есть секция «Контекст:» или плейсхолдер {context}, блок с примерами вставляется прямо перед ней; если нет — используется более общий вариант с разбиением строки. Таким образом, итоговый промпт всегда имеет понятную структуру: сначала правила, затем примеры, потом уже retrieved‑контекст и формулировка вопроса.

Обработку вопросов про сотрудников я вынес в отдельный модуль с SQLite и SQL‑агентом по причинам:

  • Точность и воспроизводимость: SQL даёт детерминированные выборки и агрегаты, меньше риск “галлюцинаций”, чем при RAG по документам.

  • Эффективность: фильтрации/сортировки/группировки/подсчёты дешевле и быстрее делать запросом, чем тащить всё в контекст LLM.

  • Удобные вычисляемые поля (например, birth_year) убирают бизнес-логику из промптов и дублирование в коде.

  • Структурные данные (должность, зарплата, возраст) проще и надёжнее хранить и искать в БД, чем в длинных текстах.

  • Лучшее качество маршрутизации: вопросы “про людей” можно направлять в SQL-ветку, не ухудшая поиск по документам.

Маршрутизация сделана максимально просто: is_employee_question проверяет наличие «сотрудник»- слов (EMPLOYEE_WORDS), маркеров возраста (AGE_WORDS) или явного года по regex. Если речь о людях и затрагиваются возраст/годы — запрос уходит в SQL‑ветку, иначе остаётся для RAG по документам. В более зрелом решении маршрутизацию конечно же стоит делать не на регулярных выражениях, но для текущей задачи этого вполне достаточно.

И коротко про достигнутый результат.
На момент окончания соревнования мне удалось удерживаться на первом месте, но public составляющая датасета была все лишь 25%, поэтому конечный результат будет известен только завтра.

Источник

Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу [email protected] для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.