Урок 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, в виде строки.
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
}
}
Теперь любой запрос с картинкой собирается в одну строчку.
Куда в запросе кладётся картинка?
Картинка + текст
Можно отправить одну только картинку без единого слова — запрос валидный. Модель тогда просто начнёт её описывать: инструкций нет, она выбирает самое очевидное поведение.
Чаще нужно другое: картинка плюс вопрос. Добавляем второй блок — текстовый:
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?».
Модель отвечает только про одну из трёх переданных картинок. Что помогает в первую очередь?
Что просить у модели
Описание и разбор сцены
Базовый режим: «опиши, что здесь», «что тут происходит», «оцени, безопасно ли это». Работает из коробки, но качество сильно зависит от точности запроса — ровно как с текстом.
Классический пример из курса — фотография компании друзей: восемь человек, двое из них частично обрезаны краем кадра. На вопрос «сколько людей на фото?» модель уверенно отвечает «семь». Развёрнутый промпт вытягивает правильный ответ:
{"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. Быстрая модель хороша там, где картинка простая, а задача типовая.
Нужно расшифровать пятистраничный 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:».
- Промпт-инжиниринг работает и здесь: роль, явные краевые случаи, пошаговое рассуждение, примеры «картинка → идеальный ответ».
- Многостраничные документы — по странице за запрос, склейка отдельно.
- Лимиты размера, разрешения и стоимость меняются — сверяйтесь с документацией, а не с памятью.
Дочитали и сделали упражнения? Зафиксируйте прогресс — отметка сохранится в вашем браузере.