Skip to main content
ProjectsDocuMind

Project 06 / 12

Document Intelligence Platform

DocuMind

Chat interface for PDFs that answers only from what's actually in the document, no invented citations. An async ingestion pipeline chunks and embeds files in the background so uploads never block the API, and answers stream back over SSE.

documind-web-production.up.railway.app
DocuMind preview
Stack
NestJSpgvectorBullMQRedis
Modules
4
Key decisions
2

01Problem

What was broken

Chatting with a PDF usually goes wrong one of two ways: the model answers from its training data instead of the document, or a heavy synchronous ingestion step freezes the UI while a large file gets parsed and embedded.

02Impact

What it does now

Ingestion runs entirely off the request thread. BullMQ workers handle chunking and embedding in the background while the API stays responsive, and the answer streams back over SSE as soon as the model starts generating.

03Architecture

How it is built

BullMQ workers run ingestion on their own queue, off the Next.js API thread. Chunks land in Supabase Postgres with pgvector, and search there combines keyword and vector matches, so a pasted error string still finds its chunk even when the wording doesn't match.

Modules

  • Async Chunking
  • SSE Stream Engine
  • pgvector Store
  • BullMQ Workers

04Intelligence

Where the AI sits

The model only sees retrieved chunks, nothing from training data. The system prompt tells it to say so when an answer isn't in those chunks instead of guessing.

05Decisions

Trade-offs I chose

  1. 01

    pgvector over Dedicated SaaS DBs

    I picked PostgreSQL with pgvector inside Supabase instead of a separate vector database, so a user's account, their chats and their embeddings live in the same set of tables. A single query can join across all three instead of stitching results from two databases.

  2. 02

    Async Message Queues (BullMQ)

    Parsing a 100-page PDF blocks the JavaScript event loop if it runs inline. Pushing that work to a Redis-backed worker keeps the API answering other requests while a big file is still being chunked.