Skip to content

v0.0.7 — Universal Vision

Choose a tag to compare

@sebastienrousseau sebastienrousseau released this 11 Apr 14:01
· 106 commits to main since this release
v0.0.7
c86f30e

Universal Vision. Turns the local Ollama vision path from 🔴 (600 s LiteLLM timeout, hallucinated output) to 🟢 (all 11 rows extracted in ~33 s, correct currency and balances). Three independent improvements, all verified end-to-end against real local Ollama models on Apple Silicon.

What's new

1. Direct Ollama bridge — bankstatementparser.hybrid.ollama_direct

# Auto-selected for any ollama/* model — zero opt-in needed
from bankstatementparser.hybrid import smart_ingest
result = smart_ingest("scan.pdf")  # just works, ~33s instead of 600s timeout

A ~220-line drop-in replacement for litellm.completion that targets Ollama's /api/chat endpoint via httpx. Sidesteps the upstream LiteLLM ↔ Ollama integration bug where vision calls with long structured-JSON system prompts hang at the 600 s timeout.

  • ollama_direct_completion(**kwargs) — accepts OpenAI-style messages (including multimodal image_url blocks), returns OpenAI-style response envelope
  • is_ollama_model(model) — returns True for ollama/<name> or ollama_chat/<name>
  • Auto-selection in both VisionExtractor and LLMExtractor — no user action required
  • No new dependencies — httpx is already a transitive dep of LiteLLM in [hybrid]

2. ollama/minicpm-v recommended default

ollama pull minicpm-v
export BSP_HYBRID_VISION_MODEL=ollama/minicpm-v

minicpm-v:8b (5.5 GB) is explicitly trained for OCR and document understanding. Replaces ollama/llava:7b which was a general-purpose multimodal model not designed for dense statement tables.

Model Result on synthetic scanned PDF
ollama/llava:7b 🔴 Hallucinates INR currency, fabricated rows
ollama/minicpm-v:8b 🟢 All 11 transactions, GBP, balances correct, ~33 s

3. Strip mode — VisionExtractor(strip_rows=True)

from bankstatementparser.hybrid import VisionExtractor, smart_ingest

vision = VisionExtractor(strip_rows=True, n_strips=4)
result = smart_ingest("dense_statement.pdf", vision_extractor=vision)

Splits each page into N overlapping horizontal strips (default 4, 10% overlap). Header strip extracts balances; body strips extract transactions; results merged by transaction_hash. Designed for dense pages (≥15 rows) where small local models can't process the full page — CLIP's 336×336 internal downscale destroys fine table detail on a full A4 page, but preserves it on a strip.

Smoke-test results

Path Model Mode Result
Text-LLM ollama/llama3 single-shot ✅ All 11 rows, VERIFIED, ~25 s
Vision-LLM ollama/minicpm-v:8b single-shot ✅ All 11 rows, GBP, ~33 s
Vision-LLM ollama/minicpm-v:8b strip_rows=True ✅ Sign convention correct, ~43 s

Install

pip install 'bankstatementparser[hybrid-vision]'

Migration from v0.0.6

Fully backwards compatible. Existing code keeps working — it just runs faster. Three opt-in upgrade patterns:

# 1. Do nothing — auto-bridge activates for ollama/* models
result = smart_ingest("scan.pdf")

# 2. Switch to minicpm-v
os.environ["BSP_HYBRID_VISION_MODEL"] = "ollama/minicpm-v"

# 3. Enable strip mode for dense pages
vision = VisionExtractor(strip_rows=True, n_strips=4)
result = smart_ingest("dense.pdf", vision_extractor=vision)

Test plan

  • 677 tests at 100% line + branch coverage (up from 649 on v0.0.6)
  • mypy --strict clean on 24 source files
  • ruff check + bandit -r clean
  • 32 docs accuracy tests all pass
  • All examples verified end-to-end
  • 44 CI checks pass

Full changelog

See CHANGELOG.md for the complete v0.0.7 entry.

Pull request: #51 (8 commits, all SSH-signed)