Skip to main content
Xenors Xenors
Home › Data Science › Project: Document Q&A Bot with RAG, LangChain and Streamlit

Module 13: Advanced Real-World Projects & CapstoneBy Ashok Kumar Yadav4 min read

Project: Document Q&A Bot with RAG, LangChain and Streamlit

This capstone combines three earlier lessons into one portfolio app: upload a PDF, ask questions, and get answers with the source pages cited.

Streamlit chat app answering questions from uploaded PDF documents using RAG
Quick answer: Load the PDF with PyPDFLoader, split it with RecursiveCharacterTextSplitter, embed chunks into a FAISS store, build a retriever, and answer in Streamlit with a prompt that restricts the model to the retrieved context and shows sources.

Project plan and structure

Keep the project small and testable: ingestion, retrieval, generation and UI as separate functions.

  • app.py: Streamlit interface
  • rag.py: loading, chunking, embedding, retrieval, answering
  • requirements.txt: pinned dependencies
  • README.md: screenshots, setup, evaluation results
Install dependencies
# pip install streamlit langchain langchain-community langchain-openai langchain-text-splitters faiss-cpu pypdf

Ingestion and retrieval (rag.py)

Page numbers are kept in metadata so the answer can cite them. LangChain package paths change between versions, so confirm imports in the current documentation.

Index a PDF and answer from retrieved chunks
# rag.py
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate

def build_index(pdf_path):
    pages = PyPDFLoader(pdf_path).load()
    chunks = RecursiveCharacterTextSplitter(chunk_size=900, chunk_overlap=150).split_documents(pages)
    return FAISS.from_documents(chunks, OpenAIEmbeddings())

PROMPT = ChatPromptTemplate.from_messages([
    ("system", "Answer only from the context. If the answer is not in it, say you don't know. "
               "Be concise and mention page numbers."),
    ("human", "Context:\n{context}\n\nQuestion: {question}")])

def answer(index, question, k=4):
    docs = index.similarity_search(question, k=k)
    ctx = "\n\n".join(f"(page {d.metadata.get('page', 0) + 1}) {d.page_content}" for d in docs)
    reply = (PROMPT | ChatOpenAI(model="gpt-4o-mini", temperature=0)).invoke(
        {"context": ctx, "question": question})
    return reply.content, docs

Streamlit interface (app.py)

Cache the index in session state so the PDF is embedded once per upload.

Chat UI with source expander
# app.py   ->  streamlit run app.py
import tempfile, streamlit as st
from rag import build_index, answer

st.title("Ask your document")
up = st.file_uploader("Upload a PDF", type="pdf")
if up and st.session_state.get("name") != up.name:
    with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as f:
        f.write(up.read())
    with st.spinner("Indexing..."):
        st.session_state.index = build_index(f.name)
    st.session_state.name = up.name

q = st.chat_input("Ask a question about the document")
if q and "index" in st.session_state:
    st.chat_message("user").write(q)
    text, docs = answer(st.session_state.index, q)
    st.chat_message("assistant").write(text)
    with st.expander("Sources"):
        for d in docs:
            st.caption(f"Page {d.metadata.get('page', 0) + 1}")
            st.write(d.page_content[:300] + "...")

Evaluate it like a real project

Write 15 questions with known answers and page numbers. Measure how often the right page appears in the top 4 results, and review answers manually for faithfulness. Put those numbers in your README, since measured results make a portfolio project convincing.

Deploy and extend

Deploy on Streamlit Community Cloud or in Docker, with the API key in secrets. Extensions to try: multiple documents, hybrid keyword search, a reranker, conversation memory and an upload size limit.

Hands-on practice

Run the examples in the downloadable Jupyter notebook. The extra checks below focus on mistakes that appear in real projects, not just syntax.

Define a project evaluation record

Practice example
record={'question':'refund window?','expected_source':'policy.pdf','retrieved':['policy.pdf'],'answer_supported':True}
print(record)
{'question': 'refund window?', ... 'answer_supported': True}

Store evaluation evidence with the project so improvements are measurable.

Cache document fingerprints

Practice example
import hashlib
text='example policy text'
print(hashlib.sha256(text.encode()).hexdigest()[:12])
<12-char fingerprint>

Fingerprints help avoid re-embedding unchanged documents.

Self-check

  • Can you explain why the first practice result is trustworthy rather than just plausible?
  • What would you monitor or test before using this RAG Project workflow in production?
  • Which assumption in the example would break first on a larger or messier dataset?

Frequently asked questions

Do I need a paid API for this project?

The example uses OpenAI. You can replace it with a local model through Ollama or Hugging Face to avoid API charges.

How do I stop the bot from making things up?

Restrict answers to the context in the prompt, show sources, and return 'I don't know' when retrieval finds nothing relevant.

Can this handle large PDFs?

Yes, but embed once and persist the index to disk so you do not pay to re-embed on every run.

Primary references

Use these official sources when exact syntax, defaults or definitions matter beyond the examples here.

Written by Ashok Kumar Yadav
Ashok Kumar Yadav writes Xenors tutorials on applied AI, programming and data workflows. Every lesson is built around code you can run and check yourself.
GitHub · LinkedIn

Continue exploring