v0.0.7 — Universal Vision
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 timeoutA ~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 multimodalimage_urlblocks), returns OpenAI-style response envelopeis_ollama_model(model)— returnsTrueforollama/<name>orollama_chat/<name>- Auto-selection in both
VisionExtractorandLLMExtractor— no user action required - No new dependencies —
httpxis 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-vminicpm-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 --strictclean on 24 source filesruff check+bandit -rclean- 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)