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.
- The repo treats support-grade RAG as a service boundary problem, not a chatbot demo.
- FastAPI owns ingestion, retrieval, and answer generation while Streamlit stays a thin client.
- Grounding comes from a strict prompt, page-level citations, and short trimmed memory.
- The test suite and modular layout are the strongest evidence that this project was built for maintenance, not spectacle.
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
| Layer | Typical demo | This repo |
|---|---|---|
| UI | Monolithic Streamlit app | Streamlit client over HTTP |
| Backend | UI-driven or implicit | FastAPI service boundary |
| Retrieval | Usually embedded in the page | FAISS hydrated by the API |
| Memory | Chat history in the widget | Trimmed custom memory object |
| Grounding | Best effort | Prompted to use only retrieved context |
| Fit | Prototype | Small 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.
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.
| Event | What happens | Why it matters |
|---|---|---|
| API boot | Existing index is checked and loaded | First query can be served immediately |
| Document upload | Files are saved, split, and indexed | Ingestion and serving stay in one path |
| Query time | Retriever hits local FAISS | Latency stays low and behavior is predictable |
| Growth path | Local vector store first | Managed 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.
| Option | Strength | Trade-off |
|---|---|---|
| Monolithic Streamlit demo | Fast to build | Hard to maintain and scale |
| Local-first support assistant | Privacy and offline control | More runtime and model complexity |
| Managed enterprise RAG | Scale and governance | More moving parts and vendor weight |
| This repo | Clean modular baseline | Best 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.