n4nAI

How GPT-5 interprets images alongside text

A practical guide to GPT-5 image understanding — how vision-language models process multimodal inputs, API patterns, token economics, and production pitfalls.

n4n Team5 min read1,027 words

Audio narration

Coming soon — every post will get a voice note here.

GPT-5 image understanding represents a shift from bolted-on vision encoders to native multimodal reasoning. The model doesn’t see images as separate tokens fed through a projection layer; it reasons over visual and textual representations in a shared latent space. For engineers building on this, the practical implications show up in prompt design, token budgeting, and failure modes that don’t exist in text-only workflows.

How the model actually processes images

When you send an image to the API, it gets resized and tiled based on resolution. The default “auto” mode fits the image into a 512×512 tile grid, but you can control this with the detail parameter. Low detail uses a single 512×512 encoding (85 tokens). High detail tiles the image at native resolution up to 2048×2048, with each 512×512 tile costing 170 tokens plus 85 for the base thumbnail.

{
  "model": "gpt-5",
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "Extract the table from this invoice"},
        {
          "type": "image_url",
          "image_url": {
            "url": "data:image/png;base64,...",
            "detail": "high"
          }
        }
      ]
    }
  ]
}

The token math matters. A 1024×1024 image at high detail consumes 4 tiles × 170 + 85 = 765 tokens before any text. At $5 per million input tokens, that’s ~$0.004 per image — negligible for low volume, significant at scale. Batch your vision calls when possible.

Prompt patterns that work

The model responds to structured visual reasoning prompts. Chain-of-thought works differently here: ask it to describe what it sees before answering the question.

VISION_COT_PROMPT = """
First, describe the image in detail: layout, text regions, charts, 
UI elements, colors, and any anomalies. Then answer the question.
Question: {question}
"""

For structured extraction, define a JSON schema and ask the model to populate it. This beats free-form parsing every time.

{
  "type": "object",
  "properties": {
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "description": {"type": "string"},
          "quantity": {"type": "number"},
          "unit_price": {"type": "number"},
          "total": {"type": "number"}
        },
        "required": ["description", "quantity", "unit_price", "total"]
      }
    },
    "subtotal": {"type": "number"},
    "tax": {"type": "number"},
    "total": {"type": "number"}
  },
  "required": ["line_items", "subtotal", "tax", "total"]
}

Pass the schema in the system prompt or as a function definition. The model’s ability to follow schemas on visual input is stronger than on text alone because the visual grounding constrains hallucination.

Handling multi-page documents

GPT-5 doesn’t natively accept PDFs. You render pages to images client-side. The standard pattern: convert PDF to PNG at 150-200 DPI, then send as multiple image_url blocks in a single message.

import fitz  # PyMuPDF

def pdf_to_base64_images(pdf_path: str, dpi: int = 150) -> list[str]:
    doc = fitz.open(pdf_path)
    images = []
    for page in doc:
        mat = fitz.Matrix(dpi / 72, dpi / 72)
        pix = page.get_pixmap(matrix=mat)
        images.append(base64.b64encode(pix.tobytes("png")).decode())
    return images

Send all pages in one request when the total token count fits. For long documents, chunk by logical sections (e.g., 5 pages at a time) and maintain a running summary in the conversation history. Don’t stuff 50 pages into one context window — you’ll hit limits and degrade quality.

Common failure modes

Text in images smaller than 12px at 512×512 becomes unreadable. The model literally cannot resolve it. If you’re processing receipts, screenshots, or dense tables, you must use high detail or pre-crop the relevant regions.

Charts and graphs fail when the visual encoding is ambiguous. A pie chart with 12 slices labeled only by legend colors will hallucinate values. Bar charts with logarithmic scales often get misread as linear. Mitigation: ask the model to describe the axes and scale first, then extract values.

UI screenshots confuse the model when chrome and content overlap. Browser tabs, bookmarks bars, and OS window decorations look like content. Crop to the viewport before sending.

Handwriting quality varies wildly. Print text at 300 DPI works reliably. Cursive, low-contrast, or rotated text degrades fast. If handwriting is core to your use case, budget for a dedicated OCR pass (Tesseract, PaddleOCR, or cloud OCR) and feed the extracted text alongside the image.

Token optimization strategies

Use low detail for classification, routing, or yes/no questions. Reserve high detail for extraction, reasoning, and anything requiring reading text.

def choose_detail(question: str, image_size: tuple[int, int]) -> str:
    # Heuristic: need to read text or inspect fine structure?
    text_heavy = any(kw in question.lower() for kw in 
                     ["read", "extract", "transcribe", "table", "text", "number", "value"])
    large_image = image_size[0] * image_size[1] > 512 * 512
    return "high" if (text_heavy or large_image) else "low"

Pre-process images server-side: downsample non-critical images, crop whitespace, convert to grayscale for text-heavy docs (saves bandwidth, tokens unchanged but faster upload). Strip EXIF data — it leaks privacy and adds bytes.

Streaming and latency

Vision requests add 200-800ms base latency over text-only, scaling with image count and resolution. Stream the response to keep perceived latency low.

async def stream_vision_response(messages: list[dict]):
    async with client.chat.completions.stream(
        model="gpt-5",
        messages=messages,
        max_tokens=2000,
    ) as stream:
        async for chunk in stream:
            if chunk.choices[0].delta.content:
                yield chunk.choices[0].delta.content

For high-throughput workloads, batch multiple images per request (up to the context limit) rather than making sequential calls. The model processes them in parallel internally.

Caching and deterministic outputs

Image inputs defeat standard prompt caching because the base64 payload changes every request. Two workarounds:

  1. Reference images by URL when the provider supports it — some gateways cache the downloaded bytes. n4n.ai forwards provider cache-control hints, so if the upstream caches the image fetch, you benefit automatically.

  2. Hash and deduplicate client-side. Store sha256(image_bytes) -> response in Redis. Before calling the API, check the cache. This works because the model is deterministic at temperature=0 for the same image+prompt.

import hashlib
import redis

r = redis.Redis(decode_responses=True)

def cached_vision_call(image_bytes: bytes, prompt: str, **kwargs) -> str:
    key = f"vision:{hashlib.sha256(image_bytes).hexdigest()}:{hashlib.sha256(prompt.encode()).hexdigest()}"
    cached = r.get(key)
    if cached:
        return cached
    response = call_vision_api(image_bytes, prompt, **kwargs)
    r.setex(key, 86400, response)  # 24hr TTL
    return response

Evaluation: measure what matters

Don’t rely on vibes. Build a small eval set (50-100 images) representative of your production distribution. Measure:

  • Field-level F1 for extraction tasks (precision/recall per field)
  • Exact match rate for structured output validity
  • Hallucination rate — count claims not grounded in the image
  • Latency p50/p95 at your target concurrency
def evaluate_extraction(ground_truth: dict, predicted: dict) -> dict:
    tp = fp = fn = 0
    all_keys = set(ground_truth.keys()) | set(predicted.keys())
    for k in all_keys:
        gt = ground_truth.get(k)
        pred = predicted.get(k)
        if gt and pred and gt == pred:
            tp += 1
        elif pred and gt != pred:
            fp += 1
        elif gt and not pred:
            fn += 1
    precision = tp / (tp + fp) if (tp + fp) else 0
    recall = tp / (tp + fn) if (tp + fn) else 0
    f1 = 2 * precision * recall / (precision + recall) if (precision + recall) else 0
    return {"precision": precision, "recall": recall, "f1": f1}

Run this eval on every prompt change. GPT-5 image understanding improves with prompt iteration, but regressions are real — especially when adding few-shot examples that confuse the visual reasoning.

Cost control in production

Set a hard token budget per request. Reject or downsample images that would exceed it.

MAX_VISION_TOKENS = 4000  # leave room for prompt + response

def estimate_vision_tokens(width: int, height: int, detail: str) -> int:
    if detail == "low":
        return 85
    tiles_w = math.ceil(width / 512)
    tiles_h = math.ceil(height / 512)
    return 85 + tiles_w * tiles_h * 170

def validate_image_budget(image_bytes: bytes, detail: str) -> tuple[bool, int]:
    img = Image.open(io.BytesIO(image_bytes))
    tokens = estimate_vision_tokens(img.width, img.height, detail)
    return tokens <= MAX_VISION_TOKENS, tokens

Log every request with: model, detail level, image dimensions, input tokens, output tokens, latency, and whether it hit cache. This data drives optimization decisions.

When not to use GPT-5 vision

  • High-volume OCR — dedicated OCR APIs are 10-50x cheaper per page
  • Real-time video — frame extraction + vision API adds too much latency; use a specialized video understanding model
  • Pixel-perfect measurement — the model estimates, it doesn’t measure. Don’t use it for dimension extraction from engineering drawings
  • Adversarial inputs — CAPTCHAs, distorted text, adversarial patches. The model will confidently hallucinate

Summary checklist

  • Choose detail: low by default, escalate to high only when text reading or fine detail matters
  • Structure prompts: describe → reason → extract
  • Use JSON schemas for all structured outputs
  • Pre-process: crop, downsample, strip metadata
  • Cache aggressively by image hash at temperature=0
  • Build an eval set before optimizing prompts
  • Monitor token spend per image category
  • Fall back to specialized tools for OCR, measurement, and video

GPT-5 image understanding is powerful because it fuses vision and language in a single reasoning pass. That same fusion means you can’t optimize the vision path in isolation — prompt, schema, and image preprocessing all interact. Treat the image as a first-class input with its own validation, budgeting, and evaluation pipeline.

Tagsgpt-5vision-language-modelimage-understanding

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All vision-language models: gpt-5, gemini 3 & claude opus 4.8 posts →