Lesson 19 / 33

Sending Images to a Model API

Build a chat request that mixes text and an image, using a URL or a base64 data URL.

By · 15 years in web development

Messages with multiple content parts

Hosted multimodal APIs extend the familiar chat format: instead of a single string, a message's content becomes a list of parts, each with a type such as text or image. An image part can usually reference a public URL the provider fetches, or carry the bytes inline as base64, often wrapped in a data URL such as data:image/png;base64,.... The exact field names differ between providers, so check the documentation of the one you use; the OpenAI-style image_url part shown here is also accepted by many OpenAI-compatible servers, including local ones. Some APIs offer a detail or resolution setting that trades accuracy for token cost. Base64 inflates the payload by about a third, and providers enforce limits on image size, count and format, so resize images on your side before sending. For repeated use of the same large file, some providers offer a file upload endpoint so you send the bytes once.

A multimodal request end to end

Your code prepares the image, wraps it with the text in a message, sends it to an API or local server, and parses the reply.

Four connected stages: prepare image, build message parts, model server, parsed response.
Figure 7.1: prepare image (resize, encode) -> build message with text + image parts -> hosted API or local server -> parse and validate the response.

Building an image + text message

The image is encoded in memory as base64 and placed next to the question. Printing the part types confirms the structure before any network call.

import base64, io, json
from PIL import Image

img = Image.new("RGB", (64, 64), "white")      # stand-in for a real photo
buf = io.BytesIO()
img.save(buf, format="PNG")
b64 = base64.b64encode(buf.getvalue()).decode("ascii")

message = {
    "role": "user",
    "content": [
        {"type": "text", "text": "What is written on this receipt?"},
        {"type": "image_url",
         "image_url": {"url": f"data:image/png;base64,{b64}"}},
    ],
}
print([part["type"] for part in message["content"]])
print(message["content"][1]["image_url"]["url"][:22] + "...")
print(len(json.dumps(message)) > len(buf.getvalue()))   # base64 grows the payload

Output:

['text', 'image_url']
data:image/png;base64,...
True

Ask for structure, then validate it

For extraction tasks (bills, ID cards, forms) ask the model for JSON with named fields and validate the result with a schema library such as Pydantic. Treat a parse failure as a retry or a human-review case, never as data.

Quick check: Why should you resize a 12-megapixel phone photo before sending it to a vision API?

  • APIs only accept square images
  • Base64 cannot encode large files
  • Resizing adds EXIF metadata the model needs
  • Smaller images upload faster, fit size limits and usually cost fewer image tokens
Answer

Smaller images upload faster, fit size limits and usually cost fewer image tokens — Large images waste bandwidth, can exceed limits, and often cost more tokens without improving answers for typical questions.