Проверка ответов нейросети — главная тема кейса, который разработчик опубликовал на Хабре под заголовком «Как я перестал верить нейросети и полюбил контрольные разряды: Zero-Trust для распознавания юридических документов». Автор собирает конвейер, который читает сканы российских юридических документов и готовит по ним реестры для 1С. Это исполнительные листы, постановления ФССП, удержания из зарплаты, УПД и договоры. Статья рассчитана на восемь минут чтения и написана от первого лица: автор рассказывает о собственном проекте, который уже работает в production.
Исходная проблема знакома всем, кто пробовал распознавание документов нейросетью. VLM, то есть модель, читающая изображения, уверенно возвращает реквизиты в идеальном формате. Среди них может оказаться несуществующий ИНН правильной длины или сумма, которую никто не сверил с документом. В логах проекта автор видел сводку «качество: 100 %» рядом со сканом 745×1024 при 96 DPI, с которого реквизиты прочитать невозможно в принципе.
Ниже пересказ того, как автор выстроил защиту от этого: модель предлагает, алгоритм проверяет, человек смотрит только то, что не сошлось. Отдельно разобраны замеры и ограничения, которые автор называет сам.
Откуда взялся проект и почему модель нельзя брать на слово
Проект начался случайно. Автора позвали на встречу внутренней команды, где уже работал другой инструмент распознавания, а в его данных постоянно находились расхождения и ошибки. За два дня автор собрал набросок под узкую задачу: исполнительные листы и удержания из зарплаты, проверенные на документах из открытого доступа. По его словам, его вариант подошёл лучше решения смежной команды по двум причинам: он работал внутри закрытого контура без отправки данных наружу и был заточен под запрошенные типы документов.
Главное ограничение проекта жёсткое: использовать можно только модели внутри периметра. Документы строго конфиденциальны, передавать их нельзя даже другим сотрудникам. Автор признаёт, что модели в контуре работают медленнее привычных облачных подписок, и всё же пишет, что хорошего результата добиться на них можно.
У такой архитектуры есть побочный эффект: она не привязана к конкретной модели. Конвейер работает с любым OpenAI-совместимым шлюзом. Автор проверял его дома на Ollama с моделью Qwen2.5-VL.
Отсюда и принцип, ради которого написана статья: вывод модели никогда не принимается на веру. Нейросеть может ошибиться так, что формат ответа останется безупречным, поэтому надёжность нужно искать вне модели, в детерминированных проверках.
Как устроен конвейер распознавания сканов документов
По словам автора, «из коробки» система поддерживает 9 типов документов:
- постановления ФССП;
- исполнительные документы;
- удержания из зарплаты;
- УПД и накладные;
- договоры;
- кадровые приказы;
- доверенности;
- претензии и иски;
- акты выполненных работ.
Каждый тип оформлен как изолированный плагин. Новый тип добавляется папкой, а ядро о типах ничего не знает. Стек автор называет скромным: Python 3.9+, Pydantic v2, Pillow и PyMuPDF, openpyxl, по желанию rapidocr.
Пакетная обработка идёт в шесть этапов: окружение, поиск файлов, классификация, экстракция, метрики, реестр и Excel.
Классификация устроена каскадом. Сначала работают бесплатные эвристики по имени файла и якорным словам, затем дорогой VLM-роутер. Автор замерял первую ступень на «грязных» бухгалтерских именах вроде Скан0001.pdf и IMG_20240115_143022.jpg. Она закрыла 53,7 % файлов с одной ошибкой маршрутизации, остальное ушло в модель.
Предобработка решает проблемы реальных сканов. Изображения меньше 1500 px увеличиваются вдвое методом Lanczos. Многостраничный PDF обрезается лимитом в 20 страниц. Автор приводит пример: в 45-страничном документе реквизиты с 31-й страницы в лимит не попадают. Это отражается в отчёте и не теряется молча.
Экстракция идёт по плагину: промпт, Pydantic-схема и постобработка. Постобработка чинит типовые ошибки VLM, например восстанавливает формат номера исполнительного производства NNNNN/NN/NNNNN-ИП, если модель потеряла слэши. Ответ модели всегда проходит через безопасный разбор JSON, который вычищает «рассуждения». Их, по наблюдению автора, любят добавлять Qwen и DeepSeek.
На выходе получаются два JSON на документ: полный, с аудитом, и плоский для 1С. К ним добавляются общий реестр и Excel-книга. Для интеграции есть CLI с осмысленными кодами возврата и JSON-RPC-сервер.
Проверка ответов нейросети: четыре стены
Центральная часть статьи — четыре уровня проверок, которые автор называет стенами. Каждая ловит свой класс ошибок.
Контрольные разряды: проверка ИНН по контрольной сумме
Алгоритм проверяет ИНН (10 и 12 знаков), СНИЛС, ОГРН и ОГРНИП, БИК и 20-значный счёт. Для счёта используется ключ ЦБ РФ по положению 565-П. Выдуманное, но правдоподобное значение отсекается до того, как попадёт в реестр.
Автор приводит ядро проверки 10-значного ИНН юрлица. Первые девять цифр умножаются на веса 2, 4, 10, 3, 5, 9, 4, 6, 8, сумма берётся по модулю 11, затем по модулю 10. Результат должен совпасть с десятой цифрой. У 12-значного ИНН физлиц и ИП контрольный разряд считается дважды, с другими весами. Несколько строк арифметики, и несуществующий ИНН не пройдёт, как бы уверенно модель его ни «прочитала».
Проверка обязана знать исключения, иначе она окажется строже стандарта. СНИЛС, выданные до 1998 года, могут не иметь корректной контрольной суммы: это историческая особенность ПФР. Счета ГРКЦ Банка России и казначейские БИК УФК не должны отклоняться стандартной проверкой ключа. Без этого исключения, по словам автора, половина постановлений ФССП получила бы ложные ошибки.
Арифметика и закон
До появления этой стены настоящие суммы и подставные 999 999 проходили через логику одинаково: деньги никто не сверял. Теперь суммы сверяются с допуском. Автор отдельно упоминает пойманный баг: выражение x or y в Python молча съедало законный ноль 0.0.
Проверяется и хронология: дата акта не позже даты исполнительного документа, а она не позже возбуждения исполнительного производства.
Лимиты удержаний составляют отдельный юридический блок: 50 % и 70 % по статье 99 закона 229-ФЗ, 20 % по статье 138 ТК РФ, дробные доли 1/4, 1/3, 1/2 и 2/3. Есть исключение по 314-ФЗ: к возмещению вреда здоровью процентный потолок не применяется. Автор признаётся, что когда-то сам ошибся в этом месте в документации к собственному коду.
Кросс-модальный гейт
Эту стену автор называет главной. Название «кросс-модальная» объясняется тем, что сверяются два разных представления одного документа: структурированные поля от модели и текст этого же документа. Каждый критичный реквизит должен найтись в эталонном тексте. Эталоном служит текстовый слой PDF, независимый OCR или транскрипция той же модели, причём последняя явно помечается как «не независимая проверка».
Гейт учитывает особенности русского языка:
- документ склоняет фамилии, а модель отдаёт именительный падеж. «Иванов» подтверждается по фразе «Взыскать с должника Иванова Ивана Ивановича». При этом «Иван» не подтверждается внутри «Иванов», а «Иванов» не подтверждается внутри «Ивановский»: это другой человек;
- модель возвращает сумму числом
5075.0, а в документе напечатано «5 075,00». Поэтому суммы сравниваются как числа, и десятикратная ошибка (507500 против 5 075,00) не проходит.
Честные статусы
Четвёртая стена касается того, как система разговаривает с оператором. Каждый документ получает статус:
| Статус | Смысл | Действие |
|---|---|---|
zero_trust_verified | все применимые проверки прошли | выборочный контроль |
partially_verified | проверок было мало | выборочный контроль |
gate_not_executed | эталона для сверки не было | ручная проверка |
discrepancy_detected | неверный разряд, расхождение, превышен лимит | ручная проверка |
heuristic_fallback | суммы восстановил regex, а не модель | сверка сумм |
ocr_low_confidence | скан меньше 150 DPI | пересканировать |
Автор подчёркивает: zero_trust_verified означает «применимые проверки прошли», а не «правильно на 100 %». Формулировки «100 % подтверждено» в документации проекта запрещены, и это контролируют тесты. Причина в том, что автор однажды видел, как Excel показывает «отлично» рядом с расхождением.
CLI дублирует статус кодами возврата: 0 означает, что всё чисто, 3 — расхождение или невыполненный гейт (автоимпорт недопустим), 4 — эвристику, 1 — сбой. Провальная экстракция возвращает 1, а не «успех с пустыми данными». Это тоже исправление реального бага.
Что показали замеры и чего автор не обещает
Сначала автор оговаривает, что утверждают его цифры. Это внутренние измерения на детерминированном стенде с фиксированным seed, они воспроизводятся одной командой. Сравнением с другими системами они не являются: публичного бенчмарка по извлечению российских юридических документов автор не нашёл. Эталонов 23, причём у 6 из 9 типов всего по одному документу. Шум вносился только в эталонный текст, а извлечение оставалось чистым. Так моделируется сценарий, в котором ошибся OCR, а модель была права.
Главная операционная метрика — ложные срабатывания гейта. Она показывает, сколько честных документов система зря отправит человеку. Замер шёл по 460 проверок на уровень шума, с 20 повторами:
| Доля ошибок распознавания в эталоне | Ложные срабатывания |
|---|---|
| 0 % (чистый скан) | 0,0 % |
| 1 % | 21,5 % |
| 2 % | 38,5 % |
| 5 % | 73,5 % |
| 10 % | 89,1 % |
Автор так объясняет таблицу: на чистом тексте гейт не шумит вообще, а дальше кривая показывает цену недоверия. Чем хуже скан, тем больше документов уходит на ручную проверку.
Отдельно автор приводит метрики качества кода. В проекте 667 автотестов против 75 в версии 0.8.0, покрытие 73,5 % при пороге 70 %, ruff и mypy проходят без замечаний. Секреты маскируются в логах, в том числе в случае, когда секрет утекает через аргументы записи лога, а не через сообщение.
Каждый старый провал закрыт регресс-тестом. Автор перечисляет три примера из чейнжлога:
- ошибка в 10 раз давала 98,32 % и метку «excellent», потому что суммы сравнивались как строки;
- провальный документ уходил в реестры 1С со статусом
COMPLETED; - прогон оставлял 56 файлов на 13 документов, и три четверти из них были байтовыми дубликатами.
Практический итог автор описывает так. Оператор проверяет исключения, а не весь поток, и автоимпорт в 1С идёт только для чистых документов. Ошибки ловятся на входе, до банка. Интеграция возможна без графического интерфейса: тихий CLI совместим со сценарием 1С через WScript.Shell. Чего автор прямо не обещает: замены бухгалтера, 100 % точности и работы на любом скане.
Аналоги автор разбирает по категориям. OCR-движки (Tesseract, PaddleOCR, EasyOCR/RapidOCR, docTR, Surya) дают только текст. Инструменты разбора документов (unstructured, marker, Apache Tika) не знают юридической модели данных. Шаблонный invoice2data ломается на меняющихся макетах. Открытые VLM вроде Qwen2.5-VL и InternVL сами по себе не проверяют результат. SaaS-сервисы Azure Document Intelligence и Google Document AI требуют отправки данных наружу и не знают 229-ФЗ, ИНН и 1С. Прямого открытого аналога с верификацией для российских документов автор не нашёл. Вклад проекта он видит в слое проверки поверх VLM. Код открыт на GitHub под названием ScanReader, лицензия MIT.
Коротко
- VLM на юридических сканах ошибается правдоподобно: формат верный, значение выдуманное. Поэтому ответ модели в проекте считается предложением, а не результатом.
- Надёжность дают четыре детерминированные проверки: контрольные разряды реквизитов, арифметика сумм и лимиты по закону, сверка полей с текстом документа, честные статусы с кодами возврата.
- Проверки обязаны знать исключения: старые СНИЛС, счета ГРКЦ, БИК УФК, склонение фамилий, запись сумм в разных форматах. Без этого они шумят.
- На чистом скане гейт не дал ни одного ложного срабатывания. При 10 % ошибок OCR ложными оказались 89,1 % срабатываний. Цифры внутренние и получены на 23 эталонах.
- Человек разбирает только помеченные документы, а результат «прошёл проверки» не означает «верно на 100 %».
Что из этого следует
Подход переносится за пределы юридических документов. Он работает везде, где у извлекаемых полей есть внешняя проверка: контрольная сумма, арифметическая связь, правило из закона, присутствие значения в исходном тексте. Читателю, который строит распознавание документов нейросетью, полезно начать с инвентаризации полей. Какие из них можно проверить алгоритмом, а какие держатся только на доверии к модели? Вторые и нужно отправлять человеку.
Таблица ложных срабатываний показывает экономику такой системы. Качество скана напрямую определяет, сколько ручной работы останется. Если входной поток грязный, вложения в сканирование и OCR могут дать больше, чем смена модели.
Цифры кейса стоит воспринимать как ориентир, а не как гарантию. Эталонов немного, по большинству типов документов всего по одному, а сравнения с другими системами нет, о чём автор говорит сам. Прежде чем доверять автоимпорту, его нужно прогнать на собственных документах. Автор и сам предлагает сделать именно это.
