Multilingual-Customer-Support-RAG Is What a Real RAG Backend Looks Like

A decoupled FastAPI and Streamlit system that hydrates its vector index on startup, trims its own conversation memory, and keeps answers pinned to source pages.

8 min read • View on GitHub • More from thevaibhavsengar

A wide service desk split into two layers. A polite front-end desk handles incoming questions on the left, while a control room on the right routes documents through retrieval, indexing, and citation receipts before sending answers back. The image explains that the UI is only the front door, not the system itself.
The defining move here is architectural. Streamlit is the counter, not the brain.
Key Takeaways

A RAG App That Refuses to Behave Like a Demo

The basic promise is simple: upload PDFs, ask questions, get cited answers. The interesting part is that the repository refuses to collapse that experience into one UI file. It separates the client, the API, and the retrieval engine, which makes the whole thing feel like a small production system instead of a notebook with a chat box.

That design choice matters because support is where RAG breaks first. If the retrieval path is messy, the memory is bloated, or the UI owns too much logic, hallucinations become hard to trace and harder to fix. This repo keeps those concerns apart on purpose.

The Real Boundary Is Between the UI and the Brain

The architecture is clean because it is split into responsibilities. The UI submits questions, the backend retrieves context, and the answer path stays inside one service boundary.

LayerTypical demoThis repo
UIMonolithic Streamlit appStreamlit client over HTTP
BackendUI-driven or implicitFastAPI service boundary
RetrievalUsually embedded in the pageFAISS hydrated by the API
MemoryChat history in the widgetTrimmed custom memory object
GroundingBest effortPrompted to use only retrieved context
FitPrototypeSmall production-shaped support system

The split is not cosmetic. Streamlit handles interaction, FastAPI owns the service contract, and the backend can in principle serve other frontends without changing the retrieval logic. That is a real architectural line, not just code organization.

How the Answer Path Stays Grounded

The core orchestration lives in rag/chain.py and api/main.py. A question comes in, retrieval returns the most relevant chunks, conversation history is pulled from custom memory, and the prompt is assembled with a hard instruction to answer using only the supplied context. The backend then returns both the answer and source metadata so the UI can surface page-level citations.

def ask(question):
    docs = retriever.get_relevant_documents(question)
    history = memory.get()
    prompt = build_prompt(context=docs, history=history, question=question)
    answer = llm.invoke(prompt)
    return {
        "answer": answer,
        "sources": extract_sources(docs)
    }

That sequence sounds ordinary until you notice what is missing. There is no open-ended agent loop, no free-form tool use, and no invitation for the model to improvise beyond the retrieved pages. The system is designed to be narrow on purpose.

A close-up mechanism shows a user question entering from the left, document chunks sliding in from below, and a small strip of conversation history being trimmed before reaching a prompt assembler. The final prompt is clamped shut with a strict context-only constraint, explaining how the system keeps answers grounded.
The answer path is a controlled assembly line. Retrieval, memory, and prompt constraints all meet before the model sees the question.

Why the Memory Is So Small on Purpose

class ConversationMemory:
    def __init__(self, max_messages=10):
        self.max_messages = max_messages
        self.messages = []

    def add(self, message):
        self.messages.append(message)
        self._trim()

    def _trim(self):
        self.messages = self.messages[-self.max_messages:]

This is not sophisticated memory. That is the point. The repository keeps the history window intentionally short so the prompt stays legible and the retrieval context does most of the work. For support, that is usually a better trade than letting conversation history accumulate until the model is forced to juggle too much.

The Fast Path From Startup to First Answer

The FAISS lifecycle is where the repo starts to look operational. On startup, the API checks whether an index already exists, loads it if present, and hydrates the chain immediately. On upload, the system persists documents, splits them, indexes them, and makes them queryable without requiring a separate rebuild flow.

EventWhat happensWhy it matters
API bootExisting index is checked and loadedFirst query can be served immediately
Document uploadFiles are saved, split, and indexedIngestion and serving stay in one path
Query timeRetriever hits local FAISSLatency stays low and behavior is predictable
Growth pathLocal vector store firstManaged storage can come later if scale demands it

That is the most production-minded part of the codebase. It favors a fast path that is easy to reason about now, while leaving a clear migration path if the dataset outgrows local FAISS.

What It Chooses Over Bigger, Flashier Alternatives

Compared with a monolithic Streamlit RAG demo, this project is much easier to extend. Compared with a fully managed enterprise stack, it is lighter, cheaper, and easier to understand. Compared with a privacy-first local assistant, it gives up some offline control in exchange for a cleaner service boundary and Gemini-backed speed.

OptionStrengthTrade-off
Monolithic Streamlit demoFast to buildHard to maintain and scale
Local-first support assistantPrivacy and offline controlMore runtime and model complexity
Managed enterprise RAGScale and governanceMore moving parts and vendor weight
This repoClean modular baselineBest suited to smaller to medium datasets

The repo is not trying to win a feature contest. It is proving that a compact support assistant can still behave like software you would keep around.

Why the Test Suite Matters More Than the UI

The test directory is the strongest maturity signal in the project. RAG systems usually fail in the boring places: chunking, retrieval quality, index loading, and step logic. Covering those paths says the author is thinking about regressions, not just demos.

That matters because AI projects age quickly when the first working version is treated as the final version. Tests are how this repository keeps its shape as the pipeline changes.

The Blueprint It Leaves Behind

The useful lesson here is not that RAG can answer questions about PDFs. It is that support RAG becomes much more credible when the stack is deliberately small, modular, and citation-conscious. This repo shows a path from prototype to service-shaped system without pretending the problem has already been solved at enterprise scale.

It is best read as a blueprint for teams that want maintainability first. Once the dataset or traffic grows beyond local FAISS, the design still leaves room to swap parts without tearing down the whole system.