ADR-009. Обращение к модели распознавания
Как формируется запрос к модели, как он передаётся и как ответ превращается в распознанные поля. Первая редакция намеренно минимальна: цель — получать отклики и на них докручивать качество.
1. Контекст
По ADR-007 «Ввод без плана» распознавание стало обязательным звеном: пользователь больше не сообщает Сервису ничего, кроме кадров, и всё остальное Сервис добывает сам. Значит нужен способ обратиться к модели за значениями полей и вернуть результат в проверяемом виде — со значением, уверенностью и указанием, откуда прочитано (ADR-005 «Привязка значения к странице формы и области»).
Что уже есть: структура запроса, инструкция модели, каталог полей страницы 1 формы «Издания из фондов БАН» и схема ответа — запрос к модели распознавания.
Чего мы пока не знаем. Ни одна модель на наших картах не проверена. Неизвестно, читается ли рукописный текст приемлемо, различает ли модель заполненную клетку матрицы от пустой, попадает ли она в область на изображении. Пока это неизвестно, любое сложное решение об интеграции будет обоснованием придуманных, а не наблюдаемых трудностей.
Отсюда предмет решения: минимальный работающий путь до отклика модели.
1.1. За рамками
Опознание вида ИК, выбор формы и распределение кадров по страницам решаются отдельно. Здесь считаем, что к моменту вызова форма известна, кадр отнесён к странице формы, и каталог полей этой страницы собран.
2. Решение
Один синхронный вызов на страницу. Сервис отправляет модели изображение страницы, инструкцию, каталог полей этой страницы и схему ответа, дожидается ответа и разбирает его. Страницы пакета обрабатываются последовательно, независимо друг от друга: каждая — свой вызов со своим каталогом.
Протокол — OpenAI-совместимый POST /v1/chat/completions: изображение передаётся
как image_url с data:-URI, схема ответа — параметром запроса (response_format
с JSON Schema), а не текстом в промте. Такой контракт поддерживают и облачные провайдеры,
и локальные серверы (llama.cpp, vLLM), поэтому смена модели не меняет код.
Модель — локальная, в контуре библиотеки. Работаем с развёрнутой на своём оборудовании Qwen; изображения карт наружу не передаются. Адрес, имя модели, температура и таймаут задаются настройкой, а не запросом: пользователь модель не выбирает, в домене её нет. Тот же контракт принимают облачные провайдеры — это оставляет путь к сравнению качества, но прод-вариантом облако не рассматривается.
Область на изображении берём такой, какую вернёт модель. Своей локализации значения по изображению Сервис не делает. Если окажется, что модель в область не попадает, это будет отдельное решение — с фактами на руках.
Ответ сохраняется целиком. Вместе с распознанными полями пакет хранит сырой ответ модели, имя модели и версию промта. Это цена одного текстового поля и единственный способ потом понять, почему разбор вышел таким.
Сбой не разбирается автоматически. Таймаут, недоступность модели, ответ не по схеме после одного повтора — пакет переводится в состояние «ошибка» и остаётся в очереди. Повторную обработку запускает пользователь командой, как и при любой другой правке пакета.
2.1. Чего в первой редакции нет
Сознательно не делаем — до появления фактов, требующих обратного:
| Не делаем | Почему отложено |
|---|---|
| Кэш разбора по содержимому | повторов пока единицы; кэш прячет как раз то, что мы хотим наблюдать |
| Разделение OCR и извлечения | сначала надо увидеть, справляется ли одна модель |
| Переспрос более сильной моделью при низкой уверенности | нечем задать порог: распределение уверенности неизвестно |
| Своя дообученная модель | дообучение требует эталонного набора, которого нет |
| Пакетная отправка нескольких страниц одним вызовом | экономия неощутима при последовательной обработке пакетов |
3. Обоснование
Решение выбрано по критерию «сколько времени до первого наблюдаемого отклика», а не «насколько хорошо оно масштабируется». Пока не увидим, как модель читает наши карты, все прочие критерии опираются на догадки.
Отсюда синхронный вызов и последовательные страницы: это самый прямой путь, и он не мешает позже сделать иначе — обработка пакета уже асинхронна относительно пользователя, он получает результат из очереди, а не ждёт у экрана.
Принуждение схемой — единственное усложнение, на которое идём сразу. Оно ничего не стоит (параметр запроса) и снимает целый класс работы: разбор свободного текста, ретраи на «модель ответила прозой», рассинхрон каталога и ответа.
Сырой ответ храним по той же причине, по которой вообще затеян минимальный вариант: качество будем докручивать по наблюдениям, а наблюдать не за чем, если ответ выброшен сразу после разбора.
4. Следствия
Время обработки пакета складывается из страниц. Две страницы — два вызова подряд, десятки секунд. Для очереди это приемлемо, для интерактивной подсказки при вводе — нет; если такая подсказка понадобится, потребуется другое решение.
Пропускная способность упирается в своё оборудование. Раз модель локальная, скорость обработки задаётся видеопамятью и числом одновременных запросов, а не тарифом провайдера. Соседство с другими нагрузками на той же карте — вопрос размещения, а не этого решения.
Появляется, что версионировать. Промт и модель становятся частью результата: одна и та же карта, разобранная разными версиями, даёт разные значения. Пока это только хранится; правила «пересчитать всё при смене промта» нет и не нужно.
Правки пользователя и повторная обработка ещё не примирены. Решение фиксирует, что повтор запускается командой, но не то, как исправленное значение переживает повтор. Это отдельный вопрос, он поднят в эпике обработки пакета.
5. Рассмотренные альтернативы
Свой сервис с дообученной моделью. В соседнем проекте библиотеки распознавание сделано так: дообученная модель под конкретную задачу, поднятая своим сервисом с собственным API. Отвергнуто как первый шаг: дообучение требует размеченного эталонного набора, которого у нас нет и который можно собрать только после того, как заработает базовый разбор. Как второй шаг остаётся открытым — и тем более достижимым, что контракт вызова от модели не зависит.
Разделить OCR и извлечение полей. Локальная модель читает текст со словными координатами, вторая модель извлекает из текста значения; область поля тогда вычисляется точно, сопоставлением со словами, а не доверием к модели. Отвергнуто для первой редакции: два звена вместо одного, и оба надо отлаживать одновременно. Это первый кандидат на усложнение, если окажется, что области смещены.
Отправлять все страницы карты одним вызовом. Модель видит карту целиком и может связать данные разных страниц. Отвергнуто: усложняет каталог и схему ответа (поля надо приписывать страницам), а выигрыш неочевиден — страницы наших форм самостоятельны.
Структурные связи
Использует
- Прикладной сервис Запрос на извлечение полей