RAG на реальных корпоративных документах: почему 70% работы — это парсинг, а не нейросеть
Почему в RAG-проектах для B2B 60–70% бюджета уходит на парсинг и подготовку данных, а не на нейросеть. Грабли реальных корпоративных PDF: гибридные сканы, таблицы, колонтитулы, версии — и решения с кодом.
Когда клиент говорит «сделайте нам ассистента по нашей базе знаний», в его голове проект выглядит так: берём 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» отрывается от строки «Кредит на оборудование». Ответ модели на вопрос про ставку становится лотереей.
Что сработало у нас:
- Детект таблиц отдельным проходом (camelot/pdfplumber для цифровых PDF, table-transformer для сканов).
- Сериализация таблицы в текст парами «заголовок: значение», а не в 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, а не на приёмке.
Основная работа приходится на левую и среднюю часть схемы. Модель стоит в самом конце и меньше всего влияет на качество ответов.
Чанкинг: структура важнее длины
Классический совет «режь по 512 токенов с перекрытием» на корпоративных документах работает посредственно. Регламенты и договоры имеют жёсткую структуру (разделы, пункты, подпункты) — и границы смысла проходят по ней, а не по счётчику токенов.
Наш подход:
- Резать по структурным границам (заголовки, нумерация пунктов), а токенный лимит использовать только как верхнюю границу для слишком длинных пунктов.
- В каждый чанк добавлять «хлебные крошки» контекста:
Документ → Раздел 4 «Порядок возврата» → п. 4.2. Строка стоит копейки в токенах, но резко улучшает и поиск, и способность модели корректно цитировать источник. - Хранить в метаданных чанка: id документа, версию, страницу, способ извлечения (digital/OCR), тип (текст/строка таблицы).
Поиск: dense-эмбеддингов недостаточно
По корпоративным запросам люди ищут артикулы, номера приказов, аббревиатуры внутренних систем. Dense-эмбеддинги на таких запросах промахиваются — «приказ 417-ОД» и «приказ 418-ОД» в векторном пространстве почти неразличимы.
Рабочая схема — гибрид: dense (эмбеддинги) + sparse (BM25) с фьюжном результатов, поверх — реранкер на кросс-энкодере для топ-30. В Qdrant это делается штатно, без второй поисковой системы. Прирост качества от реранкера на наших корпусах стабильно больше, чем от смены LLM на «более умную».
Оценка качества: без неё это не проект, а демо
До продакшена доходит только то, что можно измерить. Минимальный контур, который мы собираем на каждом проекте:
- Голд-сет из 50–100 реальных вопросов от будущих пользователей (не выдуманных аналитиком) с эталонными ответами и ссылками на источники.
- Автоматические метрики retrieval (hit rate, MRR по источникам) — считаются на каждый коммит пайплайна.
- LLM-as-judge для полноты/точности ответа + обязательная ручная выборочная проверка: судья-модель систематически прощает уверенные галлюцинации.
Именно голд-сет позволяет отвечать на вопрос заказчика «а стало лучше?» цифрами, а не ощущениями. И он же дисциплинирует: любое «улучшение» парсинга сначала прогоняется по метрикам.
Что в итоге
Если сжать наш опыт до чек-листа:
- Классифицируйте PDF постранично, а не подокументно.
- Таблицы — отдельным пайплайном, сериализация «ключ: значение», строка = чанк.
- Колонтитулы вычищайте до чанкинга, иначе они съедят ваш retrieval.
- Версионность — процесс с человеком, а не эвристика.
- Чанкинг по структуре документа + контекстные крошки в каждом чанке.
- Гибридный поиск + реранкер до того, как думать о смене модели.
- Голд-сет вопросов — с первой недели проекта.
Ничего из этого не выглядит как «AI» на слайдах. Но именно это отличает RAG, который отвечает правильно и цитирует актуальный пункт регламента, от демки, которая красиво работает на трёх подготовленных файлах.
Мы в MZR Digital строим RAG-системы и AI-ассистентов на корпоративных данных — в том числе в локальном контуре без передачи данных во внешние сервисы. Если оцениваете такой проект, полезны разборы сколько стоит RAG для компании и RAG или дообучение модели. Обсудить вашу задачу и корпус документов можем на встрече за 60 минут.
Читайте также
Похожая задача в вашем бизнесе?
За 60 минут разберём вашу задачу, покажем архитектуру и оценим бюджет.