← all posts
// vision · vision-models

Vision models for UI and screenshot understanding: prompts grounded in visible evidence

A screenshot comes in with a red badge in the corner of a toolbar, a modal box that shouldn't be open, and one line asking what happened.

You hand that image to a vision model and it answers in full sentences, confident, specific, occasionally right. The catch is that the image you saved and the image the model actually reasoned over are not the same object. Somewhere between your upload and the forward pass it got resized, cropped, tiled into patches, and squeezed into a token budget you probably never checked. The words in the response describe that transformed thing, not your original file. So a fluent answer can be fully grounded, half grounded, or built around a detail that vanished before the model ever saw it.

the picture that reaches the model isn't your screenshot

For UI and screenshot work specifically, the material is dialogs, browser chrome, layout regressions, and application state. What you need from the model is narrow: name the visible controls, explain a failure, or propose one verifiable next click. Feed it a screenshot plus viewport metadata, task state, accessibility data if you have it, and what outcome you expected. "Understand this image" isn't a spec, and only a bounded question gives you something to measure model choice, preprocessing, and failure against.

The instinct with anything visual is to point a model at the pixels and ask. Resist that until the cheap path is exhausted. Most UI questions are answerable straight from the DOM and the accessibility tree. Pixels earn their place only where those interfaces go quiet: layout, ambiguity, spatial relationships, a screenshot of somebody else's app you don't control. The rule I've settled on is simple: the model reports what's visible before it's allowed to add anything from outside the frame.

keep a ledger or you're debugging blind

Once you commit to a vision call, keep the original asset and log every transformation you apply on top of it. "The model got it wrong" and "the model never saw the original" produce identical symptoms and completely different fixes. A record worth trusting later needs more than a model name scribbled in a spreadsheet:

  • model id, runtime, and the exact prompt revision
  • original resolution, plus whatever crop, resize, or rotation actually ran
  • image count, order, and timestamps if the frames are sequential
  • OCR text or accessibility data handed to the model alongside the pixels
  • token counts for vision input and text output, tracked separately
  • end-to-end latency, not just decode speed
  • outcome bucket: grounded, unsupported claim, abstain, or repair

If the provider exposes an image-detail or resolution setting, log the value you actually got, not the default you assumed. Running locally adds its own bookkeeping: vision projector version, quantization, context length, and whatever preprocessing sits in front of the encoder. Pair the wrong projector with the wrong base weights and the server keeps answering in fluent language while quietly reasoning over noise. Nothing in the output tells you that happened.

Score two failure classes separately. A missing answer is a nuisance, flag it for review. A confidently invented serial number, control, trend line, or product attribute is worse, because it reads like a real answer and can drive an action nobody checked. Put a handful of genuinely unanswerable cases in your eval set on purpose, and reward the model for saying it doesn't know.

four claims hiding inside one answer

The failure I see most is a model naming a control that isn't on screen, or grabbing the right label off the wrong region entirely. Open questions make it worse. "What's going on here" invites a coherent story assembled from what a dashboard like this usually looks like, not from what this one actually shows, and that kind of answer survives a quick skim because it matches what you expected to see.

Every response is smuggling four different kinds of claim at once, and each one deserves a different amount of trust:

observation:  red badge, upper right of the toolbar
association:  sits inside the network status cluster
inference:    the connection attempt failed
action:       restart the gateway (needs confirmation)

Observation you can check straight against the image. Association and inference each need progressively more context than the pixels alone supply. Action is the layer that touches something real, and it should never inherit authority just because the observation above it sounded certain.

Multiple images multiply the risk. Label each one and state plainly whether they show the same object, a page sequence, or shots from different times, because an unlabeled set lets the model assume continuity that isn't there. Cut duplicate frames; they burn tokens without adding evidence. If order matters, attach timestamps and occasionally run the same set shuffled. A model that tells the identical causal story regardless of frame order isn't reasoning over the sequence, it's pattern-matching each frame alone and improvising the narrative afterward.

what running it for real costs

Wrap every conclusion in a rule that it has to point back to a region, a label, a frame, or a fact you supplied, and put that rule in code, in preprocessing or validation, not left as prompt wording someone edits six months from now. Store the model and pipeline version next to every result so an upgrade can be replayed against the same images.

Privacy is a before-and-after problem. Crop out other rooms, faces, other people's screens, and addresses before the pixels leave the machine, and set separate retention windows for originals, thumbnails, OCR text, embeddings, and debug captures. Running the model locally, say through Ollama's vision support, cuts the external network path but does nothing for an unencrypted laptop disk or a dashboard login with more access than it needs.

Hosted and local paths need different tests. For a hosted API, driving Gemini through a multimodal CLI means measuring upload time, which region the request landed in, retention settings, and rate limits, alongside answer quality. Locally you're measuring cold load time, projector memory footprint, CPU or GPU placement, preprocessing time before the encoder even runs, and what happens to it under concurrent requests. Either way, compare the whole path at p50 and p95. Tokens per second during generation is not the number that matters here.

Stop tuning once the accuracy gate passes at a latency and abstention rate you can live with. Throwing more pixels or a bigger model at it doesn't make the system safer by default. It mostly gives the model more private detail to expose and more room to answer questions the evidence never supported.

Here's the part I haven't closed out: the shuffled-frame trick catches a model that's faking sequence reasoning, but it only tells you the model is guessing. It doesn't tell you how to make it track order across frames reliably. I run that check on every temporal pipeline I build. I still don't have a fix for what it finds.

#vision-models#multimodal#evaluation