Модель в Jupyter выглядела убедительно: F1 выше 0.9, демо на трёх PDF прошло без ошибок. Через две недели в production точность упала, пользователи жаловались на «галлюцинации» полей, а команда не могла ответить, какая версия модели сейчас на сервере. Это классический разрыв между ML-прототипом и ML-продуктом.
В OtherCode мы строим Python-контуры для OCR и NLP в URAP AI, для разбора документов в Content Factory и для вспомогательных классификаторов в промышленных и SaaS-продуктах. Ниже — как мы доводим модели до production: версионирование, eval-наборы, CI, мониторинг дрифта и жёсткое разделение «эксперимент» и «релиз».
Прототип ≠ продукт: где ломается цепочка
| Аспект | Прототип / notebook | Production-продукт | |--------|---------------------|--------------------| | Данные | 50–200 размеченных примеров | Версионированный датасет + hold-out | | Метрика | «В целом хорошо» | SLA: precision/recall по классам, latency p95 | | Деплой | Ручной скрипт на ноутбуке | Docker-образ, GitLab CI, откат | | Мониторинг | Нет | Drift, error rate, human feedback | | Ответственность | Исследователь | Команда продукта + on-call |
Прототип отвечает на вопрос «можно ли». Продукт отвечает на вопросы «стабильно ли», «сколько стоит», «как откатить» и «кому звонить ночью».
В URAP AI распознавание и извлечение полей из документов — не разовое демо, а ежедневный поток: разные сканеры, качество печати, шаблоны актов и счетов. Без MLOps-контура любая «улучшенная модель» — лотерея.
Архитектура ML-контура в OtherCode
Типовой путь от сырых документов до ответа API:
[Загрузка документа]
↓
[Предобработка / OCR] ← Python-сервис (URAP)
↓
[Извлечение / NLP / классификация]
↓
[Валидация + rule engine]
↓
[API / очередь Redis] → [PostgreSQL]
↓
[Feedback / разметка] → [Eval set] → [Retrain]
Python здесь — основной язык пайплайна: предобработка изображений, OCR, постобработка текста, лёгкие классификаторы и оркестрация вызовов LLM. Тяжёлые модели упаковываем в отдельные контейнеры, лёгкие — в worker рядом с API.
Для асинхронных задач (длинные PDF, пакетная обработка) используем Redis-очереди — тот же подход, что и в остальных backend-сервисах студии. Подробнее про очереди и кэш мы писали в статье про Redis.
Как это стыкуется с остальными продуктами
- URAP AI — ядро OCR/NLP: модели и пайплайны версионируются, eval прогоняется в CI перед релизом.
- Content Factory (Telegram) — генерация и разбор контента: правила + модели, без «магического» промпта в проде без метрик.
- Synapse Stream / Game API — геоданные и realtime на PostGIS; ML здесь точечный (классификация событий), но тот же принцип: модель = артефакт с версией.
- Typhoon (STM32/Modbus) и Galvatec — телеметрия и промышленные контуры; ML не «вместо» протокола, а поверх нормализованных сигналов.
- Diom (Flutter) и клиенты на Next.js — UI показывает confidence и даёт human-in-the-loop, а не слепо доверяет модели.
Версионирование моделей и данных
Без версий нельзя воспроизвести баг и нельзя честно сравнить две модели.
Что мы версионируем
| Артефакт | Как храним | Зачем |
|----------|------------|-------|
| Датасет (train/val/test) | Object storage + manifest (хеш, дата, источник) | Воспроизводимость |
| Код пайплайна | Git (монорепо или отдельный ML-репо) | Аудит изменений |
| Веса / ONNX / pickled pipeline | Object storage, тег model@version | Деплой и откат |
| Конфиг препроцессинга | Рядом с моделью в образе | Один «снимок» поведения |
| Eval-отчёт | CI artifact + запись в БД | История качества |
Правило студии: один релиз = один неизменяемый артефакт (Docker-образ с зафиксированными весами и конфигом). Не «подтянули свежие веса на VPS ночью».
Семантические версии для моделей
Используем схему, понятную продукту:
- MAJOR — ломающее изменение выхода (другая схема JSON, другие классы).
- MINOR — улучшение качества при совместимом API.
- PATCH — hotfix препроцессинга, баг в постобработке.
В API URAP клиент видит model_version в ответе — это упрощает разбор жалоб: «в v2.3.1 поле ИНН ломалось на сканах с поворотом».
Eval-наборы: метрика важнее ощущения
Демо на «красивых» документах обманывает. Eval-набор — это контракт качества.
Как собираем eval в URAP
- Стратификация: типы документов (счёт, акт, паспорт, произвольный скан), качество (хороший/шум/повёрт), источники (мобильное фото / МФУ).
- Hold-out: тестовый набор не участвует в обучении и в подборе порогов.
- Золотые поля: не только «текст в целом», а точность по ключевым полям (ИНН, сумма, дата, номер договора).
- Human baseline: сколько ошибок делает оператор — чтобы не гнаться за нереалистичным 100%.
| Метрика | Для чего | Типичный порог релиза | |---------|----------|------------------------| | Field-level accuracy | Ключевые поля | ≥ целевого SLA по типу дока | | Exact match JSON | Строгие интеграции | По договорённости с заказчиком | | Latency p95 | UX и нагрузка | В пределах бюджета инстанса | | Share of «needs review» | Нагрузка на людей | Не растёт после релиза |
Релиз блокируется, если новый кандидат хуже предыдущего на критичных полях или сильно медленнее при той же инфраструктуре.
Практика OCR-пайплайнов (предобработка, движки, таблицы) разобрана отдельно: OCR и распознавание документов на Python.
CI для ML: не только «собрать образ»
GitLab CI у нас — общие docker runners. Для ML-пайплайна типичные стадии:
- lint / unit — чистый Python: парсеры, валидаторы схемы, препроцессинг без GPU.
- smoke inference — прогон 5–20 эталонных документов на CPU-образе (быстро ловит сломанный импорт и несовместимость зависимостей).
- eval job — полный eval на runner с нужными ресурсами; артефакт — JSON/HTML отчёт.
- build & push — образ в registry только если eval прошёл пороги.
- deploy — staging → production (PM2/compose на VPS или сервис в Yandex Cloud), с возможностью отката на предыдущий тег.
Параллельные runners ускоряют «обычный» backend и ML-ветки, но GPU-eval планируем отдельно: дорогой ресурс не должен блокировать каждый merge мелкого фикса в UI Next.js.
Важно: notebook не является артефактом релиза. Код эксперимента переносится в пакет (src/urap_ocr/...), покрывается тестами и только потом попадает в образ.
Мониторинг дрифта и деградации
Модель стареет не потому что «веса портятся», а потому что меняется мир: новые бланки, другой сканер в филиале, смена шрифта в ERP заказчика.
Что мониторим в production
| Сигнал | Как считаем | Реакция | |--------|-------------|---------| | Input drift | Распределение размеров, DPI, языка, доли пустых OCR | Алерт + выборка на разметку | | Prediction drift | Доли классов / средний confidence | Сверка с eval | | Quality proxy | Доля ручных правок, отказов API, тикетов | Приоритет retrain | | Latency / errors | p95, OOM, timeout очереди | Масштабирование / откат |
В URAP feedback от оператора (исправленное поле) — золотой источник для следующего eval и дообучения. Без замкнутого цикла «ошибка → датасет → релиз» MLOps превращается в театр.
Rule engine рядом с моделью
Модель предлагает, правила страхуют. Для документов это критично:
- форматные проверки (ИНН, даты, суммы);
- согласованность полей («сумма прописью» vs число);
- маршрутизация: низкий confidence → очередь человека;
- запрет автозаписи в ERP без подтверждения на критичных полях.
Тот же принцип гибрида «ИИ + правила» мы используем, когда встраиваем LLM в продукты: модель не заменяет доменную логику. Общий взгляд на AI в продуктах — в материале Разработка с AI.
Инфраструктура: где крутятся модели
- Разработка: Docker Compose локально, те же образы, что уедут в CI.
- CI: GitLab docker runners; тяжёлый eval — по расписанию или по тегу
ml-release. - Production: для большинства сервисов othercode.ru и клиентских VPS RU — Docker + PM2 для Node-слоя, Python-workers в compose; при необходимости — Yandex Cloud (в т.ч. GPU под обучение/batch, не обязательно под каждый online-запрос).
- Стоимость: online-инференс стараемся держать на CPU/оптимизированных форматах (ONNX), GPU — для обучения и пакетных джобов.
Kubernetes включаем, когда реально нужны автоскейл и много сервисов с жёстким изоляционным контуром. Для многих продуктов студии compose + PM2 на выделенном VPS проще и дешевле — без потери дисциплины артефактов.
От эксперимента к API: рабочий процесс команды
Внутри студии ML-задача почти никогда не живёт «в голове одного data scientist». Типичный цикл для URAP или похожего контура:
- Продукт формулирует SLA — какие поля критичны, какая доля ручной проверки допустима, какой p95 ответа API.
- Собираем или расширяем датасет — с разметкой и источником (клиент, синтетика, публичные бланки только как черновик).
- Базовый пайплайн без «тяжёлой» модели — правила + OCR + эвристики; это нижняя планка качества и стоимости.
- Модель как улучшение — сравниваем с baseline на том же eval, а не с ощущением «стало умнее».
- Упаковка — сервис с ясным контрактом JSON, таймаутами, идемпотентностью для очередей.
- Канареечный выкат — часть трафика на новую версию, сравнение error rate и правок операторов.
- Ретро после двух недель — что деградировало, какие типы документов добавить в train/eval.
Для Content Factory цикл короче: шаблоны и стоп-слова часто важнее fine-tune. Для Synapse/Game API классификаторы событий появляются точечно — только если правило не закрывает кейс. В Typhoon и Galvatec сначала нормализуем регистры и единицы измерения; «нейросеть на сыром Modbus» без словаря сигналов мы не делаем.
Роли и ответственность
| Роль | Зона | |------|------| | Product / заказчик | SLA, цена ошибки, приоритет типов документов | | Backend | API, очереди Redis, деплой, наблюдаемость | | ML / Python | Пайплайн, eval, версия модели | | Frontend (Next.js / Flutter Diom) | UX confidence, очередь review, понятные ошибки | | Ops | GPU/CPU бюджет, runners, бэкапы артефактов |
Без владельца метрики модель «сиротеет» после первого релиза — это частая причина тихого дрифта.
Экономика инференса и очередей
Production ML упирается в деньги так же часто, как в F1.
- Синхронный путь — только для коротких документов и ясного UX-ожидания.
- Длинные PDF и пакеты филиалов — в Redis-очередь, статус задачи в PostgreSQL, webhook или polling для клиента.
- Кэш результатов по хешу файла экономит повторный OCR одного и того же скана.
- Батчинг на GPU имеет смысл, когда очередь стабильно непустая; иначе платим за простой карты.
На VPS RU и в Yandex Cloud мы явно разделяем бюджет online API и budget batch/retrain. Иначе «ночной эксперимент» внезапно становится строкой в счёте клиента.
Документация, которую реально читают
Минимум рядом с сервисом:
- как воспроизвести eval локально (Compose + фикстуры);
- таблица breaking changes API и
model_version; - runbook: «точность упала» → какие дашборды, как откатить тег, кого звать;
- описание полей feedback из UI оператора.
Это скучно по сравнению с новым fine-tune, но именно это отличает продукт студии от прототипа подрядчика.
Чеклист: готовность модели к production
- [ ] Есть версионированный train/val/test и описание источников данных
- [ ] Eval-метрики согласованы с бизнесом (не только «accuracy»)
- [ ] Модель упакована в immutable Docker-образ с конфигом
- [ ] CI прогоняет smoke + eval до деплоя
- [ ] В ответе API есть
model_version - [ ] Есть мониторинг latency, ошибок и proxy качества
- [ ] Есть план отката на предыдущий тег
- [ ] Human-in-the-loop на низком confidence
- [ ] Документирован retrain-процесс (кто, как часто, на каких данных)
Типичные ошибки, которых мы избегаем
- Релиз «с ноутбука» — невоспроизводимо и небезопасно.
- Один общий test set на все эксперименты — незаметный data leakage.
- Оптимизация только average accuracy — просадка на редком, но дорогом классе документов.
- Игнор latency — модель точнее на 2%, но p95 вырос втрое.
- Нет владельца метрики — «ML-инженер ушёл, F1 никто не смотрит».
Итог
Python в OtherCode — не про красивые графики в Colab, а про управляемый путь: данные → eval → образ → CI → production → feedback. URAP AI живёт в этом контуре; соседние продукты (Synapse, промышленные интеграции, Flutter/Next.js-клиенты, Telegram Content Factory) забирают тот же инженерный стандарт: версия, метрика, откат.
Если нужна команда, которая доведёт OCR/NLP или классификаторы до стабильного API, а не до слайда — оставьте заявку.

