Step-by-step builds

RAG Chatbot Project: Build a Chatbot on Your Own PDFs in Python

Satvik ParamkushamUpdated October 5, 202611 min read

Quick answer

A RAG chatbot project answers questions from your own documents. You split PDFs into chunks, turn each chunk into an embedding, find the chunks closest to the user's question, and send them to an LLM with the question. The result is a chatbot that cites your data instead of guessing, buildable in Python in a weekend.

A RAG chatbot is the most asked-about AI project for final-year students and freshers right now, and for good reason. It uses the same pattern companies use to put AI on top of their internal documents, it runs on a normal laptop, and it gives you plenty to talk about in a viva or an interview.

This guide builds a working version in about 120 lines of Python: a chatbot that answers questions from any set of PDFs, such as your college syllabus, a company policy handbook or a set of research papers. You will see every step, the code for each, and the upgrades that turn a weekend demo into a strong final-year project.

If the term RAG is new to you, spend five minutes on what RAG is first. Everything below will make more sense.

What is a RAG chatbot project?

A RAG chatbot project is a chatbot that looks up relevant passages from a document collection before it answers, then asks a language model to answer using only those passages. RAG stands for retrieval-augmented generation.

A plain chatbot built on an LLM can only use what the model learned during training. Ask it about your college's attendance rules and it will either refuse or, worse, invent a confident answer. That second problem is called AI hallucination.

RAG fixes this with a simple idea: retrieve first, then generate. The chatbot finds the three or four most relevant chunks of your documents, pastes them into the prompt, and tells the model to answer from that context. When the answer is not in the documents, a well-built RAG bot says so.

What do you need to build a RAG chatbot?

You need basic Python, a laptop with about 8 GB of RAM, and an API key for any chat model. No GPU is required.

PartWhat we useWhy
LanguagePython 3.10 or newerEvery library here supports it
PDF readingpypdfPure Python, installs in seconds
Embeddingssentence-transformers (all-MiniLM-L6-v2)Free, runs on CPU, small download
SearchNumPy cosine similarityNo database to set up for a first version
Answer generationAny chat model APIThe example uses the OpenAI Python SDK; Gemini, Groq or a local model work the same way
InterfaceStreamlitA chat UI in about 20 lines

Skills you should be comfortable with before you start:

  • Writing functions and lists in Python
  • Installing packages with pip and using a virtual environment
  • Reading an error message and searching it

If you are shaky on how models read text, tokens and the context window are the two ideas that explain why we split documents into chunks at all.

How does a RAG chatbot work? (architecture)

A RAG chatbot works in two phases: an indexing phase you run once, and a question phase that runs for every message.

Indexing (once per document set):

  1. Load the PDFs and extract text.
  2. Split the text into overlapping chunks of a few hundred words.
  3. Convert each chunk into an embedding, a list of numbers that captures its meaning.
  4. Store the chunks and their embeddings.

Answering (every question):

  1. Embed the user's question with the same model.
  2. Find the chunks whose embeddings are most similar to the question.
  3. Build a prompt with those chunks plus the question.
  4. Send it to the LLM and show the answer with its sources.

For a few hundred pages, a NumPy array is fast enough to search. Once you have thousands of documents, you swap it for a vector database such as Chroma, FAISS or pgvector. The rest of the code stays the same, which is a nice point to make in a viva.

How to build a RAG chatbot in Python, step by step

  1. Step 1. Set up the project

    Create a folder, a virtual environment and install four packages.

    mkdir rag-chatbot && cd rag-chatbot
    python -m venv .venv
    # Windows: .venv\Scripts\activate    macOS/Linux: source .venv/bin/activate
    pip install pypdf sentence-transformers numpy openai streamlit

    Make a docs folder and drop two or three PDFs into it. Start small: your syllabus, an exam rule book or a few research papers. Set your API key as an environment variable named OPENAI_API_KEY (or the variable your provider uses), never inside the code.

  2. Step 2. Load the PDFs and split them into chunks

    Chunking decides what the retriever can find, so it matters more than most beginners expect. Chunks that are too big bury the answer; chunks that are too small lose context. Around 200 to 300 words with a little overlap is a good start.

    # rag.py
    from pathlib import Path
    from pypdf import PdfReader
    
    def load_chunks(folder="docs", size=250, overlap=50):
        chunks = []
        for pdf in Path(folder).glob("*.pdf"):
            reader = PdfReader(pdf)
            for page_no, page in enumerate(reader.pages, start=1):
                words = (page.extract_text() or "").split()
                step = size - overlap
                for start in range(0, max(len(words), 1), step):
                    text = " ".join(words[start:start + size])
                    if text.strip():
                        chunks.append({"text": text, "source": pdf.name, "page": page_no})
        return chunks

    Each chunk keeps its file name and page number. That small detail lets your chatbot show sources, which examiners and interviewers love.

  3. Step 3. Create embeddings for every chunk

    An embedding model turns text into a vector so that similar meanings sit close together. all-MiniLM-L6-v2 is small, free and runs on a CPU.

    import numpy as np
    from sentence_transformers import SentenceTransformer
    
    embedder = SentenceTransformer("all-MiniLM-L6-v2")
    
    def build_index(chunks):
        texts = [c["text"] for c in chunks]
        vectors = embedder.encode(texts, normalize_embeddings=True, show_progress_bar=True)
        return np.array(vectors)

    Setting normalize_embeddings to True means a plain dot product now equals cosine similarity, which keeps the search code to one line. For a few hundred pages this step takes under a minute on a normal laptop.

  4. Step 4. Retrieve the most relevant chunks

    Retrieval embeds the question and picks the chunks with the highest similarity score.

    def retrieve(question, chunks, vectors, k=4):
        q = embedder.encode([question], normalize_embeddings=True)[0]
        scores = vectors @ q
        best = np.argsort(scores)[::-1][:k]
        return [(chunks[i], float(scores[i])) for i in best]

    Print the retrieved chunks while you test. Most "the chatbot gave a wrong answer" bugs are really "the retriever fetched the wrong chunk" bugs, and you can only see that by looking at what was retrieved.

  5. Step 5. Build the prompt and call the LLM

    The prompt does two jobs: it hands the model the retrieved context, and it tells the model to stay inside that context.

    import os
    from openai import OpenAI
    
    client = OpenAI()  # reads OPENAI_API_KEY from the environment
    MODEL = os.environ.get("LLM_MODEL", "gpt-4o-mini")  # use any current chat model from your provider's docs
    
    def answer(question, chunks, vectors):
        hits = retrieve(question, chunks, vectors)
        context = "\n\n".join(
            f"[{c['source']} p.{c['page']}] {c['text']}" for c, _ in hits
        )
        prompt = (
            "Answer the question using only the context below. "
            "If the answer is not in the context, say you could not find it. "
            "Mention the source in brackets.\n\n"
            f"Context:\n{context}\n\nQuestion: {question}"
        )
        reply = client.chat.completions.create(
            model=MODEL,
            messages=[{"role": "user", "content": prompt}],
            temperature=0,
        )
        return reply.choices[0].message.content, hits

    Temperature 0 keeps answers focused and repeatable, which is what you want when the job is to report facts from a document. Read what temperature does if you want to explain that choice.

  6. Step 6. Add a chat interface with Streamlit

    Streamlit turns the functions above into a web app you can demo in a browser.

    # app.py
    import streamlit as st
    from rag import load_chunks, build_index, answer
    
    st.title("Ask my documents")
    
    @st.cache_resource
    def setup():
        chunks = load_chunks()
        return chunks, build_index(chunks)
    
    chunks, vectors = setup()
    
    if question := st.chat_input("Ask a question about your PDFs"):
        st.chat_message("user").write(question)
        text, hits = answer(question, chunks, vectors)
        with st.chat_message("assistant"):
            st.write(text)
            with st.expander("Sources"):
                for c, score in hits:
                    st.caption(f"{c['source']}, page {c['page']} (score {score:.2f})")

    Run it with streamlit run app.py. The cache_resource decorator means the PDFs are embedded once, not on every message.

  7. Step 7. Test it and measure accuracy

    A project with numbers beats a project with screenshots. Write 20 questions whose answers you know are in the documents, plus 5 whose answers are not.

    For each question, record three things in a simple spreadsheet:

    1. Retrieval hit: did the right chunk appear in the top 4?
    2. Answer correct: was the final answer right?
    3. Refusal correct: for the 5 trick questions, did the bot say it could not find the answer?

    Your results line in the report then reads something like "retrieval hit rate 18 of 20, answer accuracy 17 of 20, correct refusals 5 of 5". Those three numbers are worth more in a viva than any amount of description, and they point straight at what to improve next.

RAG chatbot project ideas you can build next

The same code becomes a different project the moment you change the documents and the user. Pick one that matches your branch or the job you want:

  • College helpdesk bot: answers questions from the academic calendar, exam rules and hostel handbook.
  • Legal or policy assistant: answers from RTI rules, labour laws or a company HR policy, always with page citations.
  • Research paper assistant: compares methods across ten papers in your domain.
  • Product manual bot: answers support questions from a device manual, a classic customer-support use case.
  • Agriculture advisory bot: answers from crop advisories in English and an Indian language.
  • Medical guideline lookup: answers from public treatment guidelines, with a clear "not medical advice" warning.

For final-year depth, add one serious upgrade: a vector database, hybrid search (keyword plus embeddings), a re-ranking step, multilingual embeddings, or a comparison of chunk sizes with results in a table. If you want the bot to take actions, not just answer, that is the jump to an AI agent project.

How to explain your RAG project in a viva or interview

Explain it in one sentence first, then go deeper only when asked. A strong opening line: "My project answers questions from a set of PDFs by retrieving the most relevant passages with embeddings and asking an LLM to answer only from them, which reduces hallucination and shows sources."

Then be ready for these follow-ups, which come up again and again:

  • Why did you choose that chunk size, and what happens if you change it?
  • Why RAG and not fine-tuning?
  • How do you know the answers are correct? (Show your test numbers.)
  • What happens when the answer is not in the documents?
  • How would you scale this to a million documents?

Our RAG interview questions page has model answers to each of these and 20 more.

Common mistakes in student RAG projects

Most weak RAG projects fail in the same five places:

  1. Scanned PDFs with no text. pypdf returns empty strings for scanned images. Check that extract_text returns words, or add OCR.
  2. No overlap between chunks. Answers that cross a chunk boundary get cut in half.
  3. Different embedding models for documents and questions. Both must use the same model, or similarity scores mean nothing.
  4. No refusal instruction. Without "say you could not find it", the model fills gaps with guesses.
  5. No evaluation. "It works" is not a result. Twenty test questions with a score is.

Fix these and your project will already be ahead of most submissions.

Frequently asked questions

Is a RAG chatbot a good final-year project?

Yes, if you go beyond the basic demo. Add an evaluation with test questions, one real upgrade such as a vector database or re-ranking, and a clear use case. Examiners reward a working demo with measured results and your ability to explain each design choice.

Can I build a RAG chatbot for free?

Mostly. The embedding model, search and interface in this guide are free and run on a laptop. The answer step needs a chat model; several providers offer free tiers with limits, or you can run a small open-source model locally if your machine can handle it.

Do I need LangChain or LlamaIndex to build a RAG chatbot?

No. Frameworks save time on bigger projects, but building the pipeline yourself first means you understand chunking, embeddings and retrieval. Many interviewers prefer hearing that you built it from scratch and then know what a framework would replace.

What is the difference between a chatbot and a RAG chatbot?

A plain chatbot answers only from what the model learned in training. A RAG chatbot first retrieves passages from your documents and answers from them, so it can handle private or recent information and show where each answer came from.

Which vector database should I use for a student RAG project?

For a few hundred pages, a NumPy array is enough. When you need persistence or more data, Chroma and FAISS are free and easy to run locally, and pgvector suits projects that already use PostgreSQL.

References