Skip to content

Blog: Getting structured data out of images with Granite Vision 4.1 - #48

Open
planetf1 wants to merge 15 commits into
generative-computing:mainfrom
planetf1:blog/granite-vision-structured-extraction
Open

Blog: Getting structured data out of images with Granite Vision 4.1#48
planetf1 wants to merge 15 commits into
generative-computing:mainfrom
planetf1:blog/granite-vision-structured-extraction

Conversation

@planetf1

@planetf1 planetf1 commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Closes #60

What this is showing off

Vision models return prose. The point of this post is that they don't have to.

The blog demonstrates Mellea's extraction pattern — pass a format= Pydantic model to
m.instruct(), get a typed Python object back instead of a string. No JSON prompt
engineering, no json.JSONDecodeError handlers, no post-processing regex. The return type
is the contract, and constrained decoding enforces it.

It then builds up two layers of validation on top:

  • requirements= — plain-English semantic constraints (date format, positive totals).
    The model retries with the failed requirement injected into the repair prompt.
  • IVR validation_fn — programmatic arithmetic check (line items × quantities = subtotal).
    The failure reason gets fed back into the repair prompt verbatim.

The receipt image is a clean, synthetically-rendered (PIL) receipt with a grey smudge box
placed over part of the subtotal digit, giving the validation layers something concrete to catch.
Reviewer note: because it's a clean digital render rather than a photographed receipt (no skew,
no paper texture, no lighting), the demo is a light test of the vision model's visual robustness —
it mainly exercises the extraction/validation pipeline, not OCR under real-world noise. The
pipeline and its value proposition are real either way; readers wanting a harder vision test
would need a photographed source image instead.

Demo sticks to Ollama throughout — no mlx-vlm/vLLM setup needed to follow along.


Status: Ready for review — targeting publish 2026-07-28

All three original blockers are resolved:

  1. Ollama ships llama.cpp ≥9630 — Ollama 0.32.4 bundles llama.cpp b10091, well past this.
  2. granite-vision-4.1 lands in the Ollama library — the short library name still 404s, but
    this is moot: IBM has published an official pre-quantized GGUF repo,
    ibm-granite/granite-vision-4.1-4b-GGUF,
    and ollama pull hf.co/ibm-granite/granite-vision-4.1-4b-GGUF:Q4_K_M is a first-class,
    permanent, supported Ollama feature — not a temporary workaround.
  3. Assumed mellea model name — moot; the blog now uses the hf.co/... reference directly
    as model_id everywhere, so there's no naming assumption to confirm.

Code and API usage verified against mellea 0.7.0 (current PyPI latest) and re-run live against
the model above — all four code blocks pass, output matches what's printed in the post.

  • Remove the EDITORIAL NOTE block from the blog post
  • Flip PR from draft → ready for review
  • Ping ajbozarth for final review pass

Receipt arithmetic (verified)

Item Qty Unit Line
Cold brew coffee 3 $4.75 $14.25
Grain bowl 2 $12.95 $25.90
Granola bar 4 $2.95 $11.80
Oat milk add-on 3 $0.75 $2.25
Avocado toast 1 $11.50 $11.50
Blueberry muffin 2 $3.95 $7.90
Subtotal $73.60
Tax (8.5%) $6.26
Total $79.86

IVR check: sum(qty × unit_price) = $73.60. Smudged subtotal scenario: model misreads
as e.g. $70.60 → check fires → repair prompt receives "line items sum to 73.60, subtotal shows 70.60".


Handover note

  • Model path changed: pulling directly from IBM's official GGUF repo on Hugging Face
    (ollama pull hf.co/ibm-granite/granite-vision-4.1-4b-GGUF:Q4_K_M) — Ollama auto-pairs the
    mmproj, granite4_vision projector loads correctly. This replaces the old gguf-forge/mlx-vlm
    workaround path entirely; that tool was only ever a stand-in until a real GGUF existed.
  • All four code blocks re-verified today against this exact pull, mellea 0.7.0.
  • Added model_options={ModelOption.CONTEXT_WINDOW: 4096} to the structured-extraction and
    backend-swap examples — Ollama otherwise defaults to the model's full 131072-token context
    (~9 GB); capping at 4096 drops that to ~2.5 GB with no change in output quality.
  • ajbozarth reviewed an earlier version; those comments are addressed.
  • I've done final validation/testing directly against this model now.

Test plan

  • npm run dev — confirm post renders at /blogs/granite-vision-structured-extraction
  • Receipt image displays correctly (smudge on subtotal visible)
  • Code syntax highlighting correct on all blocks
  • Run all four code blocks against hf.co/ibm-granite/granite-vision-4.1-4b-GGUF:Q4_K_M

@planetf1
planetf1 requested review from a team and ajbozarth as code owners May 20, 2026 13:28
@planetf1
planetf1 requested a review from psschwei May 20, 2026 13:28
@planetf1
planetf1 marked this pull request as draft May 20, 2026 13:31
@psschwei

Copy link
Copy Markdown
Member

Don't have a strong opinion here, but assuming it takes a while to get the vision model into Ollama should we consider using vllm in the blog instead?

@ajbozarth ajbozarth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll walk through the blog and try it out myself when I have bandwidth, but to start heres a small review from Claude:


Code checks out against current mellea source — APIs, imports, signatures, and the RepairTemplateStrategy switch all verify. Front matter and asset are good. Snippet syntax checks pass; live execution skipped (model not in Ollama yet, per the editorial note). de-llmify score 1.

A few inline notes below. Pre-publish blockers (editorial note removal, Ollama availability) are already tracked in the PR description.

Comment thread content/blogs/granite-vision-structured-extraction.md
Comment thread content/blogs/granite-vision-structured-extraction.md Outdated
Comment thread content/blogs/granite-vision-structured-extraction.md Outdated
@planetf1

Copy link
Copy Markdown
Collaborator Author

Don't have a strong opinion here, but assuming it takes a while to get the vision model into Ollama should we consider using vllm in the blog instead?

vllm tends to target more the operational audience rather than developer I think but also doesn't have good macOS support. Whilst there's a fork supporting mlx, it doesn't support vision models. I'd actually suggest sticking with the mlx-vlm workaround if the gguf takes a while for mac. But we could add vllm as the linux workaround. Ollama remains the least friction, so will look into likely timescales.

@planetf1

planetf1 commented May 21, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks @psschwei @ajbozarth for comments. There are still two areas I'm working on

  • the likely timescale for ollama support (sometimes new model releases require changes). If long, fallback could be to add vllm for linux users
  • refining the actual scenario - I'm not happy with the run-through currently. Needs a bit more work on the scenario, prompts & mellea constructs. (hence draft - but wanted to share the overall idea and get general review)

@psschwei

Copy link
Copy Markdown
Member

vllm tends to target more the operational audience rather than developer I think but also doesn't have good macOS support. Whilst there's a fork supporting mlx, it doesn't support vision models. I'd actually suggest sticking with the mlx-vlm workaround if the gguf takes a while for mac. But we could add vllm as the linux workaround.

Sorry, I misread your original post, thought I read mlx-vllm. Mostly was just trying to see if ollama is a hard requirement or if serving the model another way would be fine.

Ollama remains the least friction, so will look into likely timescales.

My understanding is that it is being actively worked on, but may take a few weeks (apparently getting vision models working with ollama is not very straightforward).

Though, and not saying we should just posing as an idea, what if we target colab for running the code? The vision model is 4B which might fit on a free instance, and if not would only take a few credits (<$1) to run for an hour. It would also reduce the setup headaches even more (simply click a cell or two and everything is up and running).

@ajbozarth

Copy link
Copy Markdown
Contributor

The updates addressing my previous review look good, but I'm going to hold off on further review until you've finalized this and moved it out of draft

@psschwei

psschwei commented May 21, 2026

Copy link
Copy Markdown
Member

closing and reopening to get the DCO bot to fire
edit: didn't work 😕

@psschwei psschwei closed this May 21, 2026
@psschwei psschwei reopened this May 21, 2026
@psschwei psschwei closed this May 21, 2026
@psschwei psschwei reopened this May 21, 2026
planetf1 added 4 commits May 22, 2026 11:08
Blog post covering m.instruct() + format= + ImageBlock for typed receipt
extraction, building up through requirements= and IVR validation_fn.
Includes a synthetic receipt image generated with PIL.

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
Assists-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
- Add `text` language tag to output fence (fixes MD040 lint failure)
- Wrap check_line_totals with simple_validate() — validation_fn expects
  Callable[[Context], ValidationResult], not str directly
- pip install → uv add (consistent with other Mellea blogs)
- Add conclusion section with recap and cross-references to docs.mellea.ai

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
…on blog

- Replace line-item arithmetic check with subtotal+tax=total verification;
  the old check failed because granite3.2-vision reads discounts as positive
- Rewrite 'What we covered' as narrative 'From narration to data' section

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
@planetf1
planetf1 force-pushed the blog/granite-vision-structured-extraction branch from b2c5444 to f0be9bb Compare May 22, 2026 10:11
@planetf1

planetf1 commented May 28, 2026

Copy link
Copy Markdown
Collaborator Author

granite 4.1 vision support is being worked on -> https://github.com/gabe-l-hart/llama.cpp/tree/Granite4Vision. Thought is to hold this blog until that is merged/released - it will be another nice boost for granite vision given ollama support

@planetf1

planetf1 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator Author

PR for llama.cpp is at ggml-org/llama.cpp#23545

We could update blog to use llama.cpp directly and not wait for ollama to adopt the new version. llama release multiple times per day. lag for ollama is likely 0-4 days.

@psschwei

psschwei commented Jun 4, 2026

Copy link
Copy Markdown
Member

I would just wait the extra week for ollama

@gabe-l-hart

Copy link
Copy Markdown

Hey guys! Blog looks great and should hopefully be unblocked soon. With the recent changes for Ollama to depend on llama-server directly, we should be able to get this into Ollama quickly after the llama.cpp PR is merged.

One note:

Watch https://ollama.com/library for granite-vision-4.1. When it lands: remove the
editorial note, verify ollama pull granite-vision-4.1 works, flip to ready.

The naming convention for Ollama models is slightly different from HF, so the landing zone will be https://ollama.com/library/granite4.1-vision (and https://ollama.com/ibm/granite4.1-vision which is the org we can fully control without getting the Ollama team to create the model repo for us).

@gabe-l-hart

Copy link
Copy Markdown

The llama.cpp PR is officially merged! ggml-org/llama.cpp#23545

@planetf1

planetf1 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

Nice — just tested with b9570. Converted to GGUF and ran the blog's receipt extraction against llama-server, works as expected. Waiting on Ollama to pick it up before we publish.

@ajbozarth

Copy link
Copy Markdown
Contributor

Waiting on Ollama to pick it up before we publish.

Once upstream ollama is ready and you update the draft to match I'll take another pass at review. I'd suggest tentatively setting this to 2026-06-17 is a safe target date IIUC

@gabe-l-hart

Copy link
Copy Markdown

I think the 17th may be ambitious. Ollama is still a few versions behind on llama.cpp, so we'll be blocked on starting the publication until they bump past the point where the llama.cpp support was added. Once done, we'll need to get the conversion pipelines in place and then get the Ollama team to make a placeholder model, all of which can take a while based on their schedules / responsiveness.

@ajbozarth

Copy link
Copy Markdown
Contributor

I think the 17th may be ambitious. Ollama is still a few versions behind on llama.cpp

That's fair, I didn't look into how long the llama.cpp -> Ollama cycle was

@planetf1

Copy link
Copy Markdown
Collaborator Author

Quick update: The llama.cpp fix for the n_head=0 divide-by-zero crash (commit b9509) that enables Gemma 4 12B multimodal models is now available in Ollama v0.30.8 (released June 4, 2026). I've verified works correctly and the blog's receipt extraction examples run as expected. Ready to publish once we update the editorial note in the post.

@planetf1

planetf1 commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author

Quick update: The llama.cpp fix for the n_head=0 divide-by-zero crash (commit b9509) that enables Gemma 4 12B multimodal models is now available in Ollama v0.30.8 (released June 4, 2026).

However we don't yet have a gguf version on hf or in ollama repo. We could document how to do a conversion, but I wonder if we might see a vision gguf pushed to hf @gabe-l-hart ?

@planetf1

Copy link
Copy Markdown
Collaborator Author

Local testing status — 2026-06-15

Tested the model locally while the Ollama blocker is open.

Method: built an Ollama Modelfile from the Q4_K_M GGUF + mmproj-f16 in the HF cache, then fell back to homebrew llama-server when Ollama failed.

Ollama 0.30.8: can't load the model. The granite4_vision projector type (declared in clip.projector_type in the mmproj GGUF) is not present in Ollama 0.30.8's bundled llama-server binary. The b9509 fix in 0.30.8 addressed a different issue (n_head=0 divide-by-zero crash). granite4_vision projector support landed in llama.cpp build 9630.

Homebrew llama-server 9630: works correctly. Tested the Q4_K_M quant against the receipt image — all 6 line items extracted with correct quantities, unit prices, subtotal ($73.60), and total ($79.86).

So: the blog code and the ollama pull instructions are fine as written. Waiting on Ollama to ship llama.cpp ≥9630.

@planetf1

Copy link
Copy Markdown
Collaborator Author

Follow-up: full code verification + Q4 provenance note

Ran all four code blocks from the post against llama-server 9630 via uv run:

Block Result Notes
Naïve description Prose, correct items + totals
format=Receipt All fields populated, total 79.86
requirements= 1 repair triggered (date not ISO on first attempt), passed on second
validation_fn IVR 1 repair triggered (arithmetic off on first attempt), passed on second with delta 0.0000

The IVR repair loop fired in both blocks that use it, which is the core behaviour the post is demonstrating — so that's a useful confirmation, not just a pass/fail.

Q4 provenance: the GGUF I tested against (ibm-granite--granite-vision-4.1-4b.Q4_K_M.gguf) is a local conversion — general.name in the GGUF metadata is a git commit hash, not an IBM release tag. The HF repo currently only publishes full safetensors weights. When the official GGUF lands (either on HF or in the Ollama library), a retest would be worth doing.

No code changes needed. Blocking on Ollama shipping llama.cpp ≥9630.

@psschwei

Copy link
Copy Markdown
Member

FYI: to avoid the DCO failure on bot commits, you can add the following to your settings.json:

  "attribution": {
    "commit": "",
    "pr": ""
  },

@gabe-l-hart

Copy link
Copy Markdown

@planetf1 It looks like the next version of Ollama should support G4V (https://github.com/ollama/ollama/blob/v0.30.9-rc0/LLAMA_CPP_VERSION). Support for G4V was added in b9534, so v0.30.8 was still before that (b9509)

@gabe-l-hart

Copy link
Copy Markdown

For official GGUFs, we're actively working through that. There seems to be a bug in the implementation for 4.0 that @mrutkows and I were trying to debug on Friday. I didn't find it, so we'll proceed with 4.1 and keep digging on 4.0 in the background.

planetf1 and others added 6 commits June 15, 2026 17:20
- New receipt image: 6 line items with smudged subtotal digit
- Expanded editorial note: marks as draft, notes scenario still being
  iterated, clarifies Ollama not yet available but expected soon
- Sync blog body to new receipt values ($79.86 total, no discounts)
- IVR section references smudged subtotal as the failure trigger

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
Switch from RejectionSamplingStrategy to RepairTemplateStrategy in both
the requirements= and IVR sections. RejectionSamplingStrategy just retries
with the same prompt; RepairTemplateStrategy injects the validation failure
reason into the repair prompt — which is what the surrounding prose already
describes.

Also promote "Going further" from bold text to a ## heading, and add a
paragraph to the conclusion making detection vs. repair guarantees explicit.

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
- Add representative-output note after prose output block (line 89)
- Apply suggested rewrite: "The point of wiring the check programmatically
  is that a silent wrong answer is no longer possible. Repair success is a
  separate question."
- Tighten conclusion: drop recap paragraphs covering format=, requirements=,
  and backend portability (all covered inline); lead with the
  detection-vs-repair framing which is the only new content

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
…repo directly

Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
…y Ollama blockers

All four code blocks confirmed working via homebrew llama-server 9630 with
locally converted Q4_K_M GGUF. Replaces vague "coming soon" language with
two concrete Ollama blockers: granite4_vision projector support (needs
llama.cpp ≥9630) and model not yet in the Ollama library. Also fixes
MD009 trailing spaces on blank blockquote lines.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
@planetf1
planetf1 force-pushed the blog/granite-vision-structured-extraction branch from 3a0f74c to 84a2c14 Compare June 15, 2026 16:27
@planetf1

Copy link
Copy Markdown
Collaborator Author

Thanks @gabe-l-hart - I'll be going with 4.1 anyway, and @psschwei useful tip. added (previously was only in md files so not so rigidly adhered to)

@planetf1

Copy link
Copy Markdown
Collaborator Author

@gabe-l-hart any news on getting the models on the ollama library?

planetf1 added 4 commits June 25, 2026 15:25
Replaces the mlx-vlm server approach in the editorial note with a simpler
gguf-forge workflow: quantize the HF safetensors to GGUF with
`gguf-forge ibm-granite/granite-vision-4.1-4b`, then pull via Ollama's
HuggingFace integration (`hf.co/ibm-granite/granite-vision-4.1-4b`).

Also:
- Sets target publish date of ~2026-07-02
- Flags `granite-vision-4.1` as an assumed mellea model name (verify before
  publishing, update all four model_id= references if it differs)
- The minimal test change is now just swapping model_id in start_session()
  rather than restructuring the session to use MelleaSession/OpenAIBackend

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
… scope

Two fixes in the granite-vision structured-extraction blog:

1. Add :.2f formatting to check_line_items error string. Without it Python
   strips trailing zeros: {73.60} → "73.6". The blog prose already shows
   "line items sum to 73.60, subtotal shows 70.60" — the code now matches.

2. Replace "four model_id= references" with precise instructions: update the
   bash `ollama pull` line and the three Python `start_session(model_id=...)`
   calls. The `ibm-granite/granite-vision-4.1-4b` in the OpenAI backend block
   is the HF model ID and is unaffected by the Ollama model name.

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
gguf-forge creates local GGUFs in ~/.cache/huggingface/hub/.../gguf/,
it does not push to HuggingFace. The previous workaround (ollama pull
hf.co/...) fails with "Repository is not GGUF or is not compatible".

Correct approach: register the local Q4_K_M GGUF with ollama create
so the model is available as granite-vision-4.1. This means no Python
code changes are needed — all code blocks run as written.

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
- Replace vague "remove this note and flip to ready" with a numbered
  5-step publish checklist: verify ollama pull, confirm model name,
  update frontmatter date, delete editorial block, ping ajbozarth.
- Add gguf-forge install prerequisites (brew install llama.cpp +
  uv tool install) so reviewers without it know how to get it.
- Include the mmproj-f16.gguf in the Modelfile (vision models need
  both the language model GGUF and the visual projector).
- Use a sed-substituted Modelfile to avoid heredoc variable expansion
  issues in the bash snippet.

Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
@ajbozarth

Copy link
Copy Markdown
Contributor

I did an in depth review after the latest updates and pending the items outlined in the EDITORIAL NOTE this is good to go. In my testing following Nigels gguf-forge steps the model outputted garbage, so we'll want to re-test once the real model is on ollama.

@gabe-l-hart whenever the model is available on ollama if you could ping here and I'll clean this up and do final testing if @planetf1 is still out (he's out until July 16th)

@psschwei psschwei left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The official GGUF is on HF.

So in theory we could run the model using something like this:

ollama run hf.co/ibm-granite/granite-vision-4.1-4b-GGUF:Q4_K_M

haven't tested outside of that command

@planetf1

planetf1 commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator Author

Picking this back up. HF model is finalized, and IBM shipped an official GGUF repo — ollama pull hf.co/ibm-granite/granite-vision-4.1-4b-GGUF:Q4_K_M works directly, no workarounds needed. All 4 code blocks re-verified. Doing final validation now, will ping when ready.

@planetf1
planetf1 marked this pull request as ready for review July 27, 2026 15:21
@gabe-l-hart

Copy link
Copy Markdown

There is still a bug in the llama.cpp implementation that we should have a PR up for soon, but the 4.1 model works well most of the time, even without the fix, so we should be able to move forward with this PR to the website.

We haven't pushed an official Ollama model yet, but that's on the TODO list soon. In the meantime, the command from @psschwei to run directly from HF should work now that Ollama cleanly uses embedded jinja2 templates in GGUFs.

This session's local pr-48 worktree drifted onto a different branch
than this PR's actual head, so these fixes never landed here until now.
Applying the final state as one commit on top of the existing history:

- Model now published as an official pre-quantized GGUF repo
  (ibm-granite/granite-vision-4.1-4b-GGUF); pull it directly via
  `ollama pull hf.co/ibm-granite/granite-vision-4.1-4b-GGUF:Q4_K_M`
  instead of the gguf-forge local-conversion workaround.
- Cap Ollama's context window (ModelOption.CONTEXT_WINDOW: 4096) to
  avoid the ~9GB default full-context memory footprint.
- Remove the editorial note (all blockers resolved, re-verified
  end-to-end against mellea 0.7.0).
- Drop mlx-vlm from the backend-swap example; note the demo uses
  Ollama throughout.
- Expand IVR on first use for clarity.
- Set publish date to 2026-07-28.

Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
@ajbozarth

Copy link
Copy Markdown
Contributor

I'll do a full review pass today

@ajbozarth ajbozarth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM feel free to merge tomorrow when you start your day @planetf1

@planetf1

Copy link
Copy Markdown
Collaborator Author

Thanks for the update @gabe-l-hart and for the approval @ajbozarth - publishing

@planetf1
planetf1 added this pull request to the merge queue Jul 28, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Blog post: Getting structured data out of images with Granite Vision 4.1

4 participants