# Ollama Deployment (Alternative to standalone llama-server) Ollama provides on-demand model loading with automatic 5-minute idle unload. Unlike standalone llama-server which keeps a model resident in VRAM permanently, Ollama loads on first request and frees VRAM when idle — ideal for shared GPU scenarios (work + inference on the same card). ## Installation ```bash curl -fsSL https://ollama.com/install.sh | sh ``` Creates systemd service `ollama`, listens on `127.0.0.1:11434`. ## Pull a model ```bash # Text inference ollama pull qwen2.5:7b # Vision (image understanding) ollama pull llava:7b ``` Models auto-unload after 5 minutes idle. `ollama ps` shows currently loaded models. ## Nginx reverse proxy — ⚠️ CRITICAL: Host header **Ollama rejects requests where the `Host` header is not `localhost`.** When proxying through nginx, you MUST override the Host header: ```nginx location /llm/ { proxy_pass http://127.0.0.1:11434/; proxy_http_version 1.1; proxy_set_header Host "localhost"; # ← REQUIRED — Ollama rejects $host proxy_set_header X-Real-IP $remote_addr; proxy_buffering off; proxy_read_timeout 120s; # allow for cold-start model load } ``` Without this, nginx forwards the original Host header (`grajmedia.duckdns.org`, `192.168.50.98`, etc.) and Ollama returns **403 Forbidden** immediately (microsecond reject — not a model-loading delay). Same fix applies for vision proxy: ```nginx location /vision/ { proxy_pass http://127.0.0.1:11434/; proxy_http_version 1.1; proxy_set_header Host "localhost"; proxy_set_header X-Real-IP $remote_addr; proxy_buffering off; proxy_read_timeout 300s; # vision models load slower } ``` ## OpenAI-compatible endpoint Ollama serves `/v1/chat/completions` with the same response format as llama-server and the OpenAI API: ```json { "id": "chatcmpl-898", "object": "chat.completion", "created": 1781059955, "model": "qwen2.5:7b", "choices": [{ "index": 0, "message": { "role": "assistant", "content": "Hi!" }, "finish_reason": "stop" }], "usage": { "prompt_tokens": 34, "completion_tokens": 3, "total_tokens": 37 } } ``` ## Migrating from llama-server to Ollama llama-server does not require a `model` field in the request body. Ollama does. When migrating, add the model name to every API call: **Before (llama-server):** ```javascript body: JSON.stringify({ messages: [...], temperature: 0, max_tokens: 500 }) ``` **After (Ollama):** ```javascript body: JSON.stringify({ model: 'qwen2.5:7b', messages: [...], temperature: 0, max_tokens: 500 }) ``` Then update the nginx proxy target and restart: ```bash # Change proxy_pass in nginx config, then: sudo nginx -t && sudo systemctl reload nginx ``` Stop the old llama-server to free VRAM: ```bash kill ``` ## Vision model API Ollama's vision API uses the native chat format with an `images` array. Base64 prefix `data:image/png;base64,` is NOT included — just the raw base64. ```javascript const b64 = await new Promise(resolve => { const r = new FileReader(); r.onload = () => resolve(r.result.split(',')[1]); // strip data:image/...;base64, r.readAsDataURL(file); }); const resp = await fetch('/vision/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'llava:7b', messages: [{ role: 'user', content: 'Your system prompt + instructions + schema', images: [b64] }], stream: false }) }); const data = await resp.json(); const content = data.message.content; // note: not choices[0].message (that's /v1/chat/completions format) ``` **Key difference:** The native `/api/chat` endpoint returns `data.message.content`. The OpenAI-compatible `/v1/chat/completions` returns `data.choices[0].message.content`. Use `/v1/chat/completions` for text models and `/api/chat` for vision if you use the native format — but send the `model` field either way. ## client_max_body_size Vision requests include a base64 image which can be 2-5 MB (or more for large screenshots). nginx default `client_max_body_size` is 1 MB — requests over this are silently rejected with 413. Set in the server block: ```nginx client_max_body_size 50m; ``` ## Replacing Tesseract OCR with a vision model For complex table layouts (schedules, invoices, spreadsheets), Tesseract cannot reliably preserve row/column structure. A VLM (vision-language model) like llava:7b sees the layout visually and outputs structured JSON directly, eliminating the need for: - Image preprocessing (upscaling, sharpening, contrast) - TSV bounding-box parsing - Coordinate-based row clustering - OCR text → LLM text → JSON pipeline **Old pipeline:** Screenshot → preprocessing → Tesseract → TSV bbox clustering → text LLM → JSON **New pipeline:** Screenshot → base64 → VLM → JSON