A full-stack RAG knowledge assistant that answers questions about company documents in plain language and cites its sources. Built with a FastAPI backend, a React/Vite frontend, FAISS vector search, and Llama 3.1 for generation. Embeddings run locally via sentence-transformers with SHA-256 content-hash caching, so re-indexing unchanged documents does zero embedding work. Every answer is grounded: the LLM answers only from retrieved chunks and refuses when the documents don't contain the answer.
- Semantic Q&A. Questions are matched to documents by meaning, not keywords: "When do I get paid?" finds the salary policy even though that document says "salaries are transferred."
- Grounded answers with sources. The LLM answers only from retrieved chunks. Every answer lists the files and snippets it used, with similarity scores.
- Relevance-ranked snippets. Source previews show the passage within a chunk most similar to the question, not just the first 200 characters.
- Admin workflow. Upload and edit documents in the browser, then re-index with one click. Content-hash caching means unchanged text is never re-embedded.
- Observability built in. Every query, indexing run, and error is logged. The
/metricsendpoint reports median and p95 latency, cache hit totals, and error counts, and/healthcovers liveness. - Honest failure modes. Empty queries, missing API keys, corrupted files, and unanswerable questions all return clean JSON errors and a friendly UI state instead of hallucinated answers.
documents (.txt)
| chunking: paragraph-based, ~800 chars, 1-paragraph overlap
v
chunks --> sentence-transformers (all-MiniLM-L6-v2, local)
| \- SHA-256 content-hash cache (skip unchanged chunks)
v
FAISS index (cosine similarity) + chunk metadata
v
question --> embed --> top-k retrieval --> LLM (Groq, llama-3.1-8b-instant)
\- answers ONLY from the
retrieved context
v
{ answer, sources[file, snippet, score], latency_ms }
Backend: FastAPI with modular services (chunking, embeddings, vectorstore, llm, metrics) behind thin routes. Frontend: React 19 and Vite, small components, one API module. No paid services required: embeddings run locally, and the LLM uses Groq's free tier. Any OpenAI-compatible endpoint works with a two-line change.
Five sample documents for a fictional company (Northbeam Software) live in backend/data/sample-docs/: company policies, FAQ, project setup guide, onboarding manual, and security basics. New documents can be uploaded from the admin page.
Requirements: Python 3.11+, Node 20+, a free Groq API key.
Backend:
python -m venv venv
source venv/Scripts/activate # Git Bash on Windows
pip install -r requirements.txt
cp .env.example .env # add your GROQ_API_KEY
uvicorn backend.main:app --reload
Frontend (second terminal):
cd frontend
npm install
npm run dev
Open http://localhost:5173, press Reindex on the Admin page once, then ask questions on the Search page. Interactive API docs live at http://127.0.0.1:8000/docs.
Note: the first query after backend start takes about 10 to 13 seconds while the embedding model loads. Queries are sub-second after that.
| # | Metric | Method | Result | Target | Pass |
|---|---|---|---|---|---|
| 1 | Retrieval@3 | 12 manual queries across all docs | 12/12 (100%) | >= 80% | Pass |
| 2 | Answer reference coverage | Same 12 answers, at least 1 valid source each | 12/12 (100%) | >= 90% | Pass |
| 3 | Latency (warm) | Auto-logged; /metrics median and p95 |
median 561.7 ms, p95 1069.0 ms | median < 3s, p95 < 5s | Pass |
| 4 | Embedding cache | Re-index unchanged docs; auto-logged | 14/14 hits, 0 re-embeds | 100% hits | Pass |
| 5 | Failure handling | Empty query / missing key / corrupted file / no docs | 400 / 502 / 400 / 404 with error JSON and UI banner |
4xx/5xx and friendly UI | Pass |
| 6 | Source precision | Spot-check 10 snippets against answers | 10/10 (100%) | >= 80% | Pass |
| 7 | Indexing throughput | Auto-logged duration | 5 files in ~11 ms warm; unchanged files skipped | no errors | Pass |
Notes: source snippets originally showed the first 200 characters of a chunk, which cost one source-precision miss when the supporting sentence sat past the preview. Snippets now show the passage within the chunk most similar to the question (paragraph-level re-ranking with the same embedding model), which resolved the miss. Latency targets are measured warm as the metric specifies; numbers come from the auto-logged /metrics endpoint.
guidely/
|-- backend/
| |-- main.py # app setup, CORS, /health, /metrics
| |-- routes/ # HTTP endpoints (documents, search)
| |-- services/ # chunking, embeddings, vectorstore, llm, metrics
| |-- models/record.py # Pydantic request/response models
| \-- data/sample-docs/ # the document corpus
|-- frontend/
| \-- src/ # App, pages (Search, Admin), components, api.js
|-- requirements.txt
\-- README.md



