Ошибки при отправке изображений в vision-модели
Запрос с картинкой ломается не так, как обычный текстовый. Разбираем, где именно теряются изображения при вызове API и как это видно в ответе сервера.
4 мин · · Обновлено · ошибки
Текстовый запрос, в котором что-то не так, обычно валится сразу и понятно — 401 или 400 с внятным сообщением. Запрос с картинкой чаще всего тоже возвращает 400, но причина в нём почти никогда не написана прямо: сервер видит «поле не распознано» или «невалидный content», а откуда взялось это поле — приходится восстанавливать самому. Ниже — где именно теряются изображения и как это отладить за пару минут вместо часа.
Anthropic и OpenAI кодируют картинку по-разному
Это первая и самая частая причина. У /v1/messages (Anthropic-схема) изображение — это блок внутри content с явным media_type:
curl https://api.zukko.pro/v1/messages \
-H "Authorization: Bearer fetch_ваш_ключ" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-sonnet-5",
"max_tokens": 512,
"messages": [{
"role": "user",
"content": [
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "iVBORw0KG..."}},
{"type": "text", "text": "Что на картинке?"}
]
}]
}'У /v1/chat/completions (OpenAI-схема) — это image_url с data-URI внутри строки, без отдельного поля media_type:
curl https://api.zukko.pro/v1/chat/completions \
-H "Authorization: Bearer fetch_ваш_ключ" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.6-sol",
"messages": [{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": "data:image/png;base64,iVBORw0KG..."}},
{"type": "text", "text": "Что на картинке?"}
]
}]
}'Если скопировать блок image_url в тело для /v1/messages или наоборот — сервер не распознает структуру и вернёт 400 без наводки на конкретное поле, потому что с его точки зрения это просто мусор в content. Первое, что стоит проверить при ошибке с картинкой: вы точно собираете тело под тот же эндпоинт, на который отправляете запрос.
Забытый префикс data-URI
Для OpenAI-совместимого image_url.url нужна полная строка data:image/png;base64, перед самими данными. Если отправить голый base64 без префикса, некоторые клиентские SDK молча его не добавляют, и до модели долетает строка, которая не парсится как URL. Для Anthropic-схемы префикс не нужен вообще — там media_type и data разнесены на два разных поля, и попытка засунуть data:image/png;base64, в поле data тоже сломает запрос, потому что модель ожидает чистый base64.
media_type не совпадает с реальным форматом файла
image/jpeg для файла, который на самом деле PNG (или наоборот, после конвертации на лету) — распространённая причина, когда base64 явно валиден, ключ верный, а модель либо не может прочитать изображение, либо описывает что-то не то. Проверяется тривиально: посмотреть на первые байты файла или просто пересохранить его в нужном формате перед кодированием, а не доверять расширению из имени файла.
Перепутанный эндпоинт: vision vs генерация изображений
Здесь путаница системная. Изображения на входе через /v1/messages или /v1/chat/completions читают модели Claude и GPT — claude-opus-5-5, claude-sonnet-5, gpt-5.6-sol, gpt-5.6-luna. У glm-5.3 и glm-5.3-flash vision нет — запрос с картинкой к ним нужно перенаправить на Claude или GPT. Актуальный список моделей — GET https://api.zukko.pro/v1/models. И чтение картинок — не то же самое, что их генерация. Генерация изображений через Zukko временно недоступна; когда она работала, модели вроде nano-banana-2-text-to-image были отдельным типом (kind=image) с биллингом за штуку и жили только на /v1/images/generations — отправка такого id в /v1/chat/completions давала model_not_found. Для справки, так выглядел запрос:
curl https://api.zukko.pro/v1/images/generations \
-H "Authorization: Bearer fetch_ваш_ключ" \
-H "Content-Type: application/json" \
-d '{"model": "nano-banana-2-text-to-image", "prompt": "город на закате, акварель"}'Оцените стоимость до отправки тяжёлой картинки
Изображения занимают заметно больше токенов, чем кажется на глаз, особенно в высоком разрешении. Прежде чем гонять запрос с большим файлом по кругу, дешевле прогнать тело через бесплатный подсчёт:
curl https://api.zukko.pro/v1/messages/count_tokens \
-H "Authorization: Bearer fetch_ваш_ключ" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{"model": "claude-sonnet-5", "messages": [{"role":"user","content":[{"type":"image","source":{"type":"base64","media_type":"image/png","data":"iVBORw0KG..."}}]}]}'Запрос ничего не стоит и не идёт к модели — только считает токены.
Как отладить быстро, а не гадать по коду ошибки
Соберите тело с картинкой размером 1×1 пиксель — такую строку легко держать в переменной окружения. Если запрос с ней проходит, а с боевым файлом падает — дело в кодировании или размере конкретного файла, а не в структуре запроса. Если падает и с 1×1 — проблема в самой форме тела: не тот эндпоинт, не то имя поля, забытый media_type. Это разделяет две категории ошибок за один запрос вместо перебора гипотез вслепую.
Общие ошибки — 401 за неверный ключ, 402 за пустой баланс, 429 при превышении лимита ключа — работают для запросов с картинками ровно так же, как для текстовых: проверка авторизации и баланса идёт раньше, чем сервер вообще разбирает содержимое content.
Что выглядит как ошибка vision, а на самом деле нет
Иногда причина вообще не в изображении. 503 provider_unavailable означает, что провайдер модели временно недоступен, а 503 model_disabled — что модель выключена в каталоге. Это не про формат картинки, а про доступность модели, и лечится сменой модели в запросе, а не переупаковкой base64. Отдельный случай — модель без vision: glm-5.3 и glm-5.3-flash изображения не принимают, и дело тут не в кодировании, а в выборе модели. Стоит сначала прогнать текстовый запрос той же моделью без изображения — если он тоже падает с тем же кодом, дело не в vision вообще, а в самой модели или ключе, и разбирать кодирование base64 в этом случае бессмысленно.
Ещё один источник путаницы — обрыв ответа на середине при потоковой генерации с картинкой на входе: он почти всегда означает таймаут клиента или сети, а не то, что модель не осилила изображение. Изображение увеличивает время на обработку запроса, и если HTTP-клиент настроен на короткий таймаут, расчёт токенов из count_tokens до отправки помогает понять, стоит ли увеличивать таймаут заранее, а не после того как соединение оборвалось на реальном пользователе.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Ключи OpenAI API в открытом доступе: почему ими нельзя пользоватьсяОткуда берутся «бесплатные» ключи на GitHub, почему пользоваться ими опасно и для вас, и для владельца, и как защитить собственный ключ от такой же утечки.
- Ошибки GLM API: почему не работает запрос и как это исправитьНеверный id модели, переполненный контекст 128K, ключ, баланс, лимиты: типичные причины ошибок GLM API и быстрый способ найти свою.
- This organization has been disabled в Claude API: что это и как продолжитьКлюч Anthropic внезапно отвечает «organization has been disabled». Объясняем, что это за ошибка, что проверить и как перенести SDK и Claude Code на другой провайдер.
- JetBrains AI Assistant не работает: причины и обходные путиAI Assistant не активируется, молчит или недоступен в отдельных файлах. Разбираем причины по документации JetBrains и подключаем свои модели через шлюз.