Открывать банковские выписки — удовольствие сомнительное. Они приходят в виде отсканированных PDF, CSV-экспортов или XML-файлов, обросших архаичными аббревиатурами вроде OFX. Для бухгалтеров, счетоводов и разработчиков финтех-решений превращение этих документов в чистые, структурированные данные — постоянная головная боль. Когда на сцене появились большие языковые модели, они показались спасительным кругом. Просто скормите машине PDF и попросите JSON. Что может пойти не так?
Я узнал, что именно может пойти не так, во время разработки StatementDecoder — инструмента, предназначенного для преобразования банковских выписок в пригодные для использования данные. Как и многие разработчики, я полагал, что самой сложной частью будет обучение системы чтению различных макетов документов. Я ошибался. Чтение документов было почти тривиальным. Настоящим кошмаром стало распознавание моментов, когда машина незаметно выдумывала число или меняла местами две цифры в сумме транзакции.
Демо-версия, которая сработала слишком хорошо
Моя первая попытка была соблазнительно простой. Я направлял банковские выписки напрямую в LLM и запрашивал структурированный JSON в ответ. Результаты казались магией. Модель с легкостью справлялась с различными макетами. Она читала отсканированные PDF, на которых спотыкались стандартные парсеры. Казалось, она понимает таблицы, заголовки и многостраничные выписки без явных инструкций. Несколько славных часов я думал, что проблема решена.
Затем я протестировал систему на реальных данных клиентов, и магия испарилась. Британские банки используют свои собственные дизайны выписок, и различия здесь не просто косметические. Выписки Wise имеют свои особенности форматирования. CSV-экспорты Revolut кажутся простыми, пока не заметишь, как они обрабатывают мультивалютные транзакции и поля метаданных. Старые файлы OFX — формат, который действительно выглядит так, будто он застрял в 1990-х, — подбрасывают архаичные структуры тегов и проблемы с кодировкой любому парсеру, ожидающему современную разметку.
Модель по-прежнему извлекала данные гораздо лучше, чем любая готовая шаблонная система. Но «гораздо лучше» недостаточно, когда речь идет о деньгах.
Когда точность 99% — это провал
Вот фундаментальная проблема использования ИИ для извлечения финансовых данных. Если модель обрабатывает двести строк транзакций и делает сто девяносто девять правильными, результат выглядит безупречно. JSON сформирован корректно. Ключи и значения на месте. Беглый просмотр не выявит ничего подозрительного. Однако, если эта единственная ошибка поменяет местами две цифры в сумме, превратит депозит в списание или сдвинет десятичную запятую, ваша бухгалтерия будет испорчена. Вы не заметите этого, просто просматривая стену структурированных данных.
Человек, проверяющий «сырой» JSON, редко замечает перепутанную цифру в сумме транзакции. Форматирование идеально, что, как ни парадоксально, делает ошибку еще опаснее. Нельзя выпускать финансовый инструмент, который прав в большинстве случаев. Он должен быть прав всегда, либо должен громко заявлять о своей неуверенности.
Моя первая реакция была предсказуемой. Я составлял более качественные промпты. Переходил на более мощные модели. Экспериментировал с методом «цепочки рассуждений» (chain-of-thought), чтобы модель показывала ход своих мыслей. Ничего из этого не решило основную проблему. Я просил одну и ту же вероятностную систему сгенерировать ответ, а затем просил ту же самую систему подтвердить, что этот ответ верен. Это не верификация. Это театр самосогласованности.
Пусть решит математика
У банковских выписок есть особенность, которой нет у большинства документов: встроенные арифметические ограничения. Начальный баланс плюс сумма всех транзакций должны равняться конечному балансу. Остатки на счете (running balances), если они указаны, должны сходиться строка за строкой. Это не стилистические предпочтения. Это жесткие правила.
Я перестроил архитектуру, основываясь на этом инсайте. Теперь каждое извлечение, независимо от источника, проходит через слой валидации, прежде чем его увидит пользователь. Неважно, пришли ли данные от LLM, интерпретирующей нечеткий PDF, от OCR-движка, считывающего отсканированную страницу, или в результате прямого парсинга CSV. Валидатор относится ко всем источникам с одинаковым подозрением.
Проверка предельно проста. Прибавьте каждую транзакцию к начальному балансу. Сравните результат с указанным конечным балансом. Если числа не совпадают, значит, что-то не так. Пометьте выписку для проверки. Отклоните извлечение. Не позволяйте этим данным попасть к пользователю.
Это единственное изменение полностью изменило характер продукта. Языковой модели больше не нужно было быть идеальной. Ей достаточно было быть достаточно хорошей, чтобы результат мог пройти математическую проверку. Акцент сместился с достижения невозможной точности в неограниченной области на создание тесной петли обратной связи между генерацией и верификацией.
Валидатор также выявил закономерности в ошибках. Определенные типы документов постоянно не проходили математическую проверку, что точно указало мне, куда стоит направить усилия. Вместо того чтобы слепо улучшать промпт-инжиниринг повсеместно, я увидел, что систематические ошибки вызывались конкретными макетами банковских выписок.
Код — там, где он нужен, ИИ — там, где он эффективен
Пожалуй, самым отрезвляющим уроком стало осознание того, насколько большая часть конвейера вообще не нуждается в ИИ. Когда я столкнулся с неразберихой в австралийских OFX-файлах, моим первым инстинктом было «закидать проблему токенами». Я ненадолго задумался о том, чтобы скормить сломанный XML модели и попросить её исправить структуру перед парсингом. Вместо этого я написал двадцать строк детерминированного кода. Он мгновенно исправил странности кодировки и некорректные теги — с нулевой стоимостью за файл и идеальной воспроизводимостью.
Этот опыт помог мне четко сформулировать, как должны быть организованы конвейеры извлечения данных. Существует три разные задачи, и их не следует смешивать.
- Модель понимает неструктурированные документы. Отсканированные PDF с искаженными таблицами, смешанными шрифтами и рукописным
