Recall: AI Knowledge Assistant
Production · Personal project, live on Google Cloud · Engineer, end-to-end design and implementation
Documents and voice notes are scattered across files. Finding an answer, checking its source and turning it into a useful study session or task takes repeated manual work.
On this page
Overview
I built Recall to turn a collection of documents and voice notes into a searchable study workspace. The Next.js frontend supports cited answers, document explanations, practice quizzes and an agent that proposes tasks or drafts. Behind it, FastAPI coordinates uploads, asynchronous ingestion, retrieval, streaming and billing on Google Cloud.
Problem
A chat interface is only useful if its answers can be checked. Documents need page references, voice notes need searchable transcripts, and the retrieval layer must keep each user inside their own library.
The backend also has to handle work that outlives a browser request: processing uploads, recovering from duplicate events and pausing an agent until a person approves a write. Those states belong in the database, with explicit transitions and failure paths.
Requirements
- Upload documents and audio directly to storage with scoped, temporary access
- Extract, transcribe and index uploads without blocking the browser
- Retrieve only the authenticated user's content and cite the passages used
- Support document explanations and quizzes alongside strict source-based answers
- Persist agent progress and require approval before creating tasks or saving drafts
- Apply plan-aware limits and grant paid access only after verified payment
Architecture
The Next.js client sends authenticated requests to FastAPI. The API creates a pending file record and signs a browser-to-Cloud-Storage upload. Eventarc invokes a private Cloud Run worker, which claims the file, extracts document text or transcribes audio, then chunks and embeds it with Vertex AI. Chunks, voice-note summaries and ready status commit together in Neon PostgreSQL. Questions combine pgvector similarity search with PostgreSQL full-text search before Gemini streams an answer. Agent steps and pending approvals are persisted separately; signed Cashfree webhooks update paid access.
- UploadV4 signed URL straight to Cloud Storage
- TriggerEventarc on object finalise
- ClaimAtomic claim with a 15-minute lease
- ExtractDocuments, scanned PDFs and voice notes
- ChunkSplit for retrieval
- EmbedGemini embeddings
- Indexpgvector HNSW + full-text
{ "status": "skipped" }Engineering decisions
- PostgreSQL for both retrieval methods
- pgvector and full-text search keep chunks, ownership and retrieval in one database. This avoids a second index to synchronise, while leaving room to evaluate a reranker if paraphrase retrieval remains weak.
- Recover abandoned claims with a lease
- Acknowledging every duplicate would strand a file after a worker crash. A live claim asks Eventarc to retry; an expired claim can be reclaimed. Attempts are bounded so a repeatedly crashing file eventually fails.
- Derive identity outside the model
- The API verifies Firebase identity and supplies the user ID to search and agent tools. The model cannot choose another user through tool arguments. File and chat lookups also check ownership.
- Persist approval rather than waiting in memory
- Human decisions can outlast an HTTP request. The database holds the pending write; an atomic claim prevents repeated approval clicks from executing the same pending action twice. This is not a blanket guarantee of recovery from every mid-tool crash.
- Treat payment notifications as repeatable events
- The backend verifies the webhook signature and fetches the order from Cashfree. Recording the payment ID and extending access happen in one transaction, so a redelivery cannot grant the same pass twice. A browser redirect never grants access.
Challenges
Duplicate delivery and worker crashes
Duplicate events must be harmless without hiding unfinished work. Conditional claims, retryable live-lease responses and transactional indexing handle those different states explicitly. End-to-end crash recovery still needs a fault-injection check on Cloud Run.
Retrieval inside a private document
A vector index can find nearby candidates before applying ownership or file filters, leaving too few results. Transaction-scoped HNSW iterative scans keep looking for matching rows, and the query sorts candidates by distance before fusion.
Different source formats, different evidence
Scanned PDFs use OCR when ordinary extraction finds little text. Office formats need conversion or dedicated extraction. Voice notes can be transcribed and summarised, but the current transcript has no timestamps, so citations identify the note rather than an invented time offset.
A passing answer can hide weak retrieval
The small local evaluation shows weaker retrieval for paraphrased questions and variation in generated answers. A voice note phrased as a question can also steer the response. These observations call for a larger mixed corpus and separate retrieval and answer checks before claiming broad accuracy.
Results
Qualitative outcomes; benchmark measurements are not published.
- A live Next.js application backed by FastAPI and event-driven processing on Google Cloud.
- Documents and transcribed voice notes share searchable content with source references in streamed answers.
- Document explanations, practice quizzes and voice-note summaries support study workflows around the same library.
- Agent writes require an explicit decision, with pending approvals and tool history stored in PostgreSQL.
- Verified, deduplicated payment events control prepaid access; model usage is checked against each user's plan.
Lessons learned
- A useful AI product needs explicit ownership, state and failure handling around every model call.
- At-least-once events need both duplicate handling and a way to recover abandoned work.
- Source citations make answers inspectable; they do not prove every generated claim is correct.
- Measure retrieval separately from generation, and repeat evaluations before interpreting an answer score.
- Start with one database and a small explicit agent loop; add retrieval infrastructure when evaluation justifies it.
Technical approach and performance notes
Technical approach
Separate upload coordination from processing
The API validates file type and declared size, creates the database record and returns a signed URL with required headers. Storage enforces the signed content type, exact size and create-only precondition. The worker handles extraction after the upload completes.
Make ingestion state explicit
A conditional database update claims a pending file or an expired processing lease. A live claim returns a retryable response; completed files are acknowledged without reprocessing. Successful ingestion commits chunks, the optional voice-note summary and ready status in one transaction.
Share an index across documents and voice notes
Document pages and audio transcripts use the same chunks table. Vector and full-text rankings are combined with reciprocal rank fusion. The results retain file names, content kind and page references so the answer can identify its sources.
Stream with explicit evidence
The browser reads authenticated POST streams with fetch. Strict answer mode prompts Gemini to use numbered sources and refuse unsupported facts. Explain mode permits related background, labelled separately from document evidence. Quiz answers stay on the server until submission.
Keep agent execution on the server
A bounded function-calling loop executes read tools and saves each step. A write tool becomes a persisted pending approval and ends the current request. A later approval request claims that step and continues from stored history, including the model call metadata needed for replay.
Performance and scalability
- File bytes travel directly to Cloud Storage; extraction and model calls happen asynchronously in the worker.
- HNSW vector retrieval and PostgreSQL full-text indexes produce bounded candidate sets before rank fusion.
- Streaming exposes progress while answers are generated; it does not eliminate model or retrieval latency.
- Per-user usage limits and bounded agent steps constrain model work. These controls are not a published load or cost benchmark.
- The API and worker can be deployed independently. Larger-corpus retrieval, concurrent traffic and worker crash recovery need further validation.
Technologies used
- Python
- FastAPI
- PostgreSQL / pgvector
- Next.js / TypeScript
- Cloud Run
- Eventarc
- Vertex AI (Gemini)
- Firebase Auth
- Cashfree