Spaces:
Running
A newer version of the Gradio SDK is available: 6.20.0
PROGRESS_TOMORROW.md β Supervised Build Ledger
The second half of the build: real models, real parsing, the entry points,
evaluation, and deployment. Everything past the β STOP line in PROGRESS.md.
This half is interactive, not looped. You drive it in a normal claude
session, one task at a time, and verify each task on a real document before
moving on. There is no DONE_ALL and no run-overnight.ps1 here β these tasks
call real models (non-deterministic), need API keys and a local server, and
have decisions only you should make. Leaving them to an unattended loop would
burn quota on work you can't blindly trust.
Protocol (per task)
- Prompt Claude Code for the next unchecked task, one at a time.
- Let it implement, then review the diff and eyeball the result on a real sample document β not just that tests pass. Model output that looks plausible can still be wrong; you are the check.
- When satisfied, commit (one commit per task) and tick the box.
- Add dependencies per task, not all at once (see policy below), so an install failure is isolated to the task that needs it.
- Default to Sonnet (
/model sonnet). These are routine integration tasks; Opus is not needed and costs ~3x.
Setup before T1
- Gemini key: get a free API key from Google AI Studio. Put it in
.envasGEMINI_API_KEY=...β this is the key for the app's calls to Gemini, and is completely separate from your Claude Code login. Do not setANTHROPIC_API_KEY(that would silently bill your Claude Code usage to the API). - Sample documents: have one real receipt photo, one scan, and one native-text PDF invoice on hand. You'll use them to eyeball real extractions (synthetic/public docs only β the free tier may train on inputs).
- Branch: lock in last night's milestone and start fresh (see the chat instructions accompanying this file).
Dependency policy
Add only what each task needs, when it needs it:
google-genai (T2) Β· docling (T3) Β· paddleocr or pytesseract (T4) Β·
ollama (T6) Β· watchdog (T8) Β· gradio (T9). This isolates the
Paddle-on-3.11 risk to T4 instead of breaking everything at once.
TASKS
T1 β Gemini config + key (prep) Add
uv add google-genai. In.envsetGEMINI_API_KEY,EXTRACTION_BACKEND=gemini,IMAGE_STRATEGY=vision_direct, andGEMINI_MODELperdocs/04_project_setup.md. No new code beyond confirmingconfig.pyaccepts it. Check:uv run python -c "from doc_agent.config import load_config; c=load_config(); print(c.extraction_backend, bool(c.gemini_api_key))"βgemini True.T2 β Gemini backend + real image acquire (build plan 2.5) β milestone Implement
src/doc_agent/backends/gemini.pyper architecture Β§5: multimodal call, schema-constrained JSON output, bounded retries + timeout, model id from config; register its builder in the factory. Wire a minimal realacquirefor images invision_directmode (load image bytes into the payload) soprocess_documentruns end-to-end on a photo/scan with no Docling/OCR yet. Tests:tests/test_gemini.pywith a mocked Gemini response (deterministic, no network/quota) covering schema-valid parsing, retry, and timeout. Check (auto):uv run pytest tests/test_gemini.py -q. Check (manual): runprocess_documenton your real receipt photo and confirmvendor_name,total,document_dateare actually correct. This is where the project stops being scaffolding.T3 β Docling parser (2.2)
uv add docling(first run downloads layout models β needs internet).src/doc_agent/parsing/docling_parser.py: native PDF β text/layout payload; wire intoacquirefornative_pdf. Check (manual):process_documenton your PDF invoice via Gemini returns correct fields.T4 β OCR path (2.3) β needed for Ollama /
ocr_then_text; deferrablesrc/doc_agent/parsing/ocr.py: image β text behind the payload interface; wire intoacquireforIMAGE_STRATEGY=ocr_then_text. DECISION: tryuv add paddleocr; if it won't resolve on 3.11, fall back touv add pytesseract(and install the Tesseract binary). Check: a sample image yields text;process_documentworks inocr_then_textmode. Withvision_direct+ Gemini you don't strictly need this for the demo β you can skip it now and return before adding Ollama.T5 β Consolidate real
acquireinto core Makecore.py's defaultacquiredo the real thing by modality + strategy (native_pdfβDocling;image+vision_directβbytes;image+ocr_then_textβOCR), soprocess_document(path)works on a real path for every input without injection. Keep the injectable seam for tests. Check (auto):uv run pytest -q(full suite still green; smoke tests still use the stub). Check (manual): one of each input type returns correct fields.T6 β Ollama backend (2.6) β OPTIONAL: offline/private; skip to ship faster
uv addthe ollama client; needs a local Ollama server + a pulled model (e.g.ollama pull qwen2.5:7b).src/doc_agent/backends/ollama.py: local call with JSON-schema/grammar-constrained decoding; text-in (pairs with the OCR path). Tests: mocked unit tests + a manual smoke against the local server. Check: withEXTRACTION_BACKEND=ollama+IMAGE_STRATEGY=ocr_then_text,process_documentreturns schema-valid data.T7 β Persistence (4.1)
src/doc_agent/store/db.py(SQLite append of accepted records) andstore/export.py(CSV export). Addtests/test_store.py. Check:uv run pytest tests/test_store.py -q; an accepted record persists and appears in the CSV.T8 β Watcher / batch runner (4.2)
uv add watchdog.src/doc_agent/ingest/watcher.py: watch (or poll)inbox/, call core, persist accepted, move source toprocessed/orreview/, per-document try/except + structured logging. Check: drop a mixed batch (PDF + scan + photo + one deliberately corrupt file) intodata/inbox/; all valid ones process, the corrupt one routes toreview/with a logged reason, and the loop does not stop.T9 β Web demo (4.3)
uv add gradio.src/doc_agent/web/app.py: single-upload UI rendering fields, per-field confidence, the validation report, and the decision; a "synthetic/public documents only" notice; stateless. Check:uv run python -m doc_agent.web.app; upload one of each modality and confirm a correct, validated result is displayed.T10 β Evaluation harness (5)
eval/datasets/loaders for a held-out SROIE slice + the labelled invoice JSON set, mapping gold labels to the schema.eval/run_eval.py: run core over a slice, normalize, compute per-field and per-critical-field precision/recall/F1, auto-accept precision on critical fields, and sweep the threshold. DECISION: you pickCONFIDENCE_THRESHOLDso auto-accept precision ontotal/tax/invoice_numberβ₯ 0.98; record the resulting recall. COST: this calls the model over many documents and counts against the Gemini free-tier daily limit β run a small slice first, deliberately. Check:run_evalprints the metrics table; you set the threshold; the results table goes into the README.T11 β Deploy (6) Create a Hugging Face Space (Gradio SDK, free),
python_version: "3.11",requirements.txtviauv export --no-hashes --no-dev -o requirements.txt, secrets (GEMINI_API_KEY,EXTRACTION_BACKEND=gemini,IMAGE_STRATEGY=vision_direct). Write the README: quickstart (both modes), the swappable-backend note, the results table, the demo URL, and the free-tier/privacy caveats. Check: the public URL processes an uploaded document of each modality.
Phase done when
A real document of each input type flows end-to-end through a live model into a validated record; the watcher processes a mixed batch unattended and routes exceptions to review; the eval harness has produced a precision/recall table and set the threshold; and the demo is live at a public URL. T4 and T6 are optional and can be revisited after the demo ships.