When RECEIPT_VISION_MODE=vision (default), uploaded receipt images are sent
directly to the vision-capable LLM (llama3.2-vision via Ollama) instead of
the OCR text excerpt. The model can read logos, stylised fonts, and layouts
that Tesseract OCR mangles (Home Depot, HMSHost/Sergio's, etc.).
Architecture:
- amount + date: always from Tesseract regex (deterministic, never LLM)
- vendor + category: vision LLM when image available, text LLM as fallback
- Fallthrough: if vision call fails for any reason, text path is tried next
- PDF/TXT/HTML receipts: always use text path (not visual media)
Revert instantly without a rebuild:
echo "RECEIPT_VISION_MODE=text" >> /root/odoo/odoo-ai/.env
docker compose up -d agent-service
config.py: add receipt_vision_mode setting (default 'vision')
expenses_agent.py: _VISION_MIMETYPES, _get_vision_mode() helper,
dual-path _parse_receipt_text (b64/mimetype params), _act() passes b64
tests: 92 passing — 4 new vision tests, 2 existing prompt tests
pinned to text mode via _get_vision_mode patch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The llama3.2-vision model was producing unreliable structured data
(wrong vendors, amounts, dates) making expense reports worse than
Tesseract + LLM extraction. Removes _ocr_image_vision(), the
vision JSON fast path in _parse_receipt_text(), _match_category(),
and the vision_ocr_model config setting entirely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- OllamaBackend enforces _MIN_TIMEOUT=300s (overrides OLLAMA_TIMEOUT env var)
- warm_model() background task loads activeblue-chat into VRAM at startup
- health/detailed reports "warming" vs "ok" via Ollama ps() API
- README updated with May 2026 changes and test coverage details
Introduces VISION_OCR_MODEL setting. When set (e.g. llama3.2-vision:11b),
receipt images are transcribed by the Ollama vision model before falling
back to Tesseract. Also improves Tesseract preprocessing with adaptive
binarisation (autocontrast + threshold at 140) for better accuracy on
thermal receipts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Some Odoo instances require the user's actual login/email for API key
auth rather than the __system__ special login. ODOO_USER defaults to
__system__ for standard Odoo 16+ installs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes all errors reported in docker compose logs agent-service:
1. config.py: add ollama_max_concurrent, claude_timeout, claude_max_concurrent
fields so LLMRouter(config=settings) can read them without AttributeError.
2. main.py - LLM router: drop manual OllamaBackend/ClaudeBackend construction;
call LLMRouter(config=settings, pg_pool=pool) to match class signature.
Fixes: OllamaBackend.__init__() unexpected kwarg 'base_url'.
3. main.py - DB: add 5-attempt retry with 2s backoff and redacted DSN logging.
Fixes: connection refused race on startup before Postgres accepts connections.
4. main.py - AgentRegistry: call AgentRegistry() with no args (class takes none),
then await agent_registry.load_from_odoo(odoo) to populate active agents.
Fixes: AgentRegistry.__init__() unexpected kwarg 'odoo'.
5. main.py - PeerBus: pass registry=agent_registry at construction; register
specialist agents on agent_registry (not peer_bus, which has no register()).
peer_bus.py: make directive_id optional (default None) — bus is a singleton
at startup; directive_id is only needed per-request.
Fixes: PeerBus.__init__() missing positional args 'registry' and 'directive_id'.
6. main.py - MasterAgent: drop unexpected peer_bus= kwarg from constructor call.
Fixes: MasterAgent.__init__() unexpected kwarg 'peer_bus'.
7. mcp_router.py: pass NotificationOptions() instance instead of None.
Fixes: AttributeError 'NoneType' has no attribute 'tools_changed' (was applied
in running container but not committed; now committed).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>