Chapter 1: Building a Document Q&A System
Project Overview
Build a complete PDF Q&A system with:
- PDF upload and processing
- Chat interface with history
- Source citations
Architecture
Step 1: Setup
# requirements.txt
langchain==0.1.0
openai==1.3.0
chromadb==0.4.0
streamlit==1.28.0
pypdf==3.17.0
Step 2: Document Processing
# document_processor.py
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
def process_pdf(pdf_path, collection_name="docs"):
# Load
loader = PyPDFLoader(pdf_path)
documents = loader.load()
# Split
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
chunks = splitter.split_documents(documents)
# Embed and store
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(
chunks,
embeddings,
collection_name=collection_name,
persist_directory="./chroma_db"
)
return vectorstore
Step 3: RAG Chain
# rag_chain.py
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
def create_qa_chain(vectorstore):
llm = ChatOpenAI(model="gpt-4", temperature=0)
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True,
output_key="answer"
)
chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
memory=memory,
return_source_documents=True
)
return chain
Step 4: Streamlit UI
# app.py
import streamlit as st
from document_processor import process_pdf
from rag_chain import create_qa_chain
st.title("📄 Document Q&A")
# File upload
uploaded_file = st.file_uploader("Upload PDF", type="pdf")
if uploaded_file:
# Process PDF
with st.spinner("Processing..."):
vectorstore = process_pdf(uploaded_file)
chain = create_qa_chain(vectorstore)
# Chat interface
if "messages" not in st.session_state:
st.session_state.messages = []
for msg in st.session_state.messages:
st.chat_message(msg["role"]).write(msg["content"])
if prompt := st.chat_input("Ask about your document"):
st.session_state.messages.append({"role": "user", "content": prompt})
st.chat_message("user").write(prompt)
response = chain({"question": prompt})
answer = response["answer"]
st.session_state.messages.append({"role": "assistant", "content": answer})
st.chat_message("assistant").write(answer)
# Show sources
with st.expander("Sources"):
for doc in response["source_documents"]:
st.write(f"Page {doc.metadata.get('page', 'N/A')}")
st.write(doc.page_content[:200] + "...")
Running the App
streamlit run app.py
Enhancements
- Add authentication
- Support multiple PDFs
- Export conversations
- Add reranking