Back to postsArnold Lupamo
Aug 5, 2026·8 min read

RAG pipeline

Personal AI companion for vacation rental host

RAG pipeline

While browsing through Upwork (the job posting platform) I came across this interesting job. The client who is a consultant for Vacation rental hosts had posted they wanted an AI engineer who could accumulate scattered knowledge about the hosts business(pricing notes, guest email templates, strategies that worked in past seasons). Since Generic AI chatbots couldn't help because they don't know anything about the host's specific property or history.
The goal: Give every host their own private AI that answers questions grounded strictly in their own uploaded documents and is honest when the answer isn't there, rather than just inventing something plausible-sounding.
This sounded like a fun project to work on and here's how I went about building it:

What I built

A multi-tenant Retrival Augmented Generation (RAG) pipeline, end to end: Source code

Document upload -> Text extraction -> Chunking -> Embedding -> Vector Storage
																	↓
User question -> Retrival -> Reranking -> Grounded Generation -> Answer + Sources

Architecture

LayerChoiceReasoning
APIFastAPI(async)async end to end from HTTP handler to db
DatabasePostgreSQL + pgvectorDatabase for relational and vector data
Embeddingssentence-transformers (all-MiniLM-L6-V2), localRuns on CPU, no per-embedding API cost, fast enough for MVP Scale
GenerationGroqFast Inference, low cost, good for a chat product where latency matters
Multi-tenancyRow-Level isolation (user_id on every table, quey filtered)Simpler and cheaper to operate at this scale than schema or database per tenant

Data Model

users — auth, subscription tier
documents — uploaded files, processing status
chunks — text chunks + embeddings, scoped to user_id and document_id
chats — conversation threads
messages — individual turns, with source document references\

Decisions Worth Highlighting

Code Computes Facts the LLM only narrates; Anywhere the system needs precise number or filtering that logic lives in deterministic code, SQL queries with explicit user_id filters, never left to the model to reasib about correctly. This is what nakes tenant isolation a guarantee rather than a hope.
Honesty over completeness; The system prompt explicly instructs the model to say when an answer isn't in the retrived context, rather than filling the gap with generic advice. This was the hardest thing to get right rather than just working. Most RAG demos quietly let the model pad with plausible-sounding filer when retrival comes up empty. Getting a model to say "I don't have that information" convincingly, without becoming so cautious it hedges on things it does know, took explicit prompt design and testing. Background processing with real failure state; Document ingestion (extract -> chink -> embed -> store) run as a background task after upload, so the API responds immediately. Failures are caught and written back as status: "failed" rather than leaving a document stusk in "processing" forever, a real bug that I caught and fixed during testing, not assumed away.

Validation: What I tested

There are specific behaviours I had to test end-to-end against the running instance.

Testwhat It provesResult
Ask a question with a planted, specific answer in an uploaded doc (a 15% long-stay discount)Retrieval and generation correctly surface real informationModel returned the exact figure and condition, with correct source attribution
Ask a question the uploaded document doesn't coverThe model doesn't hallucinate when it lacks informationModel explicitly stated the information wasn't available, rather than inventing an answer
Ask the same question as a second user with no documents uploadedTenant isolation actually holds under real conditionsSecond user received no information and an empty source list — no leakage from user 1's data

The third test is the one that mattered most for product handling private business data. I verified against a real second user on a real database not just asserting it in a code comment.

Engineering Process

I had some dependency compatiability problem while working on this, my machine being an Intel Mac, and Pytorch dropped x86_64 macOS builds after version 2.2.2 which put it at odds with newer transformers releases that didn't exist for that platform, the fix was pinning the entire ML dependency chain to a mutually compatiable set with the constraint documented directly in requirements.txt for future reference, since it's a local-machine limitation, not a permanent architectural one.

Result

A working, tested, multi-tenant RAG backend: document upload, background ingestion, grounded chat, and verified tenant isolation the foundational layer the rest of the product (calendar analysis, monthly reviews, pricing extraction) builds on top of.