Case study
Knowledge Workbench
Research with hybrid retrieval and citation-grounded answers
A NotebookLM-style research assistant. Notebooks hold PDFs, articles, YouTube videos and VTT transcripts; answers stream with citations that jump back into the source viewer.
- 01Ingest
- 02Dense + full-text
- 03Fuse + rerank
- 04Cited answer
Query rewrite → Qdrant dense search + Postgres full-text search → reciprocal rank fusion → time diversify → LLM rerank → streamed answer with [n] citations.
- Status
- Public repository
- Demo
- None hosted
- Runtime
- Bun + TanStack Start
- Stores
- PostgreSQL + Qdrant
01Problem
Why this is hard
Research means reading across mixed sources — papers, articles, videos — and an answer is only useful if you can check exactly where it came from.
Each retrieval method misses something on its own: vector search can miss exact terms, full-text search can miss paraphrase. And a citation has to land on the cited material, not just name a document.
02Approach
The shape of the solution
- 01
Every source runs the same pipeline: create, background job, type-specific extract, chunk, embed, write chunks to Postgres and upsert vectors to Qdrant, then mark ready.
- 02
Questions run hybrid retrieval: rewrite the query, search Qdrant (dense) and Postgres full-text (lexical), merge with reciprocal rank fusion, diversify, and rerank with an LLM.
- 03
Answers stream with [n] citation badges that open the source viewer at the cited material.
- 04
Studio artifacts — brief, study guide, comparison, learning roadmap — run as notebook-wide retrieval probes, not per-source features.
03System
How the pieces fit
- Source types
- pdf, text, url, youtube, vtt. Status flows uploading → indexing → ready or failed.
- Ingest
- YouTube caption indexing behind residential proxy checks; playlist import up to 25 videos.
- Progress
- Server-sent events stream live indexing progress to the notebook.
- Auth
- Clerk; notebooks carry an ownerId and mutating server functions check ownership.
04Engineering decisions
What was chosen, and what it cost
- K1
Two stores with explicit ownership
PostgreSQL (with Drizzle) owns text, metadata and chat; Qdrant owns embeddings, with point IDs kept in sync with the Postgres chunks.
- Cost
- Every ingest writes to both stores, so the two must be kept consistent.
- K2
Hybrid retrieval with rank fusion
Dense and lexical results are merged with reciprocal rank fusion, time-diversified, then reranked by an LLM before the answer is generated.
- Cost
- Each question pays for a query rewrite and a rerank call on top of two searches.
- K3
Citations as navigation
Answers carry [n] citation badges that open the source viewer, so a claim can be checked against the material it came from.
- K4
Bun and TanStack Start server functions
Bun is the production runtime; TanStack Start file routes and server functions carry the API, with ownership checks on every mutation.
- Cost
- Background work runs in-process rather than on a durable queue.
05Tradeoffs
Known limits, written down
Background jobs run in-process (setTimeout) and are lost if the process crashes — not Redis, not a durable queue.
Rate limits are in-memory per process.
Analytics are a development-only console track(); there is no analytics vendor.
bun run eval:rag covers two hardcoded timestamp cases. It is not a CI evaluation harness.
Plan-limit and upgrade UI exist; there is no Stripe billing.
06Technology
What it runs on
- Runtime
- Bun
- TypeScript
- App
- TanStack Start
- TanStack Router
- TanStack Query
- React 19
- Tailwind 4
- Data
- PostgreSQL
- Drizzle
- Qdrant
- AI
- OpenAI embeddings
- OpenAI chat
- Auth
- Clerk
- Deploy
- Docker Compose
- Caddy on a VPS
- Qdrant Cloud
07Proof