Урок 6. Изображения: как модель видит картинки

До сих пор мы отправляли модели только текст. Но Claude умеет смотреть: скриншот, фотографию, скан документа, слайд из презентации. Механика та же самая — тот же messages, тот же client.messages.create. Меняется одна деталь: поле content становится списком блоков, и среди них появляется блок с картинкой.

Вот эти два сообщения абсолютно одинаковы для API:

{"role": "user", "content": "Расскажи анекдот"}

{"role": "user", "content": [
    {"type": "text", "text": "Расскажи анекдот"}
]}

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

Блок типа image

Изображение — это блок с type: "image" и вложенным объектом source:

messages = [
    {
        "role": "user",
        "content": [
            {
                "type": "image",
                "source": {
                    "type": "base64",
                    "media_type": "image/jpeg",
                    "data": "/9j/4AAQSkZJRg..."
                }
            }
        ]
    }
]

Внутри source три поля:

  • type — как передаются данные. В базовом варианте это base64: картинка едет в теле запроса строкой.
  • media_type — что именно за файл: image/jpeg, image/png, image/gif, image/webp. Поле должно совпадать с реальным форматом файла — если подписать PNG как JPEG, запрос упадёт.
  • data — сами данные картинки, закодированные в base64, в виде строки.
API умеет и другие способы доставки картинки — например, по URL или через Files API. Актуальный список поддерживаемых значений source.type, форматов, лимитов на размер файла и разрешение смотрите в документации по vision: эти цифры меняются чаще, чем обновляются курсы.

Готовим base64

Base64 — способ записать двоичный файл обычными текстовыми символами, чтобы его можно было положить в JSON. Код выглядит громоздко, но делает всего четыре шага: открыть файл в бинарном режиме, прочитать байты, закодировать их, превратить результат в строку.

import base64

with open("./images/photo.png", "rb") as image_file:
    binary_data = image_file.read()            # байты файла
    encoded = base64.b64encode(binary_data)    # base64 в виде байтов
    base64_string = encoded.decode("utf-8")    # и в виде строки

Смотреть на base64_string глазами бессмысленно — это километр букв и цифр. Он нужен только машине.

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

import base64
import mimetypes

def create_image_message(image_path):
    with open(image_path, "rb") as image_file:
        binary_data = image_file.read()

    base64_string = base64.b64encode(binary_data).decode("utf-8")
    mime_type, _ = mimetypes.guess_type(image_path)   # "image/png" и т.п.

    return {
        "type": "image",
        "source": {
            "type": "base64",
            "media_type": mime_type,
            "data": base64_string
        }
    }

Теперь любой запрос с картинкой собирается в одну строчку.

Квиз 1

Куда в запросе кладётся картинка?

Картинка + текст

Можно отправить одну только картинку без единого слова — запрос валидный. Модель тогда просто начнёт её описывать: инструкций нет, она выбирает самое очевидное поведение.

Чаще нужно другое: картинка плюс вопрос. Добавляем второй блок — текстовый:

messages = [
    {
        "role": "user",
        "content": [
            create_image_message("./images/photo.png"),
            {"type": "text", "text": "Что человек на фото мог сделать, чтобы этого избежать?"}
        ]
    }
]

response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=2048,
    messages=messages
)
print(response.content[0].text)

В оригинальном курсе тут используется фотография из Wikimedia Commons: машина, застрявшая в глубокой луже посреди затопленной дороги. Модель видит и воду, и посадку кузова, и разбирает ситуацию по существу — «объехать по сухой стороне», «не соваться в воду неизвестной глубины». Своё фото подставьте любое: суть в том, что текстовый блок задаёт задачу, а блок с картинкой — материал.

Порядок блоков имеет значение для восприятия. Привычный и хорошо работающий вариант — сначала картинка, потом вопрос о ней; либо короткая подводка, картинка, вопрос.

Несколько картинок

Ограничения «одна картинка на сообщение» нет: добавляйте столько блоков image, сколько нужно.

messages = [
    {
        "role": "user",
        "content": [
            create_image_message("./images/animal1.png"),
            create_image_message("./images/animal2.png"),
            create_image_message("./images/animal3.png"),
            {"type": "text", "text": "Что это за животные?"}
        ]
    }
]

Здесь прячется грабля, ради которой этот раздел и написан. В оригинале передают три фото — белоголовый орлан, гризли в воде и крупный план дикобраза — и просят назвать животных. Сильная модель отвечает нормально. Модель попроще (в оригинале это старый Haiku) отвечает так, будто картинка была одна: подробно описывает дикобраза и молчит про орлана с медведем.

Лечится одной строчкой — подписями. Ставим перед каждой картинкой текстовый блок-ярлык:

messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": "Изображение 1:"},
            create_image_message("./images/animal1.png"),
            {"type": "text", "text": "Изображение 2:"},
            create_image_message("./images/animal2.png"),
            {"type": "text", "text": "Изображение 3:"},
            create_image_message("./images/animal3.png"),
            {"type": "text", "text": "Что это за животные?"}
        ]
    }
]

Ответ сразу становится нормальным: три пункта про трёх зверей. Подписи дают модели адреса, на которые она может ссылаться, — и заодно позволяют вам спрашивать точечно: «чем изображение 2 отличается от изображения 3?».

Квиз 2

Модель отвечает только про одну из трёх переданных картинок. Что помогает в первую очередь?

Что просить у модели

Описание и разбор сцены

Базовый режим: «опиши, что здесь», «что тут происходит», «оцени, безопасно ли это». Работает из коробки, но качество сильно зависит от точности запроса — ровно как с текстом.

Классический пример из курса — фотография компании друзей: восемь человек, двое из них частично обрезаны краем кадра. На вопрос «сколько людей на фото?» модель уверенно отвечает «семь». Развёрнутый промпт вытягивает правильный ответ:

{"type": "text", "text": "У тебя отличное зрение и внимание к деталям, ты эксперт в подсчёте объектов на фотографиях. Сколько людей на этом снимке? Некоторые люди могут быть частично перекрыты или обрезаны краем кадра — у кого-то видно только руку. Считай человека, даже если видна лишь часть тела. Прежде чем дать ответ в тегах <answer>, рассуждай пошагово в тегах <thinking> и разбери каждую часть изображения."}

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

Извлечение текста

Скан, фотография страницы, скриншот интерфейса — модель читает текст и возвращает его словами. Промпт вроде «перепиши текст с этой страницы как можно точнее» даёт транскрипт, с которым дальше можно работать как с обычным текстом.

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

Извлечение структурированных данных

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

Формат удобнее не описывать словами, а показать примером — и здесь пригождается формат диалога. Кладём в messages пару «картинка-пример → идеальный ответ», а следом настоящий запрос:

slide1_response = """{
    "background": "#F2E0BD",
    "title": "Haiku",
    "body": "Самая быстрая модель линейки, отвечает почти мгновенно",
    "image": "Схематичный рисунок головы человека в профиль, толстые чёрные линии на бледно-жёлтом фоне"
}"""

messages = [
    {
        "role": "user",
        "content": [
            create_image_message("./images/slide1.png"),
            {"type": "text", "text": "Сделай JSON-представление этого слайда: цвет фона, заголовок, основной текст, описание картинки."}
        ]
    },
    {"role": "assistant", "content": slide1_response},
    {
        "role": "user",
        "content": [
            create_image_message("./images/slide2.png"),
            {"type": "text", "text": "Сделай JSON-представление этого слайда: цвет фона, заголовок, основной текст, описание картинки."}
        ]
    }
]

Модель видит, как выглядел правильный ответ на первой картинке, и повторяет структуру на второй — те же ключи, тот же уровень детализации. Один пример экономит абзац инструкций.

Ограничения

  • Картинки стоят токенов. Изображение занимает место в контексте пропорционально размеру — десяток фото в одном запросе ощутимо дороже текста. Считать заранее удобно через token counting; актуальные формулы — в документации.
  • Есть потолки по размеру файла, разрешению и числу изображений в запросе. Слишком большие картинки уменьшаются автоматически, а вместе с масштабом теряется мелкий текст. Конкретные цифры смотрите в документации — они периодически меняются.
  • Модель не всегда точна в счёте и координатах. «Сколько объектов», «что левее» — зона ошибок; помогают явные инструкции и пошаговое рассуждение.
  • Мелкий или размытый текст читается плохо. Лучше отдать один разворот в хорошем качестве, чем всю пачку в низком.
  • Люди на фото — отдельная тема. Модель не занимается идентификацией конкретных людей по лицам и откажется это делать.
  • Модели различаются. На сложных картинках claude-sonnet-5 и claude-opus-5 заметно надёжнее, чем claude-haiku-4-5. Быстрая модель хороша там, где картинка простая, а задача типовая.
Квиз 3

Нужно расшифровать пятистраничный PDF, разложенный на пять скриншотов. Как надёжнее?

Упражнения

Упражнение 6.1 — Чек в таблицу

Сфотографируйте любой чек из магазина и попросите Claude вернуть строгий JSON: продавец, дата, список позиций с ценами, итоговая сумма. Добейтесь, чтобы в ответе был только JSON — без вступлений и комментариев.

Решение упражненияСначала попробуйте сами — потом сверьтесь
messages = [
    {
        "role": "user",
        "content": [
            create_image_message("./images/receipt.jpg"),
            {"type": "text", "text": (
                "Это фотография кассового чека. Верни данные строго в JSON "
                "со схемой: {\"merchant\": str, \"date\": \"YYYY-MM-DD\", "
                "\"items\": [{\"name\": str, \"qty\": number, \"price\": number}], "
                "\"total\": number}. "
                "Если поле не читается — поставь null, не выдумывай. "
                "В ответе выведи только JSON, без пояснений и без markdown-обрамления."
            )}
        ]
    }
]

response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=2048,
    messages=messages
)
print(response.content[0].text)

Три вещи делают ответ пригодным для программы: явная схема, правило «не читается — null» (иначе модель дорисует правдоподобные цифры) и запрет на любой текст вокруг JSON. Если ответ всё равно приходит в блоке с тройными кавычками, поможет приём из курса по промпт-инжинирингу — «вложить слова в уста Claude», начав ответ ассистента с открывающей фигурной скобки.

Упражнение 6.2 — Расшифровать и пересказать статью

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

Решение упражненияСначала попробуйте сами — потом сверьтесь
pages = [
    "./images/paper/page1.png",
    "./images/paper/page2.png",
    "./images/paper/page3.png",
    "./images/paper/page4.png",
    "./images/paper/page5.png",
]

def transcribe_page(path):
    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=5000,
        messages=[{
            "role": "user",
            "content": [
                create_image_message(path),
                {"type": "text", "text": "Перепиши текст с этой страницы статьи максимально точно, "
                                         "сохраняя заголовки и порядок абзацев. Ничего не пересказывай."}
            ]
        }]
    )
    return response.content[0].text

def summarize(pages):
    full_text = ""
    for path in pages:
        print("расшифровываю", path)
        full_text += transcribe_page(path) + "\n\n"

    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=5000,
        messages=[{
            "role": "user",
            "content": (
                f"Это расшифровка научной статьи <paper>{full_text}</paper>. "
                "Перескажи её для читателя без технического бэкграунда, минимум в трёх абзацах. "
                "Обязательно расшифруй все аббревиатуры и жаргон, по возможности используй аналогии."
            )
        }]
    )
    return response.content[0].text

print(summarize(pages))

Ключевое решение здесь — разделить работу на два разных типа запросов: пять «зрительных» на расшифровку и один чисто текстовый на пересказ. Каждая задача получает свой промпт, и модель не пытается одновременно читать и обобщать. Обратите внимание на теги <paper>: они отделяют данные от инструкции, чтобы текст статьи не был прочитан как команда.

Что запомнить

  • content может быть строкой или списком блоков. Для картинок нужен список.
  • Картинка — это блок {"type": "image", "source": {...}}, где в source лежат type: "base64", media_type и сама строка data.
  • media_type должен совпадать с реальным форматом файла: JPEG, PNG, GIF, WebP.
  • Функция-помощник на пять строк избавляет от ручной сборки блоков и сама угадывает MIME-тип.
  • Несколько картинок в одном сообщении — норма, но подписывайте их текстовыми блоками «Изображение 1:», «Изображение 2:».
  • Промпт-инжиниринг работает и здесь: роль, явные краевые случаи, пошаговое рассуждение, примеры «картинка → идеальный ответ».
  • Многостраничные документы — по странице за запрос, склейка отдельно.
  • Лимиты размера, разрешения и стоимость меняются — сверяйтесь с документацией, а не с памятью.

Дочитали и сделали упражнения? Зафиксируйте прогресс — отметка сохранится в вашем браузере.

Это был последний урок курса. Вы прошли путь от первого запроса до стриминга и картинок — теперь у вас есть весь базовый инструментарий Claude API. Дальше интереснее всего идти в две стороны: научить модель вызывать ваш код — курс «Работа с инструментами», — и научиться выжимать из промптов максимум качества — курс «Промптинг в реальных задачах».
Перевод и адаптация урока «Vision» курса Anthropic API Fundamentals © Anthropic, лицензия CC BY-NC 4.0. Перевод: Дарья Воронкина (@aishipuchka). Материал изменён: переведён на русский, названия моделей и примеры актуализированы, добавлены квизы и упражнения. Используется некоммерчески.