Меня зовут Саприн Семён. Я занимаюсь анализом данных и машинным обучением в компании ПГК Диджитал. Сегодня мы начинаем серию статей, в которой я расскажу о том, как мы с командой разрабатывали ИИ-помощника, а также приведу практические кейсы по улучшению точности ответов с минимальными затратами памяти графических процессоров.
Как вы уже могли догадаться, наш ИИ-помощник разработан на основе RAG (Retrieval-Augmented Generation) системы. Хотя принцип работы RAG многим уже знаком и не вызывает того самого «вау», я всё же кратко напомню, как эта система работает, почему она так популярна и почему её ответам можно доверять.
В этой статье я расскажу, как мы разрабатывали RAG-систему для юридического отдела нашей компании, с какими вызовами столкнулись и как их преодолевали. Вы узнаете, почему стандартные подходы не всегда работают, и как, погрузившись в специфику данных, мы смогли значительно улучшить качество ответов, сохранив при этом экономию ресурсов GPU.

Преимущества ИИ-помощника на основе RAG
Начнём понемногу погружаться в тему и первым делом проговорим про преимущества ИИ-помощника по сравнению с другими онлайн системами по типу ChatGPT, DeepSeek, Claude и тд.
ИИ-помощник на основе RAG – это не что иное, как вопросно–ответная система или, другими словами, чат-бот, который должен генерировать ответы на вопросы пользователя. Эта система работает in-house, то есть никакие данные, которые вы ему подаёте, не утекают в бесконечные просторы всемирной паутины. Это очень важный фактор, ведь если вы любитель облегчить свою работу, задавая вопросы какому-нибудь ChatGPT, прикрепляя ему рабочие документы (или даже несколько страниц этих документов), то будьте уверены, что товарищи «безопасники» вас не похвалят и уж точно не погладят по голове за то, что через час у вас произойдёт утечка корпоративных данных.
Но почему именно RAG? Теперь мы можем ответить этот вопрос:
-
конфиденциальность: система работает in-house, что исключает утечку данных;
-
гибкость: она может быть адаптирована под любую предметную область, будь то юриспруденция, медицина или финансы;
-
доверие: ответы системы основаны на реальных документах, что делает их достоверными и обоснованными.
Принцип работы RAG
Перейдём к принципу работы RAG. В сети уже крайне много описаний работы этой системы, не хочу повторяться, поэтому кратко:
RAG состоит из двух основных инструментов:
-
Retriever (поисковик) – часть, которая отвечает за нахождение релевантных документов, вернее их отрывков, которые называются чанками.
-
Generator (генератор
) – часть, которая отвечает за генерацию ответа на основе чанков, найденных ретривером.
В качестве генератора и ретривера мы используем open-source инструменты Open-Source они как раз для того, чтобы всё было in-house.
Первый заказчик: юридический отдел ПГК
Первым внутренним заказчиком ИИ-помощника стал юридический отдел Первой грузовой компании (ПГК), материнской компании ПГК Диджитал. Это решение было инициативой самого отдела, так как юристы ежедневно сталкиваются с огромным объёмом сложных документов, требующих высокой точности и внимания к деталям.
Эффекты от внедрения ИИ-помощника:
-
снижение нагрузки на экспертов и высвобождение времени на другие важные задачи;
-
ускорение адаптации новых сотрудников и вовлечения их в рабочие процессы;
-
снижение порога экспертности нанимаемых сотрудников.
Чтобы у читателя не возникло недопонимания, важно четко обозначить позицию: ИИ-помощник создан для того, чтобы помогать сотрудникам экономить время, автоматизируя рутинные задачи, такие как поиск необходимых данных и извлечение информации из документов. Его цель — упростить и ускорить рабочие процессы, освобождая время для более важных и творческих задач.
Baseline и вводные
Baseline (Semantic Search): В качестве базового подхода мы взяли простой семантический поиск. В качестве модели для создания эмбеддингов использовали эмбеддер от ВК: deepvk/USER-bge-m3.
К счастью, наши коллеги помогли сформировать выборку для оценки качества получившегося функционала. В нашем распоряжении оказалась тестовая выборка содержащая:
-
~100 вопросов (мало, но зато не синтетика)
-
Ответы на эти вопросы, сформированные экспертами
-
Куски текста, в которых содержится информация для ответа на вопрос
Также хочется упомянуть, что в базовом решении мы использовали Recursive Chunking с параметрами разделения:
-
chunk_size=1000 – длина чанка в символах
-
chunk_overlap=200 – длина перекрытия чанков
-
separators=[“nn”, “n”, “.”, “;”, “,”, ” “]. Эти разделители были выбраны как стандартные маркеры, которые обычно указывают на завершение мысли или логического блока текста.
Думаю, читателю понятно, что при нахождении «идеальных» чанков, даже относительно слабая LLM легко выдаст правильный ответ (если её не начнёт уносить на родной язык), а без них – какую бы модель вы ни взяли, она не сможет ответить на вопрос, ответ на который содержится в закрытых корпоративных документах (по крайней мере мы на это надеемся ). И, если вы не забыли, мы находимся в условиях экономии памяти GPU.
Исходя из этого, в качестве модели генератора мы использовали gemma-2-9b-it-sppo-iter3, которая довольно хорошо справляется с текстами на русском языке, но при этом не требует большой нагрузки на GPU. Размер контекстного окна в этой модели – 2048 токенов.
Размер контекстного окна – это максимальная длина последовательности, которую языковая модель способна обработать за один проход. Другими словами, представьте, что ваша школьная жизнь — это как контекстное окно языковой модели. Самые яркие и запоминающиеся истории — это последние годы учёбы: последний звонок, выпускной. А вот что было в начальных классах? Кто-то помнит, как учился писать буквы или как впервые получил двойку? Вряд ли. Эти воспоминания, как и начало длинного текста, просто стираются из памяти, потому что “окно” внимания ограничено.
В выбранной нами модели размер контекстного окна – 2048 токенов, что эквивалентно примерно одной странице текста в формате word, написанного 12-м шрифтом. Может показаться, что это довольно простая задача – найти из 100 страничного документа 1 страницу, на которой будет содержаться ответ на вопрос, ведь размер контекстного окна в 2048 токенов нельзя назвать маленьким, но оказывается, что и этого может быть недостаточно.
Думаю, что можно переходить к экспериментам с ретривером, ведь как мы поняли, качество ответов зависит, по большому счету, от него. Наши эксперименты начались с общеизвестных техник.
Пробуем разные подходы RAG
Гибридный поиск (Semantic Search + BM25)
Первым делом мы решили попробовать гибридный подход, сочетающий семантический поиск (по смыслу) и лексический (по ключевым словам). Это дало некоторый прирост в точности, релевантность найденных чанков увеличивалась, и система начинала отвечать на большее количество вопросов, но все равно оставались проблемные моменты.
Хочется сделать один из промежуточных выводов: ставить RAG на систему с юридическими текстами может оказаться не самой простой задачей, ведь такие тексты являются очень сложными для восприятия даже живым человеком, и настроить поиск, который будет отрабатывать с минимальными потерями информации, не всегда удаётся.
Модель кросс-энкодера для ранжирования найденных чанков по релевантности
Также не будем забывать о проблеме Lost-In-The-Middle, которой подвержены большие языковые модели. Она заключается в следующем: при подаче на вход LLM больших контекстов, модель фокусируется на начале и конце входного текста, при этом «забывая» середину. То есть наша задача – отранжировать найденные чанки по релевантности. Другими словами, мы хотим добиться того, чтобы куски текста, в которых содержится бо́льшая часть ответа, модель видела как можно раньше. Для этого используем кросс-энкодер, в нашем случае PitKoro/cross-encoder-ru-msmarco-passage, в надежде на то, что он сможет лучше понять значимость контекста, нежели привычная косинусная мера от би-энкодера.
Саммари документов
В процессе работы мы столкнулись с тем, что не все стандартные подходы и методы, которые хорошо работают в других областях, оказываются неэффективными в нашем случае. Одним из таких примеров стало использование саммари чанков.
Идея подхода:
-
Генерируем саммари по каждому чанку с помощью LLM;
-
По входящему запросу пользователя осуществляем поиск по всем саммари;
-
Для топ-5 найденных саммари возвращаем их начальные чанки для генерации ответа.
В теории, саммари должны обобщать содержание текста, выделяя ключевые моменты и упрощая поиск релевантной информации. Однако в контексте юридических документов мы обнаружили, что саммари зачастую становятся ещё более унифицированными и похожими друг на друга, чем исходные чанки. Это связано с тем, что юридические документы и так содержат много повторяющихся формулировок и стандартных конструкций.
В результате, внедрение саммари не только не улучшило качество поиска релевантных чанков, но в некоторых случаях даже ухудшило его, так как саммари теряли важные детали и нюансы, характерные для конкретных документов.
Аннотация чанков
Ещё одним экспериментом, который не оправдал наших ожиданий, стало использование аннотаций, сгенерированных с помощью LLM.
Идея подхода:
-
для каждого чанка создаём аннотации в виде 5-10 ключевых слов;
-
модифицируем исходные чанки, добавляя перед каждым его аннотацию;
-
для топ-5 найденных модифицированных чанков возвращаем исходные для генерации ответа.
Мы предполагали, что аннотации, состоящие из ключевых слов, в отличие от саммари, которые «размывают» исходные чанки, смогут улучшить качество поиска как для классических методов (BM25), так и для современных подходов, таких как би-энкодеры. Однако на практике аннотации, созданные LLM, оказались не всегда полезными. Они часто содержали ненужную или нерелевантную информацию, что не улучшало точность поиска и не приносило значительного улучшения точности поиска.
Также стоит упомянуть, что создание саммари или аннотации требует дополнительных вычислительных ресурсов GPU. А формула у нас простая: если мы и тратим GPU, то тратим его на улучшение качества функционала, а не для того, чтобы время работы системы увеличилось без существенного улучшения ответов.
Осознание проблемы
Мы пробовали множество классических (и не очень) подходов, которые я не буду здесь затрагивать, все они сильно нагружали систему из-за «бесконечного» обращения к LLM за помощью в разных задачах, но по итогу не приводили к реальным улучшениям. Тогда мы решили отойти от классических подходов и более детально погрузиться в исходные документы, их структуру и содержание.
Предложенные решения
Решение 1:
Изучив имеющиеся документы, возникла одна идея: создание аннотации для каждого чанка, но не те аннотаций, который мы уже пробовали генерировать с помощью LLM, а создавать их вручную, опираясь на структуру юридических документов, ведь в таких текстах всегда присутствует оглавление, которое можно использовать для создания аннотаций. Мы обработали оглавление (текст в начале документа в котором указано, на какой странице содержится какая глава и параграф) и использовали его в качестве аннотаций для чанков.
Глава: «2. Ответственность сторон»
Параграф: «2.3. Условия одностороннего расторжения договора»
Текст: «В случае одностороннего расторжения договора, инициатор расторжения обязан уведомить другую сторону в письменной форме не менее чем за 30 календарных дней до предполагаемой даты расторжения. Уведомление должно содержать причины расторжения и ссылки на соответствующие пункты настоящего договора. …»
Например, если чанк находился в главе “2. Ответственность сторон” и параграфе “2. 3. Условия одностороннего расторжения договора”, то в качестве аннотации к этому чанку мы добавляли именно эти названия. И модифицированный чанк становился следующим:
Решение 2:
Кроме того, мы обратили внимание на важность правильного разделения текста на чанки. Юридические документы имеют чёткую структуру, и важно, чтобы чанки не пересекались между главами или параграфами. Мы начали с того, что разделили тексты на главы, затем на параграфы, и только внутри каждого параграфа выделяли чанки. К каждому чанку добавляли название главы и параграфа в качестве аннотации. Это позволило сохранить контекст, дало системе лучшее понимание каждого чанка и существенно улучшило качество поиска.
Этот подход оказался гораздо более эффективным, как с точки зрения вычислений так и с точки зрения качества ответов.
Решение 3:
И последним неочевидным действием в борьбе за высоким качеством ответов стало самое простое – изменение разделителей для сплиттера (той части, которая разбивает текст на чанки). Изначально мы задали следующие параметры для сплиттера:
-
chunk_size=1000
-
chunk_overlap=200,
-
separators=[“nn”, “n”, “.”, ” “, “”].
После проведения ряда экспериментов с разной длиной разбиения мы пришли к выводам:
-
лучшие ответы получаются при chunk_size=1500, chunk_overlap=400;
-
символ «.» нельзя использовать в качестве разделителя в нашем случае, ведь он приводит к ухудшениям связности контекста
пример:
«Глава 1. Общие положения
1.1. Стороны договора обязаны соблюдать условия, указанные в настоящем документе.
1.2. В случае нарушения условий, сторона-нарушитель обязана возместить убытки в соответствии со ст. 15 ГК РФ.».
Если у нас в разделителях находится «.», мы можем получить следующие чанки:
• Глава 1. Общие положения 1.1.
• стороны договора обязаны соблюдать условия, указанные в настоящем документе. 1.2.
• в случае нарушения условий, сторона-нарушитель обязана возместить убытки в соответствии со ст. 15 ГК РФ.
Это может стать проблемой в том случае, если вы хотите, чтобы модель при генерации финального ответа, указывала название и номер пункта, из которого он был получен (что является хорошей практикой, ведь пользователь всегда может провалидировать ответ системы, открыв указанные в ответе пункты документа – тем самым повышается доверие к системе).
Также мы убрали пробел из разделителей, что привело к тому, что чанки не разрываются на середине предложения, теряя важную информацию. Конечно, данный подход увеличивает их средний размер, но при этом сохраняет контекст, что необходимо в случае работы с юридическими документам, где важно сохранить начальную структуру.
Заключение и основные выводы
Разработка RAG-системы для юридических документов оказалась сложной, но интересной задачей. В процессе мы провели множество экспериментов, пробуя различные подходы, и пришли к нескольким ключевым выводам. Во-первых, аннотации на основе оглавления показали себя гораздо эффективнее, чем автоматически сгенерированные саммари или ключевые слова. Они позволили сохранить контекст и значительно улучшить качество поиска. Во-вторых, правильное разделение текста на чанки с учётом структуры документа (главы, параграфы, пункты) стало критически важным для сохранения смысловой целостности.
Несмотря на то, что многие классические подходы не оправдали ожиданий, этот опыт подтвердил, что адаптация под специфику данных является ключом к успеху. Мы продолжаем улучшать нашу систему, и в следующей статье я расскажу, как мы дообучали модель эмбеддингов для ещё более точного поиска, а также приведу метрики, которые мы использовали для оценки качества работы нашей системы. Оставайтесь с нами!
Автор: huraligne