Извлечение структурированных данных из документов
Разбор счетов, накладных и анкет вручную не масштабируется. Как отдать эту работу vision-модели и получить на выходе валидный JSON.
4 мин · · Обновлено · разработка · документы
Почему это задача для vision, а не для OCR-скрипта
Обычный OCR отдаёт текст без структуры — дальше всё равно нужно регулярками или руками раскладывать его по полям, и это ломается на первом же документе с другой вёрсткой. Модели Claude и GPT в каталоге принимают изображения (vision; у GLM его нет), так что можно сразу отдать скан или фото документа и попросить структурированный ответ, вместо промежуточного шага с распознаванием текста.
Формат запроса с изображением
Через Anthropic-совместимый эндпоинт POST /v1/messages изображение передаётся как отдельный блок содержимого в base64:
import base64, requests
with open("invoice.jpg", "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
resp = requests.post(
"https://api.zukko.pro/v1/messages",
headers={
"Authorization": "Bearer fetch_ваш_ключ",
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
},
json={
"model": "claude-opus-5-5",Для сложных сканов — плохое качество, рукописные пометки, нестандартная вёрстка — стоит попробовать claude-opus-5-5 с extended thinking и проверить на своих документах, реже ли он додумывает несуществующие значения там, где текст нечитаем.
Жёсткая схема через tools вместо надежды на формат
Просить модель "верни JSON" работает, но она может обернуть его в пояснение или markdown-блок. Надёжнее задать схему через tool-описание и заставить модель ответить вызовом инструмента — тогда парсинг ответа детерминирован:
{
"model": "claude-opus-5-5",
"max_tokens": 1024,
"tools": [{
"name": "extract_invoice",
"description": "Извлечённые поля счёта",
"input_schema": {
"type": "object",
"properties": {
"number": {"type": "string"},
"date": {"type": "string"},
"total": {"type": "number"},
"vendor_tax_id": {"type": "string"}
},
"required": ["number", "date", "total"]
}
}],
"messages": [ ... ]
}Тот же принцип работает и через OpenAI-совместимый POST /v1/chat/completions с tools в теле — если пайплайн уже написан под OpenAI SDK, переписывать его под Anthropic-формат не обязательно.
Многостраничные PDF
Эндпоинт /v1/messages принимает изображения, а не PDF целиком, поэтому многостраничный документ нужно сначала разложить на страницы-изображения (например, через pdftoppm или библиотеку вроде pdf2image) и передать несколько блоков image в одном сообщении — модель видит все страницы разом и может сопоставить данные между ними, например итоговую сумму на последней странице со списком позиций на первой.
from pdf2image import convert_from_path
pages = convert_from_path("contract.pdf", dpi=200)
content = []
for page in pages:
buf = io.BytesIO()
page.save(buf, format="JPEG")
content.append({
"type": "image",
"source": {"type": "base64", "media_type": "image/jpeg", "data": base64.b64encode(buf.getvalue()).decode()},
})
content.append({"type": "text", "text": "Извлеки стороны договора, сумму и дату подписания. Верни JSON."})Поле уверенности вместо тихой ошибки
Для полей, которые модель не смогла прочитать уверенно — смазанная печать, обрезанный край скана — полезно явно попросить возвращать null вместо угадывания значения, и отдельное булево поле needs_review. Это превращает молчаливую ошибку в явный сигнал для очереди ручной проверки, вместо того чтобы правдоподобно выглядящее, но неверное значение тихо ушло в базу.
Пакетная обработка большого архива
Перед тем как отправлять в обработку архив на несколько тысяч документов, стоит прикинуть стоимость: изображение в запросе занимает заметную часть контекста, и POST /v1/messages/count_tokens посчитает точный размер бесплатно для одного образца — дальше можно умножить на количество документов и свериться с ценами моделей из GET /v1/models/info.
Ошибки, специфичные для пакетной обработки
На большом архиве почти гарантированно всплывёт хотя бы одна из типовых ошибок: 403 model_not_allowed_for_key, если на ключе в кабинете стоит ограничение по списку моделей и в скрипте случайно указана не та модель; 429 с key_daily_limit_reached, если дневной лимит на ключе меньше, чем нужно для всего архива за один день; 503 model_disabled, если модель временно отключена в каталоге — в этом случае имеет смысл предусмотреть fallback на другую vision-модель из актуального списка GET https://api.zukko.pro/v1/models (Claude или GPT — у GLM vision нет), а не останавливать весь пайплайн.
Формат даты в разных документах
Даты в счетах и накладных встречаются в разных форматах — ДД.ММ.ГГГГ, ММ/ДД/ГГГГ, ГГГГ-ММ-ДД — и модель без явных указаний иногда додумывает формат по умолчанию для своей внутренней локали. Полезно прямо в промпте задать, в каком формате возвращать дату, например всегда ISO 8601, чтобы не тратить время на постобработку разнородных строк из разных документов одной партии.
Именование файлов и трассируемость
При обработке архива стоит сохранять исходное имя файла и номер страницы прямо в самом JSON-ответе, а не только в имени файла на диске. Тогда при разборе несовпадений видно, откуда взялась конкретная запись, даже если файлы позже переименуют, переместят в другую папку или объединят в один общий CSV с результатами всей партии.
Проверка на выходе
Хороший тест перед тем как пускать пайплайн на весь архив — прогнать 20–30 документов, для которых значения уже известны, и сверить построчно. Модели иногда путают похожие поля (дату документа с датой оплаты, ИНН поставщика с ИНН покупателя), и на выборке это видно сразу, а не после того, как ошибка попадёт в базу на тысячах записей.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Промпт-инъекция: что это и как защитить бота и агентаОбъясняем, как работает промпт-инъекция через пользовательский ввод и чужие документы, и какие меры снижают ущерб: изоляция данных, права инструментов, лимиты ключа.
- Свой прокси к нейросети на Node.js: не показывать API-ключ во фронтендеНебольшой сервер на Express между вашим фронтендом и API нейросети: ключ остаётся на сервере, а модели, длина запроса и частота ограничены.
- DeepSeek API на Python: запросы, стриминг, JSON и обработка ошибокГотовый код для DeepSeek V4.1 Flash и V4 Pro на OpenAI SDK: запрос, стриминг, разбор JSON и аккуратная обработка ошибок.
- Промпт-инжиниринг: примеры и 12 готовых шаблонов для работыДвенадцать шаблонов промптов для рабочих задач: что в них написано, почему именно так и сколько стоит один вызов по API.