A retrieval-augmented generation system that lets you semantically search and ask questions across a corpus of news articles — with FAISS for vector search, LangChain for orchestration, and a Streamlit UI.
Built with an emphasis on making the answers trustworthy, not just fluent: every response comes with source citations, a transparent confidence score, and a guardrail that refuses to answer when nothing in the corpus is actually relevant.
- Semantic search over a corpus of news articles using FAISS
- RAG question answering grounded in retrieved excerpts, orchestrated with LangChain
- Prompt guardrails — if retrieval similarity is too low, the system says so instead of letting the model improvise from weak context
- Transparent confidence scoring — every answer is labeled High / Medium / Low / Insufficient Evidence based on retrieval strength and agreement across sources
- Runs with zero API keys in mock mode (deterministic hashed embeddings + placeholder generation), so the whole pipeline — retrieval, guardrails, scoring — is testable and demoable without credentials
Streamlit UI (src/app)
│
▼
answer_question() (src/rag/chain.py)
│
├──▶ VectorIndex.search() ──▶ FAISS (src/rag/index.py)
│ │
│ embeddings (src/rag/embeddings.py)
│
├──▶ check_retrieval_guardrail() ──▶ refuse if similarity too low
│
├──▶ score_confidence() ──▶ High / Medium / Low / Insufficient
│
└──▶ LangChain prompt + ChatOpenAI (or mock) ──▶ grounded answer
rag-article-navigator/
├── data/
│ └── articles/ # sample news corpus (JSON, one file per article)
├── src/
│ ├── config.py # env-driven settings
│ ├── rag/
│ │ ├── documents.py # article loading + chunking
│ │ ├── embeddings.py # real (OpenAI) + mock embedding backends
│ │ ├── index.py # FAISS vector index + retriever
│ │ ├── guardrails.py # retrieval guardrail + confidence scoring
│ │ └── chain.py # LangChain-orchestrated RAG answer chain
│ └── app/
│ └── streamlit_app.py # Streamlit UI
├── tests/
├── requirements.txt
├── .env.example
└── README.md
git clone https://github.com/<your-username>/rag-article-navigator.git
cd rag-article-navigator
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txtcp .env.example .env
# Optionally set OPENAI_API_KEY for real embeddings + generation.The app runs in mock mode automatically with no API key: retrieval, guardrails, and confidence scoring all run for real against deterministic hashed embeddings, while generated answers are placeholder text. This makes the whole pipeline runnable and testable with zero credentials.
streamlit run src/app/streamlit_app.pyOpen the local URL Streamlit prints (usually http://localhost:8501). Use the Ask a question tab for grounded Q&A with citations and a confidence score, or the Semantic search tab to browse raw retrieval results.
pytest -vEvery query goes through retrieval first. If the best-matching passage falls below MIN_SIMILARITY_FOR_ANSWER (default 0.35), the system refuses to answer and explains why, rather than asking the model to generate a plausible-sounding response from irrelevant context — the most common way RAG systems quietly hallucinate.
If retrieval clears that bar, a confidence score is still attached to the answer: a weighted blend of the top match's similarity and the average similarity across all retrieved passages, mapped to a high / medium / low / insufficient_evidence label. This means an answer can be allowed but still flagged as low-confidence, giving the product surface (and the user) an honest signal about how much to trust it.
Drop additional JSON files into data/articles/, following the shape:
{
"id": "art-006",
"title": "Article headline",
"source": "Publication name",
"published": "YYYY-MM-DD",
"text": "Full article body as plain text."
}The index rebuilds from the corpus on app startup, so no separate ingestion step is required for a corpus this size.
MIT — see LICENSE.