← Все статьи

Загрузка документов в RAG: PDF, таблицы, распознавание и версии без тихого мусора

Конвейер приёма документов для промышленного RAG: неизменяемый оригинал, разбор PDF и сканов, таблицы без сломанных ячеек, карантин парсера, версии и идемпотентность.

Загрузка документов в RAG: PDF, таблицы, распознавание и версии без тихого мусора
Содержание

Демонстрационный RAG «загружает PDF» как одну операцию: файл → текст → фрагменты → векторы. В продакшене это четыре разных контура, и ломается обычно первый. Сканированный договор превращается в кашу колонок, таблица теряет заголовок, колонтитул становится самым частым «фактом» корпуса, а новая редакция регламента живёт рядом со старой без периода действия. Загрузка документов — это производство канонической версии, а не вызов библиотеки разбора.

Ниже — практическая архитектура приёма для команд, которые тащат в поиск корпоративные PDF, выгрузки из ECM, вложения писем и сканы. Статья развивает контур «приём и нормализация» в промышленной инженерии RAG и не подменяет эксперименты с разбиением: сначала честный документ, потом нарезка.

Устойчивая единица здесь — каноническая версия источника, а не файл на диске.

Ключевые выводы

Оригинал неизменяем, индекс — производная. Сырой байтовый объект, результат каждого преобразования и поисковые представления хранят отдельно. Новый парсер должен уметь переиграть корпус без повторной выгрузки из SharePoint.

Разбор — измеряемый продукт, не разовая утилита. Версия парсера, уверенность распознавания, признаки «это таблица / это колонтитул / это скан» входят в манифест документа. Тихий провал разбора загрязняет и лексический поиск, и векторы.

Таблица без шапки — не доказательство. Ячейка, оторванная от заголовков строк и столбцов, даёт уверенный, но ложный ответ. Макет важнее «извлечённого текста».

Версия и удаление — часть контракта свежести. Один source_id с историей редакций, периодом действия и каскадом удаления на фрагменты, кэш и цитаты. «Просто перезалить папку» создаёт двойников.

Карантин дороже молчания, но дешевле инцидента. Документ с низкой уверенностью распознавания или сломанной структурой не должен попадать в боевой индекс «на всякий случай».

Что такое конвейер загрузки документов

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

Практическая граница: любой компонент, который видит текст документа после разбора — нарезка, индекс, оценка, отладочная выгрузка — потребляет канон, а не «то, что получилось из PDF сегодня ночью». Если канон нельзя воспроизвести из оригинала и версии парсера, у вас нет загрузки, есть разовая миграция.

Чем это не является

Это не «выбрать Unstructured или Docling». Библиотека — деталь этапа. Контракт — входы, артефакты, отказ, версия.

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

Это не контроль доступа. Права копируются на документ в момент приёма и обновляются событиями, но решение «можно ли искать» остаётся за фильтром до извлечения. Загрузчик не должен писать в общий индекс текст, который некому будет отфильтровать.

Почему парсер важнее эмбеддинга

Команды меняют модель векторов, когда пользователь жалуется на «глупые ответы». Часто виноват разбор.

Типичная цепочка: PDF с двумя колонками читается слева направо через обе колонки сразу; отрицание («не применяется к подрядчикам») отрывается от условия; повторяющийся колонтитул «конфиденциально, стр. 14» становится частым токеном; в таблице «ставка / 12%» ячейка 12% индексируется без шапки. Эмбеддинг честно считает это смыслом. BM25 честно поднимает колонтитул по слову «конфиденциально».

Второй класс ошибок — отсутствие текста. Скан без OCR даёт пустой канон. Система индексирует имя файла и путь. Поиск по содержанию молчит, поиск по названию врёт. Третий класс — смесь: половина страниц текстовый слой, половина картинка. Наивный парсер извлекает только текстовый слой и молча теряет приложения со сканами.

Неизменяемый оригинал и канон

Рабочая схема — три слоя.

Слой 0 — оригинал. Байты файла, MIME, размер, контрольная сумма, идентификатор в системе-источнике, время source_updated_at. Этот объект не переписывают. Если источник отдал новый файл с тем же именем, это новая версия, а не перезапись.

Слой 1 — канон. Структурированное представление: дерево разделов, блоки (абзац, список, таблица, рисунок, колонтитул), язык, геометрия страницы если есть, уверенность OCR, версия парсера, предупреждения. Формат — ваш JSON или Markdown с явной разметкой таблиц, главное чтобы был стабильный и удобный для сравнения правок. Канон можно пересчитать, оригинал — нет.

Слой 2 — поисковые представления. Фрагменты, лексические поля, векторы. Их можно сжечь и собрать заново из канона. Если вы умеете только «переиндексировать из канона», вы уже впереди большинства демо.

Минимальный манифест документа:

Поле Зачем
source_id устойчивая личность в системе истины
version_id конкретная редакция
content_hash идемпотентность и сверка
parser_version воспроизводимость канона
media_class цифровой PDF, скан, html, офис, письмо
ocr_confidence порог карантина
acl / арендатор копия политики на момент приёма
valid_from / valid_to деловой срок редакции

Повторная обработка с тем же content_hash и той же parser_version не создаёт вторую запись. Смена парсера создаёт новый канон той же версии оригинала и помечает старый канон архивным. Так сравнивают парсеры на одном корпусе.

Текст, макет и сканы

«PDF» — не один формат, а три разных входа.

Цифровой PDF с текстовым слоем

Есть символы, шрифты, координаты. Задача — восстановить порядок чтения и блоки, а не склеить extractText(). Многоколоночная вёрстка, врезки, сноски требуют анализа макета. Библиотеки уровня PyMuPDF, pdfminer, Docling, коммерческие конвейеры различаются именно здесь, а не «умением открыть файл».

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

Скан и «PDF-картинка»

Текстового слоя нет или он пустой мусор. Нужен OCR. Сохраняйте уверенность по странице и по блоку. Низкая уверенность — карантин или повтор с другой моделью распознавания и языком, не молчаливая индексация. Язык документа задавайте явно: автодетект на бланке с латиницей в шапке и кириллицей в теле часто выбирает не тот движок.

Не смешивайте в одном каноне «уверенный текстовый слой» и «сомнительный OCR» без признака. Иначе оценка качества поиска не отличит плохой эмбеддинг от плохой буквы «з» вместо «3».

Гибрид

Много корпоративных PDF — текст плюс вставленные сканы приложений. Конвейер должен идти по страницам: есть текстовый слой достаточной плотности — макетный разбор; иначе OCR. Приложения (вложенные PDF, TIFF) — отдельные документы со связью parent_source_id, а не «ещё страница того же файла», если у них своя нумерация и гриф.

Офисные файлы и письма

DOCX и таблицы Excel часто кажутся «проще PDF», и на этом команда экономит контур. Ревизии Word, внедрённые объекты, скрытые листы и объединённые ячейки ломают канон так же, как двухколоночный регламент. Для таблиц исходник .xlsx обычно честнее, чем «напечатанный в PDF» отчёт: сетка уже есть, её не нужно угадывать по координатам. Печатный PDF остаётся нужен, когда юридически подписан именно он — тогда оригинал PDF, а таблица из Excel, если её прислали отдельно, живёт как связанный источник, а не как домысел парсера.

Письмо — не один документ. Тема, тело, цепочка ответов и каждое вложение имеют разный гриф и разную ценность как доказательство. Канон письма хранит заголовки, участников и ссылки на дочерние вложения; вложения идут тем же конвейером, что и файлы из ECM. Иначе поиск по «тому письму с договором» поднимает цитату из подписи менеджера.

Таблицы, колонтитулы и ложный факт

Таблицы — главный источник уверенно неверных ответов в промышленном RAG.

Ячейка без контекста шапки бесполезна. «12» может быть ставкой, количеством, годом или кодом ошибки. Канон таблицы обязан нести: заголовки столбцов, заголовок строки если есть, объединение ячеек, номер таблицы и подпись. Для поиска часто делают два представления: структурированные строки «столбец=значение» для точных вопросов и сериализованный Markdown/CSV для модели. Оба строятся из одного разобранного грида, а не из «текста вокруг».

Сломанный порядок ячеек (читаем по столбцам вместо строк, или наоборот) даёт правдоподобный абзац. Это хуже пустого результата: модель цитирует «доказательство». На эталонных таблицах считайте не BLEU текста, а совпадение ключ→значение.

Колонтитулы и повторяющиеся шапки страниц нужно классифицировать и отрезать от тела. Иначе BM25 влюбляется в «ООО Ромашка, конфиденциально». Подписи к рисункам, наоборот, нельзя терять: вопрос «что на схеме 3» ищет подпись, а не пиксели. Связка «рисунок — подпись — номер» живёт в каноне до нарезки.

Сноски: либо привязка к абзацу с маркером, либо отдельный блок с номером. Склейка сноски со следующим абзацем меняет правовые формулировки.

Версии, удаления, идемпотентность

Источник редко отдаёт «вот актуальный файл». Он отдаёт события, папки и конфликты имён.

Идемпотентный приём

Ключ (source_id, content_hash) или (source_id, source_version). Повтор события не плодит точек в индексе. Переименование файла без смены хеша — то же содержание, обновить путь в манифесте. Смена хеша — новая версия: старая помечается superseded_by, период valid_to закрывается, фрагменты старой редакции либо снимаются с текущего поиска, либо остаются доступны для запроса «на дату».

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

Удаление

Событие удаления должно каскадом пройти: канон (статус «удалён»), фрагменты, векторы, лексический индекс, кэш ответов, сохранённые цитаты в сессиях. «Файл убрали из папки, в чате всё ещё цитируется» — дефект загрузки, не генерации. Для отзыва прав смотрите соседний материал про права доступа до поиска: это другой триггер, но тот же каскад на производные.

Конфликт версий

Два коннектора принесли один source_id с разными хешами в одну минуту. Нужно правило: источник истины побеждает (ECM, а не почтовый ящик), либо ручной карантин. Молча взять «последний по времени приёма» — лотерея.

Карантин и качество разбора

Карантин — очередь документов, которые не должны попасть в боевой поиск без решения. Причины: OCR ниже порога, ноль извлечённых блоков при ненулевом размере файла, таблица с невозможной геометрией, неподдерживаемый шифр PDF, язык вне политики, вирусный скан, разбор упал исключением.

Каждая запись карантина: оригинал, ошибка, версия парсера, владелец источника, срок. Молча пропустить «разберём потом» означает, что пользователь думает, будто документ в системе. Лучше явный статус в карточке источника: «принят, не проиндексирован».

Оценка парсера — отдельный контур, не путать с эталонным набором вопросов. Набор страниц: двуколоночный текст, скан среднего качества, таблица с объединёнными ячейками, колонтитул, смешанный PDF. Метрики: порядок блоков, F1 ключей таблицы, доля страниц в карантине, регрессия при повышении parser_version. Новый парсер сначала гоняют по этому набору, потом переигрывают канон, потом пересобирают индекс.

Не сравнивайте парсеры «на глаз по одному договору». Сравнивайте на фиксированном снимке корпуса и смотрите, какие поисковые срезы изменились: идентификаторы, таблицы, сканы.

События, пакеты и контракт свежести

Коннекторы разные: SharePoint и диск отдают дельту, почта — письма с вложениями, Git — коммиты, ERP — выгрузку вложений к карточке, ручная папка — ничего. Единый «ночной скрипт по всем шарам» скрывает потерянные события.

Практичный контур: поток изменений (webhook, журнал изменений, очередь) плюс периодическая сверка по хешам. Сверка ловит то, что поток потерял. Поток даёт свежесть. Без сверки вы однажды обнаружите, что три месяца индексируете призрак удалённого файла.

Контракт свежести уже назван в опорном руководстве: source_updated_at, ingested_at, indexed_at, удаление, valid_from / valid_to. Загрузка отвечает за первые два и за то, что канон готов к индексации. Если ingested_at − source_updated_at стабильно больше SLO источника, проблема не в LLM.

Наблюдаемость: сколько документов принято, сколько в карантине, распределение ocr_confidence, доля таблиц с распознанной шапкой, отставание сверки, ошибки коннектора по источнику. Без этих рядов «индекс обновляется» — лозунг.

Типичные ошибки и план на четыре недели

Типичные ошибки

Один вызов extractText на весь PDF. Теряете макет, таблицы и половину сканов.

Перезапись оригинала. Нельзя доказать, что канон соответствует файлу, который видел юрист.

Индексация из карантина «чтобы покрытие было». Покрытие мусором хуже дыры: модель цитирует кашу.

Версия = имя файла. регламент_финал_v3_new(2).pdf не является version_id.

Смена парсера вместе со сменой эмбеддинга. Регрессию невозможно отнести к контуру.

Права только в источнике, не на манифесте. После выгрузки связь с ACL рвётся; см. мультиарендность.

Оценка только ответов чата. Парсер зелёный, пока не разобрали табличный срез запросов.

Четыре недели до контролируемого приёма

Неделя 1 — инвентаризация и слои. Реестр источников, форматов, объёмов. Введите слой оригинала и запрет перезаписи. Посчитайте, сколько файлов сейчас попадают в индекс без хеша и версии парсера — это долг.

Неделя 2 — канон и карантин. Один формат канона, очередь карантина, признаки media_class и OCR. Эталон из 20–40 страниц ваших реальных макетов, не демо-PDF из интернета.

Неделя 3 — таблицы и гибридные PDF. Отдельное представление таблиц, отрезание колонтитулов, постраничный выбор «текст или OCR». Прогоните эталон, зафиксируйте базовую линию.

Неделя 4 — версии и сверка. Идемпотентный ключ, каскад удаления, ночная сверка одного источника. Переиграйте канон новым парсером без повторной выгрузки. Если это невозможно — контур ещё не готов.

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

Частые вопросы

Нужно ли OCR для всех PDF?

Нет. Сначала смотрите плотность текстового слоя по страницам. OCR дорог и сам вносит ошибки. Его включают там, где слоя нет или он пустой, и всегда с уверенностью в манифесте.

Можно ли кормить модель сырым PDF без канона?

Для разового разбора — иногда. Для корпуса с версиями, правами и оценкой — нет. Без канона нельзя переиграть парсер, сравнить регрессию и отрезать колонтитул один раз для всех каналов поиска.

Чем загрузка отличается от подготовки данных?

Подготовка данных — ещё и политика: что вообще индексировать, кто владеет источником, как часто обновлять. Загрузка — технический контур, который эту политику исполняет: оригинал, канон, карантин, версии.

Как версионировать, если ECM не даёт номера редакции?

Свой version_id от хеша содержания плюс source_updated_at. Имя файла не использовать как истину. Конфликт хешей при том же id — карантин.

Что делать с зашифрованными и защищёнными от копирования PDF?

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

Нужно ли хранить канон, если есть оригинал?

Да. Пересчёт канона дорог (OCR). Канон — кэш преобразования с версией парсера. Оригинал нужен, чтобы этот кэш можно было инвалидировать.

Как понять, что виноват парсер, а не поиск?

Смотрите канон найденного фрагмента: есть ли в нём шапка таблицы, не склеены ли колонки, не торчит ли колонтитул. Если канон уже врёт, гибридный поиск только размножит ложь.

Связана ли загрузка с промпт-инъекцией?

Да. Каждый проиндексированный источник — канал ввода. Карантин и классификация «это текст документа, не инструкция» не заменяют защиту от инъекций, но отсекают очевидный мусор и чужие вложения без разбора.

Можно ли пропустить карантин для «простых» DOCX?

Можно снизить порог, нельзя убрать учёт ошибок. DOCX тоже бывает с внедрёнными OLE-объектами, сломанными таблицами и ревизиями. Идемпотентность и хеш нужны всем форматам.

Когда подключать мультимодальный индекс картинок?

Когда канон уже стабилен для текста и таблиц, а вопросы реально требуют схемы или чертежа. Иначе вы удвоите стоимость, не закрыв базовый разбор. Это соседняя тема кластера, не замена загрузке.

Дальнейшее чтение

Заключение

Промышленный RAG перестаёт врать таблицами в тот момент, когда загрузка становится отдельным продуктом: неизменяемый оригинал, воспроизводимый канон, карантин вместо тишины, версии вместо файла с пятью «финал» в имени. Эмбеддинг и переранжировщик работают поверх этого контура, а не вместо него.

На этой неделе сделайте одно проверяемое действие: запретите перезапись оригинала и запишите content_hash плюс parser_version на каждый принятый файл. Затем положите в карантин всё, что сейчас индексируется без текстового слоя и без OCR. Остальное — наращивание той же границы: макет, таблицы, сверка, переигрывание парсера без повторной выгрузки.

Разбор документов скучен. Именно он отделяет корпоративный поиск от папки PDF с вежливой моделью сверху.