Portfolio Projects ยท Project 1 of 10

Project 1: RAG Chatbot over PDFs with Citations

๐Ÿ› ๏ธ Beginnerโฑ 1โ€“2 weekends๐Ÿ“„ Resume lines included

The most asked-about GenAI project in interviews. You'll build a chatbot that answers questions from a set of PDFs (policies, manuals, research papers), cites its sources and says "I don't know" when the answer isn't there. Then you'll measure its quality.

What you'll build

Architecture

PDFs โ†’ loader (PyPDF / unstructured) โ†’ splitter (800 tokens, 100 overlap) โ†’ embeddings โ†’ FAISS/Chroma
User question โ†’ retriever (k=8) โ†’ re-ranker (top 4) โ†’ prompt with context + rules โ†’ LLM โ†’ answer + citations
                                                          โ†˜ logs (question, chunks, answer, latency)

Tech stack

Python, LangChain (LCEL), FAISS or Chroma, bge-small or OpenAI embeddings, an LLM (OpenAI / Groq / Ollama), FastAPI, Streamlit, RAGAS.

Step by step

  1. Ingest: load the PDFs with page numbers in the metadata; clean headers and footers.
  2. Chunk: a recursive splitter, about 800 tokens with 100 overlap; keep source and page.
  3. Embed and index: build FAISS and persist it to disk.
  4. Retrieve: similarity search with k=8, then a cross-encoder re-ranker down to the top 4.
  5. Prompt: "Answer only from the context, cite [file:page], otherwise say you don't know."
  6. Serve: a FastAPI /chat endpoint with streaming; Streamlit as the front end.
  7. Evaluate: write 30 Q&A pairs, then compute recall@k and RAGAS faithfulness and answer relevancy.
  8. Iterate: try chunk sizes, hybrid search and re-ranking; record the scores in a table.

Starter code

from langchain_community.document_loaders import PyPDFDirectoryLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

docs = PyPDFDirectoryLoader("data/").load()
chunks = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=100).split_documents(docs)
db = FAISS.from_documents(chunks, HuggingFaceEmbeddings(model_name="BAAI/bge-small-en-v1.5")); db.save_local("index")
retriever = db.as_retriever(search_kwargs={"k": 4})
prompt = ChatPromptTemplate.from_template(
  "Answer ONLY from the context. Cite sources as [file:page]. If unsure, say \"I don't know\".\n\n{context}\n\nQ: {q}")
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
def ask(q):
    ctx = retriever.invoke(q)
    c = "\n\n".join(f"[{d.metadata['source'].split('/')[-1]}:{d.metadata.get('page', 0) + 1}] {d.page_content}" for d in ctx)
    return llm.invoke(prompt.format(context=c, q=q)).content

Deploy it

Put it on your resume

(Use your own real numbers from the evaluation step.)

Interview questions you'll get

Practise these in Mock Interview โ†’ RAG & Retrieval.

Stretch goals

Created by Ajeetkumar