Перейти к содержимому
5 мин чтения

Зачем нужны лимиты изображений на LLM-шлюзе?

Лимиты изображений на LLM-шлюзе: как проверять байты, пиксели, PDF и число вложений до отправки запроса провайдеру.

Зачем нужны лимиты изображений на LLM-шлюзе?

Тяжелый мультимодальный запрос нельзя считать ошибкой провайдера. Если шлюз принимает изображение или PDF, он уже отвечает за то, чтобы отличить допустимый вход от файла, который съест память воркера, раздует JSON с base64, превысит контекст модели или закончится чужой неясной ошибкой.

Политика лимитов нужна не для того, чтобы наказать клиента за большой скан. Она нужна, чтобы отказ произошел до внешнего вызова, имел один стабильный код и объяснял, что именно клиент должен исправить. Пиксели, байты, число вложений и страницы PDF измеряют разные риски. Свести их к одному полю max_file_size_mb значит оставить большую часть проблемы без контроля.

Ранний отказ должен происходить до маршрутизации

Шлюз должен проверить вложения до выбора модели и до сетевого запроса к провайдеру. На этом этапе известны размер HTTP-тела, способ передачи, число частей, заявленный MIME-тип и, после безопасного разбора, фактический формат, размеры изображения и число страниц документа.

Проверка после маршрутизации выглядит заманчиво: можно взять ограничения конкретного провайдера и просто переслать ему файл. На практике это дает три плохих результата. Во-первых, один и тот же клиентский запрос получает разные ошибки в зависимости от маршрута. Во-вторых, шлюз тратит время на подпись запроса, сериализацию base64 и внешний вызов, хотя ответ был известен заранее. В-третьих, клиент видит текст, рассчитанный на человека, который читает документацию конкретного провайдера, а не на интеграцию через совместимый API.

Ранняя проверка не отменяет провайдерные лимиты. Она отделяет публичный контракт шлюза от переменных ограничений маршрута. После общей проверки маршрутизатор может применить более узкий профиль выбранной модели. Если модель принимает только один файл или имеет меньший предел площади, шлюз откажет с тем же форматом ошибки, но укажет ограничение выбранного маршрута.

Смысл такой последовательности прост:

  1. Ограничить тело запроса на HTTP-уровне.
  2. Разобрать структуру запроса и собрать все медиа-части.
  3. Декодировать вложения в ограниченный буфер.
  4. Прочитать метаданные без полного рендеринга, где это возможно.
  5. Проверить глобальные и маршрутные правила.
  6. Только после этого передать запрос провайдеру.

На каждом шаге шлюз обязан прекращать работу при первом достоверном нарушении. Не нужно распаковывать все страницы PDF, если заголовок уже сообщает, что тело запроса больше разрешенного предела. Не нужно вычислять пиксели у двадцатого изображения, если лимит количества файлов равен десяти.

Байты, пиксели и страницы отвечают на разные вопросы

Размер файла измеряет нагрузку на сеть, очереди, журналирование и временное хранилище. Размеры в пикселях показывают, сколько визуальных данных потенциально придется декодировать и передать модели. Число страниц измеряет кратность работы с документом. Число объектов во вложениях ограничивает накладные расходы на разбор, токены и число операций.

Ошибка, которую часто делают в шлюзах: разрешают JPEG до 10 МБ и считают задачу закрытой. JPEG с большой однородной областью может занимать мало места, но после декодирования дать десятки миллионов пикселей. PNG с прозрачностью, палитрой и метаданными создает другой профиль нагрузки. У PDF размер файла вообще плохо предсказывает число страниц, количество встроенных растров и сложность рендеринга.

Для изображения стоит хранить минимум четыре значения:

  • encoded_bytes, число байтов после декодирования base64 или загрузки файла;
  • width и height, полученные из фактического формата;
  • pixel_count, произведение ширины на высоту;
  • mime_type, установленный по содержимому, а не по расширению.

Для PDF добавляются page_count и, если шлюз рендерит страницы сам, ограничения на площадь каждой страницы и совокупную площадь. Документ из двухсот маленьких страниц и документ из двухсот плакатов формата A0 не одинаковы, даже если в обоих случаях page_count = 200.

Не смешивайте лимит пикселей и лимит длины стороны. Ограничение только на ширину или высоту пропускает очень вытянутые изображения. Ограничение только на площадь пропускает изображение с абсурдной длиной стороны, которое может сломать библиотеку, потребителя или предварительный просмотр. Нужны оба правила: максимальная ширина, максимальная высота и максимальное произведение.

Base64 увеличивает тело запроса, но не заменяет проверку файла

Когда клиент передает data: URI или поле base64, шлюз получает строку, а не готовый файл. Ее длина больше исходных байтов приблизительно на треть, плюс JSON-экранирование и служебная часть URI. Лимит тела HTTP защищает сервер от чрезмерного запроса, но не говорит, что получится после декодирования.

Поэтому проверка должна иметь два порога. Первый ограничивает Content-Length и реальный объем прочитанных байтов, если клиент использует chunked transfer. Второй ограничивает количество байтов, которые декодер имеет право выдать. Нельзя сначала безусловно декодировать строку в память, а потом смотреть на размер результата. В этот момент защита уже проиграла.

Псевдокод проверки выглядит так:

if request_body_bytes > limits.max_request_bytes:
    reject("REQUEST_TOO_LARGE")

for part in media_parts:
    if part.base64_chars > limits.max_base64_chars:
        reject("MEDIA_ENCODED_SIZE_EXCEEDED", part.index)

    decoded = decode_base64_with_output_cap(
        part.data,
        limits.max_file_bytes
    )

    if decoded.output_limit_reached:
        reject("MEDIA_FILE_SIZE_EXCEEDED", part.index)

Ограничение длины base64 не является заменой max_file_bytes. Оно помогает остановить работу еще раньше и дает более ясный диагноз, если клиент сформировал огромную строку. А ограничение результата защищает от неверной оценки длины, пробелов, переносов, URI-префиксов и ошибок реализации.

У URL-вложений другая проблема. Шлюз не должен считать URL безопасным только потому, что клиент не передал байты напрямую. Если шлюз скачивает объект сам, лимиты надо применять к ответу при чтении потока, запрещать неожиданные перенаправления и контролировать, куда разрешены исходящие обращения. Иначе лимит на inline base64 становится обходным путем через удаленный файл.

Метаданные нельзя принимать на веру

Клиент может назвать произвольные байты image/png, прикрепить расширение .jpg или указать в JSON правдоподобные width и height. Ни одно из этих полей не годится для принятия решения. Шлюз должен сам определить тип по сигнатуре, затем безопасно прочитать заголовки формата.

Для JPEG размеры обычно доступны в маркерах кадра, для PNG они находятся в заголовке IHDR, для WebP есть собственные контейнерные структуры. Это не значит, что достаточно написать несколько условных операторов. Форматы содержат варианты кодирования, метаданные, а обработчики изображений регулярно получают исправления безопасности. Используйте библиотеку, которая умеет читать заголовки с ограничениями ресурсов, и обновляйте ее так же внимательно, как HTTP-стек.

Есть важная граница: чтение метаданных не должно незаметно превратиться в полноценное декодирование изображения. Если библиотека выделяет буфер под весь растр, когда вам нужны только ширина и высота, вы все еще уязвимы к изображению с крайне высокой степенью сжатия. Установите лимит памяти и времени для обработчика, а не только лимиты, которые вы проверите после его работы.

Полезная последовательность проверки такова:

  1. Сверить заявленный MIME-тип с фактической сигнатурой и отклонить конфликт, если политика не разрешает нормализацию.
  2. Разрешить только нужные форматы, а не все, что умеет библиотека.
  3. Извлечь размеры и число кадров или страниц в ограниченном процессе.
  4. Проверить ширину, высоту, площадь и число объектов.
  5. Лишь затем декодировать, преобразовывать или создавать миниатюру.

Анимированные GIF и WebP требуют отдельного решения. Если маршрут принимает только статичный визуальный вход, шлюз должен явно выбрать первый кадр либо отказать с кодом ANIMATED_IMAGE_NOT_SUPPORTED. Молчаливо принимать анимацию и надеяться, что каждый провайдер интерпретирует ее одинаково нельзя. Число кадров, их площадь и длительность создают отдельную категорию нагрузки, которую не покрывает лимит одного изображения.

PDF нужно считать документом и набором визуальных страниц одновременно

Маскируйте PII в запросах
AI Router маскирует PII в LLM-запросах перед дальнейшей обработкой.

PDF часто попадает в шлюз под видом одного вложения, хотя для модели он может стать десятками или сотнями визуальных входов. Некоторые модели извлекают встроенный текст, некоторые рендерят страницы, некоторые делают и то и другое. Маршрут не должен обещать клиенту одинаковое качество для любого PDF только потому, что файл прошел лимит в мегабайтах.

Документация Gemini прямо разделяет эту проблему: для PDF она указывает ограничения по размеру и числу страниц, а страницы учитываются в обработке документа. Это хороший пример того, почему max_file_bytes не может быть единственным правилом. У провайдеров меняются точные пределы и способы учета, но сама модель риска не меняется.

Проверка PDF должна отвечать на три отдельных вопроса:

  • Сколько в документе страниц?
  • Можно ли безопасно разобрать его структуру в заданные память и время?
  • Какой объем работы создаст выбранная стратегия, извлечение текста, рендеринг страниц или оба варианта?

Лимит страниц полезнее сопровождать лимитом суммарной обработанной площади. Например, документ с 40 страницами может пройти ограничение по количеству, но быть плохим кандидатом для синхронного запроса, если каждая страница содержит большой скан. В таком случае есть только честные варианты: попросить клиента разделить документ, уменьшить сканы до отправки или направить работу в асинхронный конвейер.

Не делайте автоматическое уменьшение PDF единственным ответом. Оно может стереть мелкий текст, печати, таблицы и подписи, после чего модель даст уверенный, но неверный результат. Если шлюз предлагает нормализацию, клиент должен включить ее явно и знать, какой предел применится. Для задач извлечения реквизитов уместен один профиль, для классификации многостраничного пакета другой.

Один общий потолок не заменяет профили задач

Команда часто начинает с универсального правила: до 20 МБ, до 20 изображений, до 20 мегапикселей. Такой предел удобен в конфигурации, но он не отражает назначение запроса. Фото товара для классификации, скан паспорта для OCR, пачка счетов и медицинское изображение создают разные требования к детализации, задержке и ошибкам.

Разделите публичные лимиты на базовый профиль и явные профили обработки. Базовый профиль защищает шлюз и подходит для обычного chat completion с картинкой. Профиль document_ocr допускает меньше страниц, но большее разрешение на страницу. Профиль bulk_review допускает больше объектов только в асинхронной очереди. Профиль image_classification может принудительно уменьшать изображение, если задача не зависит от мелкого текста.

Не превращайте эти профили в неявную магию маршрутизатора. Клиент должен выбрать режим через отдельный параметр, через модельный алиас или через endpoint. Иначе один и тот же запрос сегодня пройдет, завтра уйдет на другого провайдера и начнет терять детали из-за скрытого ресайза.

Конфигурация может выглядеть так:

media_limits:
  default:
    max_request_bytes: 24MB
    max_media_items: 12
    max_file_bytes: 8MB
    max_width: 8192
    max_height: 8192
    max_pixels_per_image: 24000000
    max_total_pixels: 48000000
    max_pdf_pages: 24
  document_ocr:
    max_request_bytes: 32MB
    max_media_items: 6
    max_file_bytes: 16MB
    max_width: 10000
    max_height: 10000
    max_pixels_per_image: 40000000
    max_total_pixels: 80000000
    max_pdf_pages: 12
    require_explicit_pdf_mode: true

Числа здесь не являются универсальной нормой. Их надо выводить из пропускной способности воркеров, лимитов маршрутов, фактического распределения клиентских файлов и допустимой задержки. Но структура конфигурации важнее первоначальных значений: она не дает одной величине выдать себя за политику.

Ошибка должна объяснять действие, а не внутренности шлюза

Ограничивайте поток по ключам
Rate-limits на уровне ключа позволяют ограничивать поток запросов отдельных клиентов.

Клиенту не нужна строка provider rejected request. Ему нужно знать, что превысило лимит, где это произошло и какой предел действует. Но не выдавайте наружу стек обработки, имя внутреннего сервиса, правила конкретного провайдера или значения, которые помогут подбирать нагрузку на систему.

Хороший ответ API стабилен, машиночитаем и пригоден для интерфейса:

{
  "error": {
    "code": "IMAGE_DIMENSIONS_EXCEEDED",
    "message": "Изображение 3 превышает допустимую площадь.",
    "param": "messages[0].content[4].image_url",
    "details": {
      "width": 12000,
      "height": 9000,
      "pixels": 108000000,
      "max_pixels": 24000000,
      "max_width": 8192,
      "max_height": 8192
    },
    "request_id": "req_01J..."
  }
}

Стабильный код важнее текста сообщения. Текст меняется при локализации, а код нужен SDK, ретраям и аналитике. Дайте отдельные коды для превышения размера тела, размера декодированного файла, числа вложений, размеров изображения, суммарной площади, числа страниц, неподдерживаемого формата и ошибки безопасного разбора.

Не советуйте клиенту просто «повторить позже». Повтор не исправит изображение шириной 12 000 пикселей. Вместо этого укажите конкретное действие: уменьшить длинную сторону, экспортировать PDF по частям, передать файл через загрузочный механизм, выбрать асинхронный режим или убрать лишние вложения.

Также не стоит автоматически повторять запрос к другому провайдеру после отказа, связанного с медиа. Если шлюз уже установил, что запрос нарушает собственный публичный предел, повторная маршрутизация ничего не исправляет. Если же общий предел пройден, а маршрутный нет, можно выбрать совместимый маршрут только при явной политике сохранения качества и стоимости.

Наблюдаемость должна показывать причины, а не содержимое документов

Выбирайте модели из одного API
Через AI Router запросы направляются к моделям 68+ провайдеров из одного API.

Без метрик лимиты превращаются в догадки. Вам нужно видеть, какие проверки чаще всего отклоняют запросы, какие форматы создают большую площадь, как меняются размеры после обновления клиентских SDK и насколько близко успешные запросы подходят к пределам.

Не записывайте в обычные логи base64, URL с подписанными параметрами, содержимое PDF и OCR-текст ради такой статистики. Достаточно технических полей: тип входа, фактический MIME-тип, закодированные и декодированные байты, ширина, высота, площадь, число страниц, код отказа, выбранный профиль, маршрутный класс и идентификатор запроса. Для чувствительных отраслей даже эти данные должны жить в вашей политике хранения и доступов.

Особенно полезны две метрики. Первая показывает долю отказов по каждому коду и клиентскому ключу. Вторая показывает распределение успешных запросов относительно лимита, например долю изображений выше 80% разрешенной площади. Если значительная доля нормального трафика постоянно упирается в потолок, не спешите поднимать его. Сначала проверьте, не шлет ли один SDK оригиналы камер вместо уменьшенных копий.

Проведите тесты на границах. Нужны файлы ровно на лимите, на один байт выше, с максимальной шириной при малой высоте, с максимальной площадью при допустимых сторонах, с конфликтующим MIME-типом, с поврежденными заголовками, с большим числом маленьких картинок и с PDF, у которого заявленное и фактическое число страниц расходятся. Эти тесты ловят регрессии лучше, чем один счастливый запрос с обычным JPEG.

Правила маршрутов надо хранить отдельно от публичной политики

У разных моделей отличаются допустимые форматы, число изображений, способ передачи файлов и внутренняя стоимость визуального ввода. Документация Google Gemini, например, отдельно описывает ограничения inline-данных, файлового механизма, количества изображений и страниц PDF. Такие различия нельзя надежно выразить одним числом в коде приложения.

Храните возможности маршрута в данных: поддерживаемые MIME-типы, максимальное число объектов, требование загрузки файла вместо inline, лимиты документа, допустимые режимы качества и дату проверки записи. Публичная политика шлюза должна быть консервативным верхним уровнем. Маршрутная политика только сужает его, если конкретная модель требует этого.

Это разделение дает нормальный порядок выбора. Сначала шлюз отсеивает вход, который не примет ни один поддерживаемый маршрут. Потом он отбирает модели, совместимые с форматом и профилем. Наконец, он учитывает цену, задержку, резидентность данных и требования приложения. Если смешать эти этапы, ошибка клиента зависит от случайного места в логике выбора модели.

В AI Router такую политику имеет смысл применять на едином OpenAI-совместимом входе, до передачи запроса одному из внешних или локально размещенных маршрутов. Клиент тогда получает одинаковый контракт, даже когда команда меняет base_url и продолжает пользоваться своим SDK.

Не пытайтесь выиграть этот класс задач одной большой цифрой в конфиге. Начните с четкой схемы ошибок, безопасного чтения метаданных и отдельных счетчиков для байтов, пикселей, объектов и страниц. После этого каждый спор о новом лимите станет разговором о конкретной нагрузке и качестве результата, а не гаданием по тексту очередного ответа провайдера.

Часто задаваемые вопросы

Нужно ли ограничивать каждый файл, если уже есть лимит на размер запроса?

Проверяйте каждый объект изображения отдельно и весь запрос целиком. Иначе клиент обойдет лимит на файл, отправив десятки файлов, которые по отдельности выглядят безопасно, но вместе создают неподъемную нагрузку.

Можно ли контролировать тяжелые изображения только лимитом в мегабайтах?

Нет. Размер в байтах говорит о передаче и хранении, но почти ничего не говорит о стоимости декодирования и визуального разбора. Небольшой сжатый файл может содержать изображение с очень большой площадью.

Считать ли страницы PDF как изображения?

Обычно да, если PDF участвует в визуальном анализе. У него есть отдельная ось нагрузки: количество страниц, причем каждая страница может стать отдельным изображением для модели.

Как выбрать лимит на количество изображений в одном запросе?

Сначала объявите консервативный общий лимит, измерьте реальные распределения и сделайте отдельный маршрут для пакетных задач. Не ставьте высокий потолок только потому, что один провайдер однажды принял большой файл.

Почему нельзя просто передать файл провайдеру и показать его ошибку клиенту?

Лучше вернуть ошибку до обращения к провайдеру. Это снижает задержку, исключает непредсказуемые сообщения разных провайдеров и не превращает заведомо неверный запрос в платную попытку.

Как правильно учитывать base64 в лимитах API?

Пусть размер base64 будет транспортным ограничением, а размер декодированного файла и пиксели будут ограничениями содержимого. В ответе полезно показать, какой именно предел превысил клиент.

Можно ли доверять MIME-типу, который прислал клиент?

Не доверяйте имени файла, расширению и заявленному MIME-типу. Шлюз должен проверить сигнатуру формата, извлечь метаданные безопасным парсером и отклонить файл, если тип не совпадает с заявленным.

Стоит ли автоматически уменьшать все изображения перед отправкой модели?

Для чека, формы или страницы с мелким текстом иногда нужна более высокая детализация. Для общей классификации сцены она часто только увеличивает задержку и расход, поэтому полезно разделять режимы задач.

Что делать клиенту после ошибки IMAGE_DIMENSIONS_EXCEEDED?

Не повторяйте запрос автоматически без изменения вложений. Повторная отправка с тем же большим файлом только расходует квоту и маскирует ошибку клиента; предложите сжатие, уменьшение сторон или разбиение PDF.

Как учитывать разные лимиты у разных моделей и провайдеров?

Если провайдерные правила меняются, обновляйте таблицу возможностей маршрутов, а не общий публичный контракт без причины. Клиентам нужен стабильный набор понятных кодов, а внутренний выбор провайдера должен оставаться задачей шлюза.