Перейти к контенту
AI и LLM

RAG на реальных корпоративных документах: почему 70% работы — это парсинг, а не нейросеть

Почему в RAG-проектах для B2B 60–70% бюджета уходит на парсинг и подготовку данных, а не на нейросеть. Грабли реальных корпоративных PDF: гибридные сканы, таблицы, колонтитулы, версии — и решения с кодом.

Автор: Николай Мазур · · 9 мин чтения

Когда клиент говорит «сделайте нам ассистента по нашей базе знаний», в его голове проект выглядит так: берём LLM, «скармливаем» документы, готово. В нашей практике внедрения RAG-систем для B2B картина другая: обвязка вокруг модели — поиск, чанкинг и особенно парсинг исходников — съедает 60–70% бюджета проекта. А главный источник плохих ответов — не «глупая модель», а мусор, который попал в индекс на этапе подготовки данных.

В этой статье — грабли, на которые мы наступили, разбирая реальные корпоративные корпуса: тысячи PDF со сканами, таблицами, колонтитулами и тремя версиями одного регламента. И решения, к которым пришли.

Что такое «реальные документы»

В демках RAG строится на чистых markdown-файлах. В жизни корпус выглядит так:

  • PDF трёх видов: цифровые (текстовый слой есть), сканы (текстового слоя нет), гибриды (половина страниц — скан, половина — цифра). Последние — худшие: наивный пайплайн обработает половину документа и молча потеряет вторую.
  • Таблицы, которые после наивной экстракции превращаются в кашу из чисел без заголовков.
  • Колонтитулы и штампы («Конфиденциально», номер страницы, дата печати), которые размножаются в каждом чанке и засоряют поиск.
  • Версии: «Регламент_финал_v3_ИСПРАВЛЕНО(2).pdf» рядом с «Регламент_v1.pdf». Если в индексе обе версии, модель уверенно процитирует устаревшую.
  • Excel c merged cells, письма с цепочками ответов, презентации, где смысл — в картинках.

Ни одна из этих проблем не решается «более умной моделью». Все решаются на этапе инжеста.

Грабля 1: гибридные PDF

Первый инстинкт — проверить, есть ли в PDF текстовый слой, и если нет — отправить в OCR. Проблема: проверка обычно делается по всему документу, а гибридные PDF проходят её успешно — текстовый слой «есть» (на части страниц). Итог — в индексе документ с дырами.

Решение — принимать решение постранично:

import fitz  # PyMuPDF

def classify_pages(pdf_path: str) -> list[str]:
    doc = fitz.open(pdf_path)
    kinds = []
    for page in doc:
        text = page.get_text().strip()
        images = page.get_images()
        if len(text) > 50:
            kinds.append("digital")
        elif images:
            kinds.append("scan")     # → в OCR
        else:
            kinds.append("empty")
    return kinds

Порог в 50 символов — эмпирика: страницы-сканы часто содержат «текстовый слой» из одного артефакта распознавания принтера. Страницы с пометкой scan уходят в OCR (мы используем связку PaddleOCR для кириллицы + постобработку), остальные парсятся напрямую. Метаданные о способе извлечения сохраняем в чанк — при отладке ответов это экономит часы: сразу видно, что галлюцинация выросла из криво распознанного скана.

Грабля 2: таблицы

page.get_text() превращает таблицу в поток слов, где «Ставка, % — 12,5» отрывается от строки «Кредит на оборудование». Ответ модели на вопрос про ставку становится лотереей.

Что сработало у нас:

  1. Детект таблиц отдельным проходом (camelot/pdfplumber для цифровых PDF, table-transformer для сканов).
  2. Сериализация таблицы в текст парами «заголовок: значение», а не в markdown-таблицу:
Продукт: Кредит на оборудование. Ставка: 12,5%. Срок: до 60 мес. Обеспечение: залог.
Продукт: Лизинг. Ставка: 14%. Срок: до 48 мес. Обеспечение: предмет лизинга.

Контринтуитивный момент: markdown-таблицы красиво выглядят в промпте, но плохо ищутся. Эмбеддинг строки | 12,5 | 60 | залог | почти не содержит семантики. Пары «ключ: значение» дают и нормальные эмбеддинги, и понятный модели контекст. Каждая строка таблицы — отдельный чанк с метаданными родительского документа и заголовком таблицы.

Грабля 3: колонтитулы

На корпусе в 8 000 страниц колонтитул «ООО Ромашка. Конфиденциально. Стр. N» встречается 8 000 раз. При поиске по запросу со словом «конфиденциальность» топ выдачи забивают чанки, релевантные только колонтитулом.

Детект простой: группируем строки по нормализованному тексту (цифры → #), и всё, что повторяется больше чем на K% страниц документа на одной и той же высоте страницы — колонтитул, удаляем до чанкинга:

from collections import Counter
import re

def find_boilerplate(pages_lines: list[list[str]], threshold: float = 0.6) -> set[str]:
    normalized = Counter()
    for lines in pages_lines:
        for line in set(lines):
            normalized[re.sub(r"\d+", "#", line.strip())] += 1
    n_pages = len(pages_lines)
    return {line for line, cnt in normalized.items() if cnt / n_pages >= threshold}

Грабля 4: версии документов

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

Универсального решения нет, есть процесс:

  • На инжесте считаем сигнатуру содержимого (шинглы) и находим кластеры почти-дубликатов.
  • Внутри кластера выбираем актуальную версию по метаданным (дата изменения, имя файла, явная разметка от владельца корпуса) — и этот выбор подтверждает человек со стороны заказчика, не алгоритм.
  • Старые версии не удаляем, а помечаем superseded_by — иногда вопрос звучит как «что изменилось по сравнению с прошлой редакцией», и тогда старая версия нужна.

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

Пайплайн подготовки корпоративных документов для RAG: цифровые PDF, сканы, гибридные файлы и таблицы проходят постраничное решение о распознавании, очистку от колонтитулов, отбор действующей редакции и резку по структурным границам с метаданными, после чего попадают в индекс с гибридным поиском и реранкером, а качество измеряется на голд-сете

Основная работа приходится на левую и среднюю часть схемы. Модель стоит в самом конце и меньше всего влияет на качество ответов.

Чанкинг: структура важнее длины

Классический совет «режь по 512 токенов с перекрытием» на корпоративных документах работает посредственно. Регламенты и договоры имеют жёсткую структуру (разделы, пункты, подпункты) — и границы смысла проходят по ней, а не по счётчику токенов.

Наш подход:

  • Резать по структурным границам (заголовки, нумерация пунктов), а токенный лимит использовать только как верхнюю границу для слишком длинных пунктов.
  • В каждый чанк добавлять «хлебные крошки» контекста: Документ → Раздел 4 «Порядок возврата» → п. 4.2. Строка стоит копейки в токенах, но резко улучшает и поиск, и способность модели корректно цитировать источник.
  • Хранить в метаданных чанка: id документа, версию, страницу, способ извлечения (digital/OCR), тип (текст/строка таблицы).

Поиск: dense-эмбеддингов недостаточно

По корпоративным запросам люди ищут артикулы, номера приказов, аббревиатуры внутренних систем. Dense-эмбеддинги на таких запросах промахиваются — «приказ 417-ОД» и «приказ 418-ОД» в векторном пространстве почти неразличимы.

Рабочая схема — гибрид: dense (эмбеддинги) + sparse (BM25) с фьюжном результатов, поверх — реранкер на кросс-энкодере для топ-30. В Qdrant это делается штатно, без второй поисковой системы. Прирост качества от реранкера на наших корпусах стабильно больше, чем от смены LLM на «более умную».

Оценка качества: без неё это не проект, а демо

До продакшена доходит только то, что можно измерить. Минимальный контур, который мы собираем на каждом проекте:

  1. Голд-сет из 50–100 реальных вопросов от будущих пользователей (не выдуманных аналитиком) с эталонными ответами и ссылками на источники.
  2. Автоматические метрики retrieval (hit rate, MRR по источникам) — считаются на каждый коммит пайплайна.
  3. LLM-as-judge для полноты/точности ответа + обязательная ручная выборочная проверка: судья-модель систематически прощает уверенные галлюцинации.

Именно голд-сет позволяет отвечать на вопрос заказчика «а стало лучше?» цифрами, а не ощущениями. И он же дисциплинирует: любое «улучшение» парсинга сначала прогоняется по метрикам.

Что в итоге

Если сжать наш опыт до чек-листа:

  • Классифицируйте PDF постранично, а не подокументно.
  • Таблицы — отдельным пайплайном, сериализация «ключ: значение», строка = чанк.
  • Колонтитулы вычищайте до чанкинга, иначе они съедят ваш retrieval.
  • Версионность — процесс с человеком, а не эвристика.
  • Чанкинг по структуре документа + контекстные крошки в каждом чанке.
  • Гибридный поиск + реранкер до того, как думать о смене модели.
  • Голд-сет вопросов — с первой недели проекта.

Ничего из этого не выглядит как «AI» на слайдах. Но именно это отличает RAG, который отвечает правильно и цитирует актуальный пункт регламента, от демки, которая красиво работает на трёх подготовленных файлах.


Мы в MZR Digital строим RAG-системы и AI-ассистентов на корпоративных данных — в том числе в локальном контуре без передачи данных во внешние сервисы. Если оцениваете такой проект, полезны разборы сколько стоит RAG для компании и RAG или дообучение модели. Обсудить вашу задачу и корпус документов можем на встрече за 60 минут.

Похожая задача в вашем бизнесе?

За 60 минут разберём вашу задачу, покажем архитектуру и оценим бюджет.