Backend — live runner
- New `POST /api/documents/:id/rag` endpoint. Loads `document_json` from
SQLite, reconstructs the DoclingDocument, wraps the model id in
`ModelIdentifier(ollama_name=...)`, and calls `agent._rag_loop`
off-thread (blocking sync call). Returns a `RAGResult` in the shape
the existing v1 import path already consumes, so the frontend overlay
is fully reused.
- `_rag_loop` is private upstream; we call it because `run()` wraps the
answer in a synthetic DoclingDocument and drops the iteration trace.
- Settings: `RAG_ENABLED`, `OLLAMA_HOST`, `RAG_MODEL_ID`. Router mounts
unconditionally; handler 503s when the flag is off or deps aren't
installed. `rag_available` surfaced in `/api/health`.
- Maps known docling-agent bugs to readable HTTP errors: 502 with
"the model couldn't produce a parseable answer" when `_rag_loop`
raises `IndexError` from `find_json_dicts([])[0]` after 3 + 3
rejection-sampling retries (model-dependent).
- Tests: 11 cases (flag off, query empty, no analysis, happy path,
model_id wrap, Ollama env, IndexError → 502, other errors → 500,
deps missing → 503).
Backend — bug fix
- Default `BATCH_PAGE_SIZE` flipped from `10` to `0` to match the
dataclass default. The old default silently dropped `document_json`
(see `domain/services.merge_results`) for any doc > 10 pages, which
broke the reasoning tunnel. Set `BATCH_PAGE_SIZE>0` explicitly on
memory-constrained deploys if batching is wanted.
Frontend — runner UX
- `features/reasoning/api.ts:runReasoning()` — POST wrapper.
- `RunReasoningDialog.vue` — query textarea + optional model_id
override. Blocks close while running, 20-40s loading state,
synthesises a sidecar-shaped envelope so the panel surfaces query +
model the same way an imported trace would.
- `ReasoningWorkspace.vue` — primary "Run reasoning" button; "Import
trace" relegated to ghost secondary.
- Store: `runDialogOpen`, `running`, `setRunning`.
Frontend — answer polish
- Answer rendered through `marked` + DOMPurify (models emit markdown
lists; `pre-wrap` rendered them as plain "1. …" strings).
- Dedicated answer block with orange border, "ANSWER" label, "Copy"
button (clipboard + "Copied ✓" feedback).
- IterationCard: drop the duplicate `response` block (the main answer
is authoritative); style reasons equal to `"fallback"` (docling-agent
`select_from_failure` placeholder) as italic muted "— no structured
rationale".
Frontend — node details contents
- Clicking a SectionHeader (or any node with compound children) lists
its contained elements in `NodeDetailsPanel` under a new "Contents"
block. Children come from the same `parentMap` used for Cytoscape
compound parenting (explicit PARENT_OF + synthetic section scope),
inverted once and cached as a computed.
- Click a child row → pan the viewport to it + swap the selection.
Housekeeping
- `cytoscape-navigator` removed from `package-lock.json` (follow-up
from the minimap removal in the previous commit).
Add Neo4j as an optional graph-native storage layer (ingestion profile).
Introduces infra/neo4j with a singleton async driver wrapper and an
idempotent bootstrap of constraints + indexes, wired into the FastAPI
lifespan. Integration tests skip when no live Neo4j is reachable.
Refs #186
Hybrid approach: reuse LocalChunker to chunk the DoclingDocument JSON
returned by Serve, so chunking works identically in both local and
remote modes without calling Serve's chunk endpoint.
Backend:
- _build_chunker() always returns LocalChunker (remove engine guard)
- Use docling-core[chunking] extra for required dependencies
- Skip client-side batching in remote mode (Serve manages its own
resources, and batching discards document_json needed for chunking)
- Fix Serve form fields: remove generate_page_images (not a Serve
field), use repeated form keys for to_formats and page_range
- Log Serve error response body on 4xx/5xx for diagnosis
- Fix FastAPI 204 DELETE routes missing response_model=None
Frontend:
- Update chunking feature flag to enable Prepare UI in remote mode
Closes#51
pypdfium2 is imported in analysis_service for PDF page counting
(batch feature #56) but was missing from requirements.txt, causing
CI collection errors on all 4 test modules.
domain/ must be pure with no external dependencies. bbox.py imports
docling_core and belongs in infra/. Also refactor ServeConverter to
use the canonical to_topleft_list via BoundingBox instead of
duplicated manual coordinate conversion. Move docling-core to base
requirements since it is now needed in both modes.
Implement the HTTP client adapter that delegates document conversion
to a remote Docling Serve instance via its /v1/convert/file endpoint.
Switchable via CONVERSION_ENGINE=remote env var. Includes health check,
API key auth, response parsing, and 30 new tests covering parsing,
type mapping, HTTP calls, and DI wiring.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>