This reference describes the architecture implemented in the repository. GraphRAG uses deterministic ingestion stages and a bounded LangGraph workflow at query time.
flowchart LR
B[Browser] --> W[Next.js web and BFF]
W --> A[FastAPI API]
A --> R[(Redis)]
R --> K[Taskiq ingestion worker]
K --> O[(MinIO or S3)]
K --> Q[(Qdrant)]
K --> N[(Neo4j)]
A --> L[LangGraph query workflow]
L --> Q
L --> N
K --> OR[OpenRouter]
L --> OR
Only the Next.js service should be public in production. The API, worker, Redis, Neo4j, Qdrant, and object store communicate over the deployment's private network.
| Service | Responsibility |
|---|---|
| Next.js | Login, signed browser sessions, protected UI, and backend-for-frontend proxy |
| FastAPI | Health checks, access controls, document lifecycle, chat streaming, and graph responses |
| Taskiq worker | Extraction, chunking, embeddings, graph extraction, and cross-store indexing |
| Redis | Job queue, session state, progress events, quota reservations, and encrypted temporary credentials |
| MinIO/S3 | Original documents and normalized extraction artifacts |
| Qdrant | Workspace-scoped child-chunk vectors and retrieval payloads |
| Neo4j | Documents, parent chunks, entities, assertions, relations, and provenance |
| OpenRouter | Embeddings, document OCR/VLM, structured graph extraction, and answer generation |
- The web BFF forwards an authenticated upload to FastAPI.
- FastAPI validates the file, stores the original, creates a document record, and enqueues a workspace-scoped job.
- The worker selects a deterministic or visual extraction path by format and content.
- Normalized blocks become structural parent chunks and overlapping child chunks.
- OpenRouter embeds child chunks; Qdrant stores them under an immutable staging version with document, parent, and workspace identifiers.
- Structured graph extraction produces grounded entities and relationship hints. Neo4j stores only items tied to verbatim evidence from known chunks, together with polarity, conditions, and dates.
- After every required artifact succeeds, one Neo4j update atomically publishes the staging version as
active_versionand marks the document ready. Retrieval only admits versions published by ready documents. Progress events are published through Redis.
Every document-scoped write carries the ingestion version and is bracketed by workspace-epoch checks. If a reset or failure races with a write, the worker removes that version from Qdrant, Neo4j, and object storage before exiting.
| Input | Primary path | Conditional model path |
|---|---|---|
| Native PDF | PyMuPDF text and layout extraction | OCR/VLM for scanned pages or meaningful visual regions |
| Scanned PDF | Rendered page through OpenRouter document OCR | Vision fallback for complex visuals |
| DOCX | Paragraph, heading, list, and table extraction | Embedded information-bearing images |
| TXT | Deterministic decoding | None |
| Markdown | Heading-aware deterministic parsing | None |
Original files remain available in object storage. Extraction method, page number, section path, and source artifact references are retained as provenance.
PDF vision calls operate on bounded image or clustered drawing regions with nearby text as context. Pages routed through document OCR are not sent through vision a second time. DOCX images are resolved from their inline relationship position, paired with neighboring captions or prose, and de-duplicated by content hash.
flowchart LR
A[Analyze question] --> V[Retrieve child vectors]
V --> P[Expand and rank parents]
P --> S[Resolve entity seeds]
S --> G[Traverse graph 1-2 hops]
G --> F[Fuse bounded evidence]
F --> C[Create cited answer plan]
C --> T[Stream answer and evidence]
- Retrieval is filtered to the signed workspace and selected active documents.
- Child hits are deduplicated into parent candidates.
- Graph expansion is bounded by hop, node, edge, time, and context limits.
- Interactive queries have one end-to-end deadline and shorter per-model attempt timeouts; background ingestion keeps a separate, longer model timeout.
- Extracted relationships are retrieval hints, not verified facts. Model confidence is retained only as diagnostic metadata and is never used as a calibrated probability or retrieval rank.
- Vector and graph parent rankings are combined with reciprocal rank fusion rather than treating their scores as interchangeable. At least one slot is reserved for graph-only evidence when traversal finds it.
- The generated answer may reference only evidence IDs provided in context.
- Insufficient evidence produces an explicit limitation instead of unsupported model knowledge.
POST /api/v1/chat/stream streams real route progress and the traversed graph while the workflow runs, then emits the complete grounded answer. It does not simulate token streaming by slicing a finished answer.
Every durable or ephemeral record is namespaced by workspace_id:
- Neo4j nodes and assertions are workspace-scoped.
- Qdrant searches and deletions include a workspace filter.
- Object keys begin with
workspaces/{workspace_id}/. - Redis keys include the workspace or session namespace.
- Worker jobs carry both the workspace ID and its reset epoch.
This is an application-enforced isolation model for the two configured accounts. It is not a replacement for a transactional multi-tenant identity and authorization system.
GET /health/livechecks the FastAPI process.GET /health/readychecks Redis, Neo4j, Qdrant, and object storage.- Docker Compose starts the full seven-service local stack.
- Railway deploys web, API, and worker from separate Dockerfile-backed services and keeps storage services private.
See Railway deployment and Operations for procedures.