
Всем привет! Меня зовут Александра Царева, я старший специалист машинного обучения в компании «Инфосистемы Джет».
Сначала я хотела рассказать историю о впечатляющей ИИ-архитектуре или тонкостях дообучения больших языковых моделей. Но, пока в нашей дата-сайентистской башне из слоновой кости достраивается очередной этаж, коллеги из бухгалтерии пришли с более приземленной задачей – научить искусственный интеллект разбирать счета.
В проект мы вписались сразу. Во-первых, работать с прикладными сценариями всегда интересно. Во-вторых, бизнес обеспечивает нас крутыми железками для запуска LLM-ок, и помочь бизнесу избавиться от части рутины – достойный ответный жест.
Хотя, признаюсь, на этапе «посмотрите, что искусственный интеллект может сделать» отнеслась к идее скептически. Мне казалось, что бухгалтерские документы давно формируют одни и те же программы, примерно в одних и тех же форматах. Даже если между компаниями не настроен прямой документооборот, проблем передать файл от контрагента заказчику быть не должно, правда?
А они есть.
В статье расскажу, почему цифровая бухгалтерия часто не такая уж цифровая, и как мы из on-prem LLM и OCR собрали решение для обработки платежных документов, которое передает структурированные данные в 1С. Что из этого получилось – под катом.
Цифровая бухгалтерия, которой не случилось
Когда мы получили тестовый набор счетов, иллюзии о победе цифровизации пропали сами собой.
Счета, которые получали наши бухгалтеры, были разными по виду и формату:
-
сделанные в древних версиях Excel;
-
содержащие CID-шрифты;
-
таблицы, но почему-то в Word;
-
многостраничные документы;
-
просто перечисление реквизитов без какой-либо структуры;
-
собранные в pdf с границами таблиц, расположенных вовсе не так, как нарисовано;
-
сфотографированные и присланные в JPG или PNG.
Кстати, профильный инструмент для получения данных из счетов у бухгалтеров уже был, но со всем разнообразием документов не справлялся. Он был готов к неким универсальным шаблонам, но не к реальному миру, где у контрагента есть только фотография документа, сделанная в плохом освещении.
Поэтому нашей задачей было создать сервис, который учитывает все «потребности на земле», и его легко можно было подкрутить по обратной связи.
Работать предстояло с коммерчески значимой информацией, поэтому публичные ИИ-агенты нам не подходили. Кроме того, модель нужно было интегрировать с 1С и встроить в контролируемый внутренний контур. Поэтому мы выбрали собственную on-prem LLM. Документы не покидают инфраструктуру компании, а результат обработки сразу уходит дальше по пайплайну.
Как мы превратили зоопарк документов в данные для LLM
Первой по каталогу примеров прошлась наша внутренняя модель. Она помогла быстро собрать необычные случаи, на которых стандартная обработка наверняка споткнулась бы. Затем мы подготовили предобработку для разных форматов и стали приводить содержимое документов к Markdown.
На вход LLM теперь поступал не исходный Word или PDF, а текст с сохранённой структурой. Таблицы оставались таблицами, абзацы – абзацами, а разрозненные фрагменты реквизитов не превращались в одну длинную строку. Такой промежуточный формат упростил промпт и сделал поведение модели более предсказуемым.
Но навести порядок только в формате оказалось недостаточно. Реквизиты в счетах могут стоять в таблице, идти строкой после названия компании или прятаться в неожиданном месте. Иногда поле уезжает из-за дефекта PDF-формы, а иногда документ просто заполняют, мягко скажем, очень творческие люди.
Мы решили не пытаться заранее нормализовать каждый возможный вариант. Вместо этого «рассказали» модели всё, что ей нужно знать о российских бухгалтерских счетах.
В дело пошла методичка про бухучет и помощь внешнего ИИ-ассистента:
-
типичные сокращения, которые могут встретиться (например, (к/с = корреспондентский счёт, р/с = расчётный счёт),
-
какие бывают реквизиты, как их могут сокращать и какие ожидаемые значения по цифрам там могут быть,
-
отдельные ролевые инструкции, касающиеся входных данных — предупреждения о том, что они «сырые», что могут встречаться таблицы, что это бухгалтерские документы и т.п.,
-
типовые места, где могут скрываться реквизиты выставившего счет или получателя, но при этом не называться так впрямую,
-
и подробное описание, какой структурированный JSON должна вернуть модель в результате.
JSON стал контрактом между моделью, нашим API и 1С. Такой контракт не делает ответ LLM автоматически правильным, зато даёт сервису понятную точку контроля. Можно проверить структуру и разобрать поля. Затем сервис решает, что делать с ошибкой, прежде чем она попадёт в учётную систему.
Почему одного ответа модели недостаточно
После LLM начиналась менее эффектная, но не менее важная часть пайплайна — жёсткие проверки. Ответ модели забирала 1С, поэтому принцип «выглядит правдоподобно» нам не подходил – ошибка в реквизитах превращается в проблему с реальным платежом.
Сначала решение собирало JSON и проверяло его формально. Все поля должны иметь ожидаемый тип, обязательные значения не могут пропадать, а реквизиты нужно хранить строками. Последнее особенно важно: модели попроще любят превращать номер в число и терять нули в начале.
|
На это мы напоролись, когда меняли языковую модель. Наши первые эксперименты были с qwen3-thinking-30b-a3b, но для продуктива мы в итоге выбрали openai/gpt-oss-120b. Она чуть хуже по качеству «размышлений», из-за чего и произошел забавный момент с неожиданными багами. Я не сразу сообразила, что бухгалтерские реквизиты не всегда число, а регионы могут начинаться с 0. Qwen считал это само собой разумеющимся и всегда возвращал реквизиты как string. После переключения модели gpt-oss решила, что это вообще-то не строка, а число. А так как моих бухгалтерских знаний в типизации не хватало, часть реквизитов завалилась. Баг мы быстро нашли и починили. Но ценой бесконечной контекстной рекламы бухгалтерских курсов. |
Дальше, если ответ не проходил проверку, сервис выбирал один из трёх сценариев. Безнадёжный результат отклонял, исправимый отправлял на повторное извлечение, а для локальной ошибки запускал вспомогательный вызов LLM. Так мы не гоняли весь документ заново там, где достаточно поправить одно поле.
Затем подключалась предметная проверка. Вместе с коллегами мы перенесли в код правила из той же бухгалтерской методички. Функции проверяли контрольные суммы, длину значений, допустимые коды и связи между разными реквизитами.
В самих проверках нет ничего сложного, но если делаете похожего ассистента, может пригодиться:
# Сначала проверяем, что это только цифры, и вычищаем возможные дефекты от OCRdef _digits(value: Optional[object]) -> Optional[str]: if value is None: return None v = re.sub(r"\D", "", str(value)) return v or None# Потом поехали формальные проверки на длину и контрольные суммыdef is_bik(value: Optional[object]) -> bool: v = _digits(value) return bool(v) and len(v) == 9 def is_ks(value: Optional[object]) -> bool: v = _digits(value) return bool(v) and len(v) == 20 def is_kpp(value: Optional[object]) -> bool: v = _digits(value) return bool(v) and len(v) == 9 def _inn_checksum_10(inn10: str) -> int: w = [2, 4, 10, 3, 5, 9, 4, 6, 8] s = sum(int(d) * w[i] for i, d in enumerate(inn10[:9])) return (s % 11) % 10def _inn_checksum_12_c11(inn12: str) -> int: w = [7, 2, 4, 10, 3, 5, 9, 4, 6, 8, 0] s = sum(int(inn12[i]) * w[i] for i in range(10)) return (s % 11) % 10def _inn_checksum_12_c12(inn12: str) -> int: w = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8] s = sum(int(inn12[i]) * w[i] for i in range(11)) return (s % 11) % 10def is_inn(value: Optional[object]) -> bool: v = _digits(value) if not v: return False if len(v) == 10: return int(v[-1]) == _inn_checksum_10(v) if len(v) == 12: c11 = _inn_checksum_12_c11(v) c12 = _inn_checksum_12_c12(v) return int(v[-2]) == c11 and int(v[-1]) == c12 return Falsedef bik_matches_ks(bik: Optional[object], ks: Optional[object]) -> bool: b = _digits(bik) k = _digits(ks) if not b or not k or len(b) != 9 or len(k) != 20: return False return k[-3:] == b[-3:]
Сделать эти правила слишком строгими тоже нельзя. В потоке встречались не только российские счета, а зарубежные реквизиты устроены иначе. Проверка должна отсеивать чушь для российских банков, но не браковать иностранный документ из-за непривычного формата. На границе между «подозрительно» и «допустимо» пришлось попотеть. Но в итоге на тестовом наборе весь пайплайн в итоге показал 100% точности.
Сканы всё ещё существуют — подключаем OCR
Когда сервис научился разбирать текстовые документы, бизнес предложил следующий логичный шаг: добавить сканы и фото в ту же форму. Отсканированный распечатанный счет без текстового слоя – все еще реальность 2026 года. И неудобств от него больше, чем от кривого PDF: даже скопировать реквизиты не получится.
Проще всего перебить «пару цифр» руками. Но человек, который целый день переносит цифры между системами, рано или поздно ошибается. А ещё устаёт и грустит – не лучший результат ИИ-трансформации.
Мы снова пошли по пути максимальной пользы при минимальных технических вложениях. Для оптического распознавания выбрали open-source PaddleOCR. Русский текст он распознавал не идеально, зато с цифрами справлялся приемлемо и не требовал слишком много ресурсов.
Результат OCR мы не отправляли напрямую в 1С. Распознанный текст проходил ту же предобработку, трансформировался в Markdown, затем попадал в LLM и дальше шёл через формальные и предметные проверки. Для таблиц добавили отдельные инструкции, чтобы модель учитывала возможные сдвиги ячеек и потерянные границы.
Так OCR стал ещё одной входной веткой, а не отдельным сервисом со своей логикой. Текстовый PDF начинал путь с парсинга, изображение — с распознавания. После преобразования в Markdown оба маршрута сходились в одном пайплайне.
Что в итоге
Дальше начинается польза для бэк-офиса. Учётная система находит по реквизитам записи в справочниках, подтягивает договоры и счета, затем формирует платёжные документы. Ручных операций стало меньше, а вместе с ними снизился риск ошибок.
Заявки на основании счетов теперь быстрее попадают в работу. Путь от входящего документа до обработки в 1С стал заметно короче, очень близко к «режиму реального времени».
На продуктиве идеальные 100% точности, конечно, не сохранились. Сервис работает уже полгода, и за это время мы встречали новые ошибки модели. Пока с каждой удавалось разобраться: где-то уточняли знания о бухгалтерии, где-то добавляли простое правило постобработки.
Из этого проекта у нас получился довольно приземлённый рецепт успеха.
Бизнес должен понимать, какую рутину хочет убрать. Коллеги из бэк-офиса лучше всех знают, где теряется время. Если они уже пробовали публичные модели, важно сразу объяснить границы: какие документы можно загружать наружу, а какие должны оставаться внутри.
Разработчики информационных систем должны участвовать с самого начала. LLM без интеграции остается демо-игрушкой. Польза появляется, когда ответ модели попадает в привычный процесс, а не создаёт сотруднику ещё одно окно для копирования данных.
Даже небольшой сервис требует контроля качества. Нужно следить за дрейфом входных документов, балансом ресурсов и точности, обновлениями модели и изменениями в open source.
Соблазн развернуть всё на слабой виртуалке и забыть особенно велик, когда задача выглядит тривиальной. В некотором смысле «каждая домохозяйка может про KDE2 под FreeBSD, используя ChatGPT». Но между эффектной демонстрацией и сервисом, которым пользуются каждый день, лежат проверки, интеграция и сопровождение.
Проект уже начал расти за пределы исходного сценария. Другие подразделения заинтересовались распознаванием счетов и попросили API для тестов. Бухгалтерия обнаружила, что ассистент умеет извлекать полезную информацию даже из договоров. Под этот формат мы его специально не настраивали, но однозначно радуемся.
Впереди у нас новые типы документов, дополнительные проверки и наблюдение за качеством на продуктиве.
Если вы автоматизировали похожую рутину в бухгалтерии или другом бэк-офисе, расскажите в комментариях.
ссылка на оригинал статьи https://habr.com/ru/articles/1067104/