← All work / Research & learning

RAGDocs.

Building document intelligence,
one iteration at a time.

A personal experiment in asking better questions of documents became a deeper study of retrieval, processing, and source evidence. This is how the system changed as the engineering questions grew.

Project
Independent research & learning
Role
Design & implementation
Focus
RAG · Backend architecture · Document processing
RAGDocs document search interface with a knowledge-base scope, upload control, and selectable retrieval mode
V3 / A workspace-based interface for searching a document collection

The evolution

Each version started
with a new question.

The first question was simple: can I ask a PDF a question and check my understanding? The later questions were about persistence, retrieval quality, long-running work, and the boundaries between users.

V1

Understand the loop.

Local ChromaDB collections held document chunks and quiz answers. MySQL tracked state. Document filters kept retrieval tied to the selected PDF.

Learning focus: document Q&A and semantic quiz grading.

Local prototype ↗
V2

Make persistence portable.

Chunks and embeddings moved into PostgreSQL with pgvector. Supabase Storage held the PDFs, while Docker and migrations gave the project a deployment-oriented shape.

Learning focus: cloud-backed storage and relational vector search.

Cloud iteration ↗
V3

Engineer the whole path.

Workspaces, asynchronous indexing, multiple retrieval strategies, source metadata, and a separate React interface became parts of the same product workflow.

Learning focus: retrieval engineering and a multi-user experience.

Current backend ↗

Current system / V3

Separate the responsibilities.
Keep the evidence connected.

FastAPI handles authentication, uploads, scope checks, and queries. Longer indexing jobs run in a Celery worker. PostgreSQL stores both application records and pgvector embeddings; Redis coordinates queued work and status events. V3 stores original files in configurable local object storage.

React + TypeScriptAuthentication · Workspaces · Search

HTTP / WebSocket

FastAPI applicationAuthorize · Upload · Query
Indexing pathRedis → Celery workerExtract → Clean → Chunk → Embed
Answer pathScoped retrieval → Rank fusionLightweight rerank → Grounded answer
PostgreSQL + pgvectorUsers · Workspaces · Documents · Chunks · Traces
Local object storageOriginal PDF files
Conceptual responsibility map of V3. Queued ingestion and query handling share indexed data; this is not a verbatim network topology.
  • FastAPI
  • PostgreSQL 16 / pgvector
  • Redis / Celery
  • Hugging Face embeddings
  • Gemini
  • React / TanStack Query
  • Docker Compose

Engineering / Ingestion

An upload is the start
of a job.

PDF extraction, normalization, embedding requests, and database writes can outlast a normal API request. The upload path returns a queued document record and dispatches indexing to Celery. Users can follow the document state while the worker processes it.

  1. Extract pages
  2. Clean text
  3. Chunk by page
  4. Embed
  5. Persist

Keep the page attached.

Normalization repairs extraction artifacts before chunking. Each chunk retains its PDF page number, giving later evidence cards a route back to the original document. This improves traceability without promising perfect extraction for every PDF.

Make failure visible.

The worker uses bounded retries with exponential delay, up to three retries for document indexing. The document lifecycle is queued → processing → completed, or failed after the retry allowance is exhausted.

Page-aware chunk recordsSource ↗
for page_number, text in pages:
    records.extend(
        (chunk, page_number)
        for chunk in _page_chunks(text)
    )

The page number travels with each chunk into persistence. Older documents may need reprocessing to gain this metadata.

Engineering / Retrieval

Meaning and exact terms
are different signals.

Dense search can connect different phrasings of an idea. Lexical search can recover exact names, IDs, numbers, and phrases. V3 exposes both, plus hybrid and smart modes, so retrieval strategy is an explicit part of the experience.

Dense
Embed the query and order scoped chunks by pgvector L2 distance.
Lexical
Use PostgreSQL full-text search to rank exact-term matches.
Hybrid
Merge dense and lexical candidates using reciprocal-rank-fusion-style ranking.
Smart
Choose lexical search for certain short, exact-looking queries; use hybrid otherwise.
RAGDocs search-mode menu showing Smart, Semantic, Exact terms, and Hybrid options
The interface exposes the retrieval choices and explains the automatic mode.

Retrieve candidates, then refine the order.

Hybrid search combines rank positions rather than comparing raw vector-distance and lexical scores. Hybrid and lexical results then receive a lightweight rerank that blends retrieval score with query-term overlap. The current heuristic is intentionally simple; it is not a transformer cross-encoder.

Second-stage ranking / shortened excerptSource ↗
overlap = len(terms & words) / max(len(terms), 1)
item["rerank_score"] = round(
    item.get("retrieval_score", item.get("score", 0)) * 0.7
    + overlap * 0.3,
    6,
)

A ranking technique is an implementation choice, not a measured quality result. A labeled query set is still needed to evaluate its effect.

Engineering / Grounding

The answer should carry
its sources with it.

Retrieved passages reach the language model with filename, page number when available, chunk ID, and rank. The response also exposes evidence and citations for the frontend, so an answer can be inspected alongside the passages that informed it.

The zero-evidence boundarySource ↗
if not evidence:
    return {
        "answer": "I don't have enough information in the selected documents to answer that.",
        "model": None,
    }

When retrieval returns no evidence, the query path abstains. When evidence exists, the prompt instructs the model to answer from it. That does not independently verify every generated claim; the default lightweight path does not run full claim-level verification.

Engineering / Scope & access

Relevance comes after
the access boundary.

V3 organizes documents into knowledge bases inside workspaces. A query may target the workspace, a knowledge base, or selected documents. The backend checks membership, roles, and scope consistency before exposing records or retrieving evidence.

  1. User
  2. Workspace
  3. Knowledge base
  4. Documents
  5. Chunks
RAGDocs overview with separate workspace cards for document collections
Workspaces provide the organizational boundary for knowledge bases and documents.

Authentication identifies the user; authorization determines which workspace operations they can perform. The authorization module ↗ enforces those membership and role checks.

Engineering / The product experience

Make the system’s state
visible to the person using it.

The separate React and TypeScript application coordinates authentication, workspaces, uploads, and search through a shared Axios client and TanStack Query.

Sessions that can be revoked.

JWT access tokens identify API requests. Refresh sessions are hashed in the backend, rotated on refresh, and revocable. The frontend attempts a refresh after ordinary API calls return 401 and clears the session if refreshing fails.

Know when indexing is ready.

Redis publishes document status events that FastAPI relays over authorized WebSockets. Query refreshes and status endpoints remain useful because Redis Pub/Sub is transient.

RAGDocs sign-in interface with email and masked password fields
The React application provides the authenticated entry to the workspace workflow.

Observe before claiming improvement.

Query traces record strategy, latency, source counts, model usage, and errors. This makes the system easier to inspect and gives future experiments a starting point. Runtime telemetry does not establish retrieval accuracy.

Trade-offs & next questions

A useful system.
Plenty left to investigate.

I prioritized an understandable retrieval pipeline: scoped search, page-aware ingestion, background processing, rank fusion, a lightweight reranker, and evidence-aware generation. The next step is to measure those choices with controlled experiments.

Current boundaries

  • The reranker is a heuristic, not a neural cross-encoder.
  • There is no complete benchmark platform or independent claim-verification path.
  • Older chunks may lack page metadata and need reindexing.
  • V3 uses local object storage; Supabase Storage belongs to V2.

Future experiments

  • Evaluate Recall@k, MRR, or nDCG on labeled queries.
  • Compare retrieval strategies and chunking choices.
  • Review evidence relevance and answer faithfulness.
  • Investigate scanned PDFs, tables, and neural reranking.

The most valuable part was the progression of questions: how to keep context scoped, preserve useful document structure, combine search signals, and make a generated answer traceable.