← all posts
// vision · vision-models

Vision models for chart and diagram understanding: structured output from images

If the numbers behind a chart exist anywhere else, go get them there and skip the model. That's the decision rule, and the rest of this piece just earns it: reach for a vision-language model only when the boring path is missing or can't do the job, and once you reach for one, treat every field it hands back as a claim to check, not a fact to store. Schemas force the output into the right shape. They don't tell you whether a number came from the axis you think it did.

Pixels are the last resort, not the first

Vision-language models create an odd confidence problem. Looking at a screenshot feels concrete, so the answer feels concrete too, but the model never sees your image. It sees a resized, cropped, tiled version with a fixed token budget, and whatever got trimmed on the way in is gone for good. A fluent description can be fully grounded, half grounded, or built around a detail that never made it through preprocessing, and you can't tell which from the tone of the answer.

For dashboards, plots, architecture diagrams, schematics, and slide visuals, the job is narrower than "understand this image." It's answering one question without guessing at a label you can't quite read. Feed the model the original image or vector export, the caption, the legend, the units, and the exact question. Not the whole slide deck and a vague prompt. That narrowness is what makes evaluation possible later.

Before any of that: check whether the source data or graph structure already exists upstream. If it does, use it and skip inference. I wouldn't run a vision model over a chart when the CSV that generated it sits two folders away in the same repo. That's a find command. A VLM earns its keep on layout, ambiguity, or language a fixed parser stumbles on, not on data you already have.

The paper trail starts before the API call

Once pixels are the only path in, preserve the original asset and log every transformation before the call goes out: runtime schema enforcement, domain checks, source coordinates, confidence, an explicit unanswerable state. Not just the JSON validator. The record needs more than a model name:

model, api/runtime, prompt_revision
source_dims, crop, resize, orientation
image_count, ordering, frame_timestamps
ocr_or_metadata_supplied
tokens: visual, input, output
first_response_ms, total_latency_ms, peak_mem
outcome: grounded | unsupported_claim | abstain | repair

If the provider exposes an image-detail or resolution setting, log the value used, not the default you assumed. Running locally, note the projector, quantization, and preprocessing build alongside the weights: text weights from one release paired with a vision projector from another can fail in ways the server won't surface, since it keeps returning fluent language regardless. Score label transcription, relationship accuracy, numerical consistency, and evidence citation, and score abstention on unreadable detail as a pass, not a shortfall. An omission gets flagged for review. An invented serial number or trend can walk into an automated action before anyone notices.

A reversed axis reads exactly like a real trend

The failure that matters most is a model narrating a convincing trend while it has quietly flipped an axis, a unit, or an arrow. That's the trap specific to this task: a schema-valid hallucination sails through casual review because it matches what the reader expected to see. Require a visible feature behind every conclusion that matters, and show that provenance to the reviewer directly instead of parking it in a log nobody opens.

Separating the prompt into layers that don't skip steps helps too. "There's a red indicator, upper right" is observation. "It belongs to the network panel" is association. "The connection has failed" is inference. "Restart the gateway" is action. Each layer needs its own evidence and authority. A plausible observation should never jump straight to an irreversible call.

Multiple images add an identity problem. Label each input, and state whether they show the same object, a page sequence, or different points in time. Drop duplicate frames; they cost tokens and add nothing. If order matters, attach timestamps and test a shuffled version of the set. A model giving the identical causal story after you reorder the frames isn't reading the sequence.

Put the rule in code, not in the prompt (visionmodels)

Validating structure and requiring provenance belongs in preprocessing, routing, and authorization code, not in prompt wording a rewrite quietly erodes. Store model and pipeline versions with every result so an upgrade can be replayed against the same images.

Privacy sits on both sides of inference. Crop faces, screens, and addresses you don't need before the call goes anywhere. Set retention separately for originals, thumbnails, and OCR text; they leak on different timelines. Running locally cuts external transfer, sure, but it does nothing for an unencrypted disk or an overbroad dashboard account.

Hosted and local paths get compared differently. For a hosted setup, say you're already driving this through a CLI, track upload time and provider error rates. Locally, track cold load and preprocessing time under real concurrency. Either way, measure the complete path at p50 and p95, not tokens per second on generation alone, a number that flatters everyone and hides what stalls.

Here's the tradeoff I take on purpose: I stop pushing resolution and model size once the visual gate passes at an abstention rate and latency I can live with, even though a bigger model would probably squeeze out more accuracy on the hard cases. What I give up is some of that marginal recall. What I get is a system that knows when to say it can't see something, instead of one that answers anyway.

#vision-models#multimodal#evaluation