RAG против нормативки: почему это всегда проигрышная битва
Вы берете красивый, отполированный на хакатоне RAG-пайплайн, подсовываете ему PDF с техническим регламентом и ждете внятного ответа на вопрос "Какие требования к пожарной безопасности в столовой на 50 мест?". Через секунду получаете галлюцинацию, цитату из ГОСТа по молочным продуктам и рекомендацию установить огнетушитель в холодильник. Знакомая картина?
Стандартный RAG (Retrieval-Augmented Generation) спроектирован для относительно плоских, связных текстов - документации к API, статьям, книгам. Нормативные документы - это другой зверь. Их структура убивает наивный чанкинг, а семантика требует понимания контекста, который растянут на сотни страниц.
Главная ошибка: считать, что разбив документ на куски по 512 токенов и закинув в векторную БД, вы сохраните его смысл. Для нормативки это все равно что разрезать карту метро на квадратики и пытаться по ним построить маршрут.
Три кита, на которых спотыкается обычный RAG
- Иерархическая структура. Разделы, главы, пункты, подпункты. Запрос про "пункт 5.2.3" может быть найден, но контекст - что это часть главы 5, которая ссылается на приложение Б - потерян.
- Кросс-ссылки и зависимости. "В соответствии со статьей 15 настоящего Федерального закона...", "см. таблицу 2.4", "за исключением случаев, указанных в п. 3.1". Векторный поиск по чанкам эти связи игнорирует. Получается информационный вакуум.
- Определения и термины. Критически важный для понимания блок определений ("Пожарная безопасность - состояние объекта...") обычно находится в начале документа. При чанкинге он теряется, и LLM начинает трактовать термины на основе своей тренировочной выборки, что гарантирует ошибку.
Именно поэтому подходы вроде иерархического RAG (RAPTOR) показывают такие скромные результаты на нормативных документах. Они пытаются навести иерархию поверх уже убитой чанкингом структуры.
Спасительная графа: как иерархический граф знаний решает проблемы RAG
Нужно сменить парадигму. Вместо "разрезания" документа мы строим его семантический скелет - граф знаний. Узлы - это смысловые элементы: разделы, пункты, определения, таблицы, формулы. Ребра - связи между ними: вложенность ("является частью"), ссылки ("ссылается на"), терминологические ("определяет термин").
Когда приходит запрос, вы ищете не просто семантически похожие куски текста, а обходите граф. Нашли узел "Пожарная безопасность столовых"? Отлично, теперь по ребрам можно подтянуть связанные узлы: определение термина из глоссария, исключения из другой главы, таблицу с нормами из приложения. Контекст собирается не наугад, а по логическим связям. Это именно тот подход, о котором говорят в статье "RAG сломался? Графы знаний возвращаются".
Что дает граф по сравнению с векторами
| Проблема векторного RAG | Решение через граф знаний |
|---|---|
| Потеря иерархии и нумерации | Узлы графа хранят метаданные (уровень, номер), ребра "часть-целое" сохраняют структуру. |
| Пропуск кросс-ссылок | Ребра типа "ссылается на" явно соединяют связанные пункты, обход графа их учитывает. |
| Изолированные чанки | Контекст для LLM формируется целенаправленным обходом от релевантных узлов к смежным. |
| Сложность поиска по таблицам и схемам | Таблицы и иллюстрации - полноправные узлы графа, связанные с поясняющим текстом. |
Собираем граф: пошаговая инструкция без магии
Забудьте про обещания "одной кнопкой". Построение качественного графа - это пайплайн из нескольких четких этапов. Вот как это делается в 2026 году с использованием современных моделей и инструментов.
1 Извлекаем скелет: парсим структуру документа
Первым делом нужно вытащить иерархию заголовков. Универсального парсера для всех PDF не существует, поэтому комбинируем методы.
- Используйте продвинутые библиотеки вроде Unstructured.io 0.15+ (актуально на 2026) или PyMuPDF с эвристическими правилами для распознавания нумерации (1., 1.1, 1.1.1).
- Для сложных случаев подключайте LLM с vision-способностями (например, GPT-4o или более новая Claude 3.7 Sonnet). Даете ей изображение страницы и просите вернуть JSON с заголовками и их уровнями. Дорого, но точно.
Ошибка: Полагаться только на размер шрифта для определения уровня заголовка. В нормативах часто используются сложные схемы нумерации (буквы, римские цифры), которые визуально не отличить. Всегда проверяйте логическую последовательность (после 1.2.3 должен быть 1.2.4 или 1.3).
2 Наполняем узлы: разбиваем на смысловые блоки
Теперь, зная границы разделов, наполняем каждый узел (заголовок) содержимым. Здесь важно не резать по токенам, а сохранять смысловую цельность.
# Пример логики на Python (псевдокод)
# Имеем: документ, список заголовков с их позициями (start_page, end_page)
for section in document_structure:
# Собираем весь текст от start_page до end_page
content = extract_text(section.start, section.end)
# Внутри этого большого куска ищем подразделы (пункты)
# Можно использовать паттерны regex для маркеров пунктов или легкую LLM
subsections = split_into_subsections(content, section.level + 1)
# Каждый подраздел становится дочерним узлом
for sub in subsections:
create_node(parent=section.node_id, text=sub.text, type="paragraph")
Отдельный фокус - извлечение определений. Обучите маленькую модель NER (например, на основе DeBERTa-V3) или используйте промпт к LLM, чтобы найти в тексте паттерны типа "Термин - это ..." или "Под X понимается Y". Эти термины станут особыми узлами в графе, связанными со всеми местами их употребления.
3 Плетем паутину: находим и проставляем связи
Это самый важный и сложный этап. Нужно выявить все явные и неявные связи в документе.
- Явные ссылки: "см. рис. 5", "в соответствии с пунктом 2.3", "приложение А". Для их извлечения идеально подходит тонко настроенная модель извлечения отношений (Relation Extraction). Можно использовать открытые модели типа REBEL или промптить большие LLM, но это затратно. Лучше собрать датасет и дообучить небольшую модель.
- Неявные связи: Упоминание одного и того же термина в разных разделах, логические условия ("если ... то ..."). Здесь помогает семантическое сравнение эмбеддингов узлов. Но не как основной метод, а как вспомогательный для предложения возможных связей человеку или для проверки.
Каждую найденную связь добавляем в граф как ребро с типом: REFERENCES, DEFINES, IS_PART_OF, CONDITIONAL_ON.
4 Выбираем движок: где хранить и как искать
Граф построен. Теперь нужна система, которая умеет его хранить и эффективно обходить. Варианты:
- Специализированные графовые БД: Neo4j 6.0+ или Amazon Neptune. Мощные, с языком запросов Cypher, но требуют отдельного обслуживания. Идеальны для сложных обходов.
- Гибридный подход: Храните узлы и их векторные представления в pgvector (расширение PostgreSQL), а связи - в отдельной таблице. Проще в эксплуатации, если у вас уже есть Postgres. Для обхода графа пишете рекурсивные SQL-запросы.
- In-memory библиотеки: Для пробных проектов или документов среднего размера подойдут NetworkX или igraph в Python. Все держится в оперативке, что быстро, но не масштабируется.
Поиск начинается с семантического запроса к векторам узлов. Нашли топ-3 релевантных узла. Но вместо того чтобы просто отдать их LLM, вы запускаете обход графа от этих узлов на 2-3 шага в глубину по определенным типам ребер (например, игнорируя чисто структурные связи "IS_PART_OF", но включая "REFERENCES" и "DEFINES"). Собираете текст всех посещенных узлов - это и будет ваш обогащенный, связный контекст для генерации ответа. Этот метод, кстати, отлично дополняет идеи из статьи про "обратный обход графа".
Чего ждать и как не облажаться
Граф знаний - не серебряная пуля. Это более сложная, ресурсоемкая архитектура. Перед стартом проекта прикиньте эти пункты.
Подводные камни, о которых молчат в туториалах
- Качество парсинга - фундамент всего. Потратьте 80% времени на отладку именно этого этапа. Один пропущенный уровень заголовка сломает всю иерархию.
- Ссылки бывают битыми. Документ может ссылаться на "пункт 12.45", которого не существует. Ваш пайплайн должен такие случаи детектировать (хотя бы логировать) и не строить на них ребра.
- Граф может стать слишком большим. Если каждый абзац - узел, а каждое слово "это" - связь, вы получите "мусорный" граф, который будет шуметь при поиске. Определите минимальную значимую единицу (например, пункт) и игнорируйте несущественные связи.
Практический совет: Не стройте граф для всего документального массива сразу. Начните с одного ключевого документа (например, основного закона в вашей области). Отработайте на нем пайплайн, добейтесь качества ответов. Затем масштабируйтесь, добавляя связанные документы и устанавливая связи между ними. Это и есть настоящий корпоративный граф знаний.
А что там с таблицами и диаграммами?
Таблицы в нормативах - это часто критичные данные (нормы, коэффициенты). Просто засунуть их в граф как текстовый узел - преступление.
- Используйте специализированные модели для распознавания таблиц (Table Transformer) и их структуры (заголовки столбцов, строк, объединенные ячейки).
- Преобразуйте таблицу в семантическое представление, например, в набор утверждений: "Для зданий класса Ф1.3 минимальная ширина эвакуационного выхода составляет 1.2 метра".
- Каждое такое утверждение станет отдельным узлом-фактом, связанным с узлом-таблицей и с узлами разделов, где эта таблица упоминается. Это тяжелая работа, но без нее поиск по числовым нормам будет невозможен. О проблемах с таблицами в RAG хорошо написано в обзоре "RAG в 2026: хакеры атакуют, таблицы сопротивляются".
И что, теперь все идеально?
Нет. Иерархический граф знаний решает проблему контекста и связности, но рождает новые вызовы. Сложность разработки и поддержки выше. Нужны специалисты, которые понимают не только ML, но и графовые базы данных. Запросы становятся тяжелее, может страдать скорость ответа на сложные вопросы, требующие глубокого обхода.
Но выбор простой: либо вы миритесь с поверхностными, а иногда и опасными ошибками обычного RAG, либо инвестируете в создание системы, которая понимает документы, а не просто ищет в них слова. Для нормативки, где цена ошибки - штраф, суд или авария, выбор, по-моему, очевиден.
Следующий логичный шаг - превратить этот статический граф в живую систему, которая умеет делать выводы. Но это уже тема для отдельного разговора, который начинается со статьи про графы знаний в юриспруденции.
Финальный нюанс: самый главный узел в вашем графе знаний - это мета-узел "Дата актуальности документа". Нормативы меняются. И ваш граф должен уметь это отслеживать, отмечая устаревшие связи красным. Иначе через год ваша идеальная система начнет тихо выдавать отмененные нормы, и никто этого не заметит. Пока что автоматического решения этой проблемы нет - только жесткий процесс ручного контроля версий.