Привет, меня зовут Вова Ловцов. Я data science инженер, работаю в команде DS Core в Cloud.ru, где мы занимаемся разработкой агентов, RAG-систем и развитием AI-направления в целом.

Недавно мы запустили AI-помощника, который не только отвечает на вопросы по документации, разворачивает виртуальные машины и настраивает мониторинг за пользователей, но и помогает с SRE и FinOps. Под капотом это мультиагентная система, и один из ее ключевых компонентов — это RAG (Retrieval-Augmented Generation). Именно он отвечает за поиск информации и формирование понятных ответов.

Как понять, что RAG работает хорошо? Как его измерить, улучшить и выбрать лучшую конфигурацию? Обычные метрики вроде BLEU или ROUGE не всегда отражают качество ответа с точки зрения пользователя. Поэтому мы озадачились поиском автоматизированного и воспроизводимого решения и в итоге выбрали RAGAS — open-source инструмент для оценки RAG-систем. Но оказалось, что «из коробки» он работает далеко не идеально (а иногда и вообще не работает).

В этой части кратко расскажу про подходы к оценке RAG, наш выбор исходя из внутренних особенностей и опишу, как под капотом RAGAS представляет данные. А в продолжении — как RAGAS осуществляет непосредственную генерацию и оценку, какие проблемы встретили на пути применения этой библиотеки и что придумали, чтобы их решить.

Зачем и как оценивать RAG и что решили мы

RAG-системы сегодня повсюду — от чат-ботов до поисковых ассистентов. Но они не просто про вопрос-ответ. Качество работы и финальных ответов зависит от каждого из этапов, в базовом исполнении это:

  1. Поиск релевантного контекста (retrieval),

  2. Генерация ответа на основе этого контекста (generation).

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

Впрочем, современные RAG-системы зачастую содержат расширения, например:

  1. Рерайтинг запроса пользователя.

  2. Семантический поиск контекста (retrieval).

  3. Полнотекстовый поиск контекста (retrieval).

  4. Реранжирование фрагментов контекста (retrieval/reranking).

  5. Генерация ответа на исходный запрос (generation).

  6. Рефлексия и корректировка ответа (generation).

Так что при необходимости качество можно оценить после каждого шага поиска и генерации.

Вот несколько основных подходов к оценке RAG:

  • Классические метрики NLP (BLEU, ROUGE) и ранжирования (recall@k, ndcg). Эти метрики зачастую требуют Ground Truth. К тому же не всегда хорошо коррелируют с человеческой оценкой.

  • Ручная разметка — эксперты оценивают ответы. Точно, но медленно, дорого и не масштабируется.

  • LLM as a judge — оценка ответов с помощью другой LLM. Сравнительно быстро и автоматизируемо, но требует качественных промптов и метрик.

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

  • Найти готовые данные - скорее всего будут довольно общими и не подойдут под конкретную документацию.

  • Ручная генерация данных: берем эксперта/экспертов в вашей документации и ставим задачу придумать вопросы, найти к ним релевантный контекст и ответ.

  • Сгенерировать данные с помощью LLM: такой подход позволяет автоматизировать генерацию данных, при этом можно проводить множественные эксперименты в короткие сроки. Генерация может быть простой (LLM формулирует вопросы и ответы по каждому чанку, например) или с дополнительной логикой (моделируем связи между разными частями документации, разные типы вопросов и сценарии взаимодействия пользователя с документацией)

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

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

Почему мы выбрали RAGAS

После анализа альтернатив (включая DeepEval, TruLens, LangChain Evals) мы остановились на RAGAS — open-source библиотеке, которая позволяет как генерировать данные для тестирования, так и оценивать их с помощью LLM. К преимуществам RAGAS можно отнести:

  • самостоятельно разделяет документы на чанки;

  • формирует различные типы связей между документами и чанками;

  • использует разные стили, типы вопросов;

  • строит граф знаний и на его основе генерирует множество разнообразных сценариев вопросов.

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

Граф знаний: что такое и почему он для нас важен

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

Немного расскажу, как всё это устроено.

Формирование графа знаний
Формирование графа знаний

Среди трансформаций у нас есть экстракторы, которые извлекают данные — по сути выполняют задачу feature extraction. Из наших текстов извлекаются заголовки, эмбеддинги, сущности, темы и т. д.

Полный список экстракторов:

  • HeadlinesExtractor(LLMBasedExtractor)

  • EmbeddingExtractor(Extractor)

  • NERExtractor(LLMBasedExtractor)

  • ThemesExtractor(LLMBasedExtractor)

  • KeyphrasesExtractor(LLMBasedExtractor)

  • SummaryExtractor(LLMBasedExtractor)

  • TitleExtractor(LLMBasedExtractor)

Также есть один сплиттер — HeadlineSplitter. Как можно догадаться по названию, он не будет работать, если на предыдущем шаге не определили HeadlinesExtractor, ведь его задача — побить документ на чанки, основываясь на заголовках, которые были найдены. Также есть метод ограничения длины самого чанка количеством токенов.

Построение связей. У нас есть 4 (на самом деле 3, потому что два из них есть одно и то же) класса для построения связей: косинусная связь (для суммаризации), перекрытие сущностей и jaccard similarity.

На каждом этапе можно применять трансформацию не ко всем элементам нашего графа, а к определенным подвыборкам. Есть и соответствующие фильтры: только к чанкам, только к документам и к документам с определенной длиной. А еще — CustomNodeFilter, о котором чуть позже.

Пример нашего пайплайна

Как выглядит дефолтный pipeline? Первый шаг — HeadlineExtractor, который применяется к документам длиной не меньше 500 токенов. Потом мы разбиваем эти документы на чанки с помощью HeadlineSplitter, берем summary и применяем CustomNodeFilter. Затем берем эмбеддинги от summary, извлекаем темы из chunk, извлекаем сущности из chunk, строим косинусные связи между документами и строим связи по перекрытию сущностей.

Разберем на примере. У нас есть четыре абстрактных документа, один из которых меньше, чем 500 токенов.

Шаг первый: мы получили заголовки (headlines). Первый слева документ меньше 500 токенов, к нему данная трансформация не применяется (он слишком короткий)

Построение графа знаний — шаг первый
Построение графа знаний — шаг первый

Шаг второй: по этим headlines мы побили документы на чанки. Допустим, что у третьего слева документа headlines не найдены (пустой список) и он не превышает максимально ограничение по длине, поэтому чанков у него нет.

Построение графа знаний — шаг второй
Построение графа знаний — шаг второй

Шаг третий: мы берем summary у документов. Вот здесь как раз начинает работать CustomNodeFilter. В чем его идея? Он смотрит на summary родительского документа и на текущий chunk, а затем проверяет — есть ли какая-то связь или нет. Если нет — чанк удаляется. Такой фильтр позволяет удалить из рассмотрения, например, стандартные элементы навигации (меню, footer).

Построение графа знаний — шаг третий
Построение графа знаний — шаг третий

Шаг четвертый: извлекаем themes и NER из чанков, а также получаем эмбеддинги summary документов.

Построение графа знаний - шаг четвертый
Построение графа знаний - шаг четвертый

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

Финальный граф знаний
Финальный граф знаний

Такой пайплайн помогает представить базу знаний в структурированном и понятном виде.

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

Комментарии (0)