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.
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
# pip install streamlit langchain langchain-community langchain-openai langchain-text-splitters faiss-cpu pypdfIngestion 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.
# 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, docsStreamlit interface (app.py)
Cache the index in session state so the PDF is embedded once per upload.
# 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
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
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.




















































































































































