LLM над сканами и PDF: где ломаются OCR и чанкинг
LLM над сканами и PDF часто сбоит на OCR, таблицах, чанкинге и проверке полей. Разберем типовые поломки и порядок проверки пайплайна.

С чего начинается сбой
Сбой редко начинается на ответе модели. Обычно он появляется раньше, когда система ошибочно решает, что перед ней "обычный PDF". На деле один файл часто смешивает несколько типов содержимого: первая страница идет с текстовым слоем, вторая вставлена как фото, а третья приезжает как скан после печати и повторного сканирования.
Из-за этого пайплайн читает документ не как единое целое, а как набор плохо совместимых кусков. Один модуль берет текст напрямую из PDF, другой запускает OCR только на части страниц, третий режет результат на чанки так, будто структура везде одинаковая. Шум попадает в данные очень рано, и модель получает не документ, а обрывки строк, случайный порядок блоков и потерянные поля.
На счетах, актах, УПД и анкетах это быстро становится проблемой. Здесь важны слова и их место на странице. Если OCR не увидел границу ячейки, строка таблицы склеится с соседней. Если пропал мелкий шрифт рядом с подписью или печатью, модель начнет достраивать пропуск. Если штамп закрыл дату или сумму, ошибка уже меняет смысл документа.
Хуже всего то, что первый промах почти всегда маскируется. Команда смотрит на итоговый JSON и решает, что модель плохо поняла контекст. Но чаще модель ничего не поняла неправильно. Ей просто дали плохой вход.
Обычно цепочка выглядит так:
- PDF неверно относят к текстовым или скановым
- OCR теряет порядок блоков, таблицы и мелкие поля
- чанкинг режет документ по символам, а не по смысловым зонам
- LLM заполняет пробелы догадками
- проверка смотрит на формат, но не сверяет факты с документом
Если на первом шаге страница прочиталась криво, дальше ошибка только закрепляется. Чанк попадает в индекс, модель строит ответ на шуме, а выгрузка уходит в учетную систему или архив уже с неверной суммой, датой или номером. Поэтому разбирать такой пайплайн лучше не с промпта, а с простого вопроса: что именно система увидела на каждой странице.
Что OCR читает неправильно
Проблема часто начинается еще до подключения модели. OCR возвращает текст, который выглядит нормальным, но уже искажает документ. Модель строит вывод на сломанной основе, и дальше точность почти всегда падает.
На уровне символов ошибки кажутся мелочью, пока не ломают номер счета или ИНН. OCR путает 0 и О, 1 и I, 8 и В, латиницу и кириллицу в одной строке. В договоре это может пройти незамеченным. В реквизитах одна такая подмена меняет значение целиком.
Отдельная боль - перекрытия. Круглая печать закрывает две-три цифры в сумме, дате или номере документа. Подпись режет строку пополам, и OCR склеивает куски в странную фразу, где конец одной строки внезапно прилипает к началу другой.
Даже небольшой поворот страницы дает много шума. Если лист наклонен на 2-3 градуса, OCR начинает ошибаться не только в символах, но и в порядке чтения ячеек. Таблица разваливается: сумма уходит в соседний столбец, дата попадает в строку ниже, а итоговая строка смешивается с примечанием.
Особенно неприятны двухколоночные документы. OCR может взять фрагмент из левой колонки, потом сразу подцепить кусок из правой. На выходе получается текст, которого человек в документе вообще не видит. Для модели это уже не документ, а шум с несколькими знакомыми словами.
Многие команды сами усиливают проблему. Они сохраняют только чистый текст и выбрасывают координаты блоков, строк и таблиц. После этого почти невозможно понять, где был заголовок, где ячейка, а где сноска под печатью. Если OCR не отдает координаты и уверенность по блокам, восстановить смысл потом очень трудно. Гораздо надежнее сразу хранить и текст, и геометрию страницы.
Почему таблицы и печати ломают структуру
Человек видит таблицу как сетку с понятными колонками. OCR часто видит просто набор фрагментов текста, которые надо как-то выстроить в линию. На этом месте пайплайн ломается еще до модели.
Таблица живет по ячейкам и колонкам, а не по сплошному потоку текста. Если OCR не восстановил границы, строка с количеством, ценой и суммой легко смешивается с соседней позицией. Один неверный перенос меняет товарную строку целиком: описание остается от одного товара, цена уезжает от другого, а итог по строке уже нельзя проверить без ручного просмотра.
С пустыми ячейками проблема еще неприятнее. OCR часто выбрасывает их, потому что в них нет символов. Для учета это важно. Пустой НДС, пропуск в артикуле или незаполненное поле доверенности - часть смысла документа. Если пустая ячейка исчезла, соседние значения сдвигаются, и пайплайн уже не понимает, чего не было в оригинале, а что потерялось при чтении.
Печати ломают структуру грубо и довольно банально. Круглая печать накрывает сумму, дату или номер. Подпись заезжает на нижние строки таблицы. Сноска из подвала страницы может оказаться между товарными позициями, если OCR сортирует блоки по координатам не так, как это делает человек при чтении.
Отсюда и типичный результат: OCR не видит сетку и превращает таблицу в сплошной текст, пустая ячейка исчезает, значения сдвигаются по колонкам, а подпись или сноска попадает в середину строки товара. Модель потом возвращает аккуратный JSON, но он аккуратно неверный.
Поэтому мало смотреть на финальный ответ. Нужно проверять, сохранил ли пайплайн опорные элементы документа: границы ячеек, пустые поля, порядок строк и зоны, которые закрыла печать.
Как собрать пайплайн по шагам
Рабочий пайплайн начинается не с модели, а с простой развилки: перед вами текстовый PDF или скан внутри PDF. Если этот шаг пропустить, команда часто гонит весь поток через OCR и сразу теряет качество там, где текст уже был доступен в нормальном виде.
Дальше важна аккуратная сборка этапов. У каждого шага своя задача, и смешивать их не стоит.
- Сначала определите тип документа. У текстового PDF забирайте текст напрямую, а OCR включайте только для страниц со сканами, встроенных картинок и плохо читаемых фрагментов.
- Для сканов подготовьте страницу: выровняйте наклон, уберите шум, тени от сгибов и мусор по краям. Даже пара градусов перекоса ломает строки и ячейки сильнее, чем кажется.
- После OCR сохраняйте не только текст, но и координаты блоков, строк и таблиц. Без координат вы не поймете, где была шапка, где подпись, а где печать перекрыла сумму.
- Режьте документ по смыслу: по страницам, разделам, таблицам, подвалам и приложениям. Фиксированные куски по токенам удобны только на бумаге. В реальном счете строка таблицы легко разъезжается между чанками.
- В модель отправляйте только нужный фрагмент и явную схему полей. Сразу после ответа прогоняйте результат через простые проверки: даты, суммы, ИНН, номера договоров, формат валюты, совпадение итогов по строкам.
Особенно часто все ломается на страницах, где рядом стоят таблица, подпись и круглая печать. OCR может прочитать часть печати как текст, а чанкинг по токенам отделит итог таблицы от ее строк. Модель в такой ситуации не рассуждает, а достраивает пропущенное по шаблону. Ошибка выглядит правдоподобно, а потому опасна.
Что передавать в модель
Модели лучше давать узкий контекст. Не весь документ сразу, а один фрагмент с понятной задачей: извлечь номер счета, найти итоговую сумму или собрать строки таблицы. Рядом стоит передать схему полей с типами: дата, число, строка, массив строк таблицы. Ответ получается короче и чище.
Если на странице есть таблица на 20 строк и штамп поверх двух ячеек, имеет смысл передать модели и текст, и координаты спорного блока. Тогда можно попросить ее пометить строки с низкой уверенностью, а не делать вид, что все распознано точно.
Хороший пайплайн не пытается решить все одним запросом. Он сначала отделяет нормальный текст от плохого изображения, потом собирает структуру страницы, и только после этого зовет модель на узкий участок. Финальная проверка по правилам отсекает большую часть тихих ошибок еще до того, как ответ попадет в рабочий процесс.
Как проверять ответ модели
Аккуратный JSON еще не означает, что модель правильно поняла документ. Ошибки чаще всего сидят в полях, которые выглядят вполне правдоподобно: одна цифра в ИНН, перепутанная дата, сумма без НДС вместо общей суммы.
Проверку лучше строить в два слоя. Сначала идут жесткие правила по формату, потом сверка полей между собой и с текстом документа. Такой порядок быстро отсеивает явный мусор и не дает модели додумать недостающее.
Что сверять автоматически
Для сумм полезно пересчитывать документ заново. Если в таблице есть строки с количеством, ценой и суммой, пересчитайте их и сравните с итогом. Даже простая проверка ловит много сбоев OCR: модель может прочитать 8 как 3, а общий итог при этом оставить правдоподобным.
Для реквизитов нужны маски и короткие правила:
- ИНН: 10 или 12 цифр
- КПП: 9 цифр
- расчетный счет: 20 цифр
- дата: один допустимый формат или короткий список форматов
Этого все равно мало. Поле лучше не брать по одному лучшему ответу. Сохраняйте несколько кандидатов, например три версии даты или суммы с оценкой уверенности. Тогда система не застревает на первом совпадении и может выбрать вариант, который лучше согласуется с остальными данными.
Низкую уверенность лучше помечать сразу и отправлять документ человеку. Не нужно добирать ответ любой ценой. Если OCR дал два похожих ИНН, а печать закрыла последние цифры счета, ручная проверка дешевле, чем ошибка в оплате или отчете.
Очень помогает хранить рядом с каждым извлеченным полем его исходный фрагмент. Не просто значение 7701234567, а кусок страницы, где система его взяла: строку, координаты блока или вырезку текста после OCR. Тогда ревьюер видит причину ошибки за несколько секунд, а не ищет поле по всему PDF.
Рабочая схема обычно выглядит скучно, но именно она дает результат: система извлекает поле, прикладывает 2-3 кандидата, считает уверенность, запускает проверки по маскам и сверку сумм, а спорные случаи складывает в очередь на просмотр.
Пример на счете и акте со штампом
Поставщик прислал один PDF на 14 страниц. Сначала идут обычные страницы с текстовым слоем, а в конце - фото со смартфона: акт, подписи, круглая печать, немного смазанный фокус. Для человека это еще читаемо. Для пайплайна уже нет.
Счет и акт похожи по полям: номер, дата, таблица, итоговая сумма. Если резать файл чанками по две страницы подряд, в один кусок легко попадают конец счета и начало акта. Модель видит рядом две суммы, два набора реквизитов и часто берет не тот итог. Ошибка выглядит правдоподобно, поэтому ее легко пропустить.
На фото проблема усиливается. Круглая печать частично закрывает сумму "Итого", а в таблице длинные позиции переносятся на новую строку. OCR может склеить ячейки, потерять разделитель копеек или отнести хвост строки к следующей позиции. В итоге "12 540,00" превращается в "1254000", или сумма строки уезжает в соседний столбец.
В таком файле лучше не обрабатывать весь PDF как один поток текста. Гораздо надежнее сначала отделить счет от акта по маркерам вроде "Счет на оплату" и "Акт", потом определить, где есть таблица, а где свободный текст, отдельно прогнать OCR для фото, сохранить координаты блоков и вытаскивать суммы, ИНН и номера документов не из общего чанка, а из найденных зон.
После такого разбиения ошибок обычно становится меньше в разы. Модель перестает путать итог счета с итогом акта, потому что больше не видит их в одном куске. А переносы строк внутри таблицы уже не ломают структуру целиком.
Дальше нужна простая проверка после извлечения. Если сумма по строкам не сходится с "Итого", если дата акта внезапно попала в счет или если в одном документе нашлись два разных ИНН, запись надо отправлять на повторный разбор. На таких смешанных PDF это спасает чаще, чем попытка дожать модель еще одним промптом.
Если пайплайн нужен для production, особенно в РФ, храните не только итоговый JSON, но и промежуточные артефакты: текст OCR, координаты таблицы, версию промпта и причину флага проверки. Тогда спорный счет можно разобрать быстро, а не гадать, где именно сломалась цепочка.
Где команды чаще всего ошибаются
Когда команды запускают извлечение данных из сканов и PDF, они часто пытаются срезать путь. Берут файл целиком, отправляют его в модель и ждут готовый JSON. На аккуратном цифровом PDF это иногда срабатывает. На скане счета, акта или доверенности с печатью такой подход быстро ломается: модель видит смесь текста, шума и потерянной структуры.
Первая ошибка простая: пропускают нормальный OCR и разметку документа. Если система не отделила заголовок от таблицы, подпись от печати, а колонку от примечания, дальше уже не так важно, какая у вас модель. Плохая подготовка входа почти всегда сильнее любой смены модели.
Вторая ошибка встречается почти у всех. Текст режут по числу символов или токенов, потому что так быстрее. Но таблица не знает, где у вас лимит. В итоге одна строка счета попадает в первый чанк, сумма и НДС - во второй, а номер позиции - в третий. Модель потом уверенно склеивает куски, которых рядом в документе никогда не было.
Еще одна проблема всплывает позже, когда нужно объяснить, откуда взялось поле. Команда хранит только итоговое значение и теряет координаты блока на странице. Потом нельзя быстро проверить источник, подсветить фрагмент оператору или понять, где случился сбой: в OCR, в чанкинге или в самой модели.
Для каждого поля стоит сохранять хотя бы четыре вещи:
- номер страницы
- координаты блока
- короткий фрагмент исходного текста
- уверенность OCR и модели
Часто ломает не OCR сам по себе, а доверие к первому ответу модели. Если сумма строк не сходится с итогом, дата вне формата или ИНН имеет неверную длину, система должна остановиться и пометить поле для проверки. Простые правила отсекают много тихих ошибок. Без них команда замечает проблему только после жалобы бухгалтера или клиента.
И самая частая промашка - не собирать набор плохих документов. На тестах все выглядит прилично, потому что в папке лежат ровные PDF без теней и перекосов. В работе приходят серые сканы, кривые штампы, таблицы с узкими колонками и страницы, снятые на телефон. Если такого набора нет, любая правка в OCR, чанкинге или промпте может незаметно сломать вчерашний результат.
Быстрый чек перед запуском
Перед пилотом берите не самый чистый файл, а самый неудобный: бледный скан, перекошенную страницу, таблицу на две страницы и печать поверх текста. Один такой документ быстро показывает, где ломается пайплайн.
Сначала разделите входы на два сценария. Текстовый PDF и скан нельзя обрабатывать одинаково. Если в PDF есть нормальный текстовый слой, берите его напрямую. Если слой пустой, битый или смешан с картинками, тогда подключайте OCR.
Потом проверьте несколько вещей.
- OCR должен отдавать текст вместе с координатами блоков, номерами страниц и порядком чтения. Иначе модель склеит колонки, перепутает подпись со строкой таблицы или унесет печать в соседний фрагмент.
- Чанкинг не должен резать сущность пополам. Если таблица, подпись или круглая печать попали на границу чанка, границу нужно переносить.
- Для таблиц нужно отдельное правило сборки. Обычный разрез по символам или токенам почти всегда ломает строки, где сумма, НДС и номер позиции живут в разных колонках.
- Проверка должна ловить тихие ошибки до ответа пользователю: пустую сумму, дату вроде 31.11.2024, дубли строк после поворота страницы.
- Ручная проверка должна открывать точный исходный фрагмент: страницу, координаты блока и соседний контекст. Иначе на поиск одного поля уйдут минуты.
Тестовый набор тоже должен быть неприятным: плохие сканы, штампы поверх текста, повернутые страницы, серые копии, документы с двумя похожими таблицами и файлы, где часть данных уехала в колонтитул. Если этот чек не проходит хотя бы на десяти сложных документах подряд, запускать пайплайн рано.
Что делать дальше
Начните не с модели, а с набора документов, на которых пайплайн чаще всего падает. Возьмите 30-50 реальных файлов: кривые сканы, тусклые печати, смешанные PDF, страницы с таблицами, копии после нескольких пересылок. Красивые образцы почти ничего не показывают.
Дальше разделите пайплайн на три независимые проверки. Если мерить только финальный ответ, вы не поймете, где именно ошибка: OCR спутал символ, чанкинг разрезал строку таблицы или валидатор пропустил невозможную сумму. Для каждого документа сохраняйте промежуточный результат и считайте отдельные метрики по каждому шагу.
Для OCR отмечайте зоны, где путаются похожие символы, теряются подписи и ломаются номера документов. Для чанкинга проверяйте, не разъезжаются ли строки таблицы по разным кускам и не прилипает ли печать к соседнему полю. Для проверки полей задайте простые правила: дата существует, ИНН имеет нужную длину, сумма по строкам сходится с итогом.
Отдельно соберите набор для регресса. Включите в него таблицы со слиянием ячеек, страницы со штампом поверх текста и смешанные PDF, где часть страниц текстовая, а часть пришла как скан. Такой набор быстро показывает, что сломалось после новой версии OCR, другого чанкинга или смены модели.
Если вы сравниваете модели через RU LLM, удобно прогонять один и тот же набор документов через единый OpenAI-совместимый эндпоинт и не менять SDK, код и промпты. Для команд в РФ это еще и упрощает работу с чувствительными данными: у RU LLM логи и резервные копии хранятся внутри страны, а в запросы встроены маскирование PII и аудит по каждому запросу.
Юридическую часть тоже лучше проверять сразу, а не в конце пилота. Для документов с персональными данными важно понимать, где хранятся логи, кто получает к ним доступ и выполняются ли требования 152-ФЗ. Часто именно это решает, дойдет ли пилот до production.
Такие системы начинают работать заметно лучше после очень приземленной дисциплины: плохие документы в тесте, отдельные метрики по шагам и регресс после каждой правки. Это быстро показывает, что реально помогает, а что просто переносит ошибку из одного места в другое.