Building a Document Q&A Chatbot with RAG
Answering one question about a document is the easy part. A chatbot also gets follow-ups such as which one, and why. This guide builds a document Q&A chatbot over a 48-page PDF with page citations, runs one conversation two ways, and shows how rewriting follow-ups turned a wrong answer into a right one.
GloryTecks Team
Training & Placement Faculty
Last updated: 3 October 2026
"Chat with your documents" is the most requested generative AI feature in companies, and the first version is easy: split a PDF into pieces, find the pieces closest to the question, and ask a model to answer from them. It works well for the first question.
Then a user asks a follow-up. "Which one is the base for the others?" makes sense to a person who remembers the conversation. To a search index, "which one" means nothing. This guide builds a small document chatbot, runs the same conversation two ways, and shows where the naive version fails.
The document
The example uses the NIST AI Risk Management Framework, a 48-page PDF published by the US National Institute of Standards and Technology (NIST: AI RMF 1.0). NIST released it on 26 January 2023 for voluntary use and is revising it (NIST: AI Risk Management Framework). It suits the test: real, dense prose with figures, lists and cross-references.
The chatbot
Four steps:
- Ingest. Extract text page by page with pypdf and split each page into overlapping chunks that remember their page number. Extraction works only for PDFs that contain text; scanned pages may yield nothing without OCR (pypdf: extract text).
- Retrieve the three chunks closest in meaning to the search query.
- Answer from those chunks only, citing pages, or say the document does not cover it.
- Remember the conversation: the model sees the earlier turns.
The script runs the same five-turn conversation twice. The first run searches with the user's words as typed. The second asks the model to rewrite each follow-up as a standalone question before searching.
import json
import re
import urllib.request
from pypdf import PdfReader
from sentence_transformers import SentenceTransformer
URL = "http://localhost:11434/api/chat"
OPTIONS = {"temperature": 0, "seed": 42, "num_ctx": 4096, "num_predict": 150}
# 1. Ingest: split each PDF page into ~120-word chunks that remember their page number
pages = [" ".join((p.extract_text() or "").split()) for p in PdfReader("nist-ai-rmf.pdf").pages]
chunks = []
for number, text in enumerate(pages, 1):
words = text.split()
for start in range(0, len(words), 100): # 20 words of overlap
chunks.append({"page": number, "text": " ".join(words[start:start + 120])})
embedder = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
vectors = embedder.encode([c["text"] for c in chunks], normalize_embeddings=True)
print(f"{len(pages)} pages -> {len(chunks)} chunks")
def retrieve(query, k=3):
scores = vectors @ embedder.encode([query], normalize_embeddings=True)[0]
return [chunks[i] for i in scores.argsort()[::-1][:k]]
def chat(messages):
body = {"model": "llama3.2", "messages": messages, "stream": False, "options": OPTIONS}
req = urllib.request.Request(URL, json.dumps(body).encode(), {"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=900) as r:
return json.load(r)["message"]["content"].strip()
def standalone(question, history):
# 2. Rewrite a follow-up into a question that makes sense on its own
if not history:
return question
recent = "\n".join(f"{role}: {text}" for role, text in history[-4:])
return chat([{"role": "user", "content":
f"Conversation so far:\n{recent}\n\nRewrite this follow-up as one standalone question. "
f"Reply with the question only.\nFollow-up: {question}"}])
def answer(question, found, history):
# the model sees the conversation in both runs; only the search query differs
context = "\n\n".join(f"[page {c['page']}] {c['text']}" for c in found)
past = [{"role": role, "content": text} for role, text in history]
return chat([{"role": "system", "content":
"Answer from the excerpts only, in at most two sentences, and cite pages like (page 5). "
"If the excerpts do not contain the answer, reply: Not found in the document."},
*past,
{"role": "user", "content": f"Excerpts:\n{context}\n\nQuestion: {question}"}])
# 3. One conversation, run twice: retrieving with the raw question, and with the rewritten one
TURNS = [("What are the four functions of the AI RMF Core?", ["govern", "map", "measure", "manage"]),
("Which of them is described as cross-cutting?", ["govern"]),
("What characteristics of trustworthy AI does the framework list?", ["valid", "reliable", "fair"]),
("Which one is shown as the base for the others?", ["valid"]),
("What fines does the EU AI Act set for violations?", ["not found"])]
for mode in ("raw question", "rewritten question"):
print(f"\n=== retrieval with the {mode}")
history, right = [], 0
for question, needed in TURNS:
query = standalone(question, history) if mode == "rewritten question" else question
found = retrieve(query)
reply = answer(question, found, history)
ok = all(term in reply.lower() for term in needed)
right += ok
history += [("user", question), ("assistant", reply)]
print(f"Q: {question}")
if query != question:
print(f" searched for: {query}")
print(f" pages {[c['page'] for c in found]} -> {' '.join(reply.split())[:170]}")
print(f" {'OK' if ok else 'MISSED'}")
print(f"{right} of {len(TURNS)} answered as expected")48 pages -> 186 chunks
=== retrieval with the raw question
Q: What are the four functions of the AI RMF Core?
pages [25, 25, 4] -> The four functions of the AI RMF Core are GOVERN, MAP, MEASURE, and MANAGE (page 25). Governance is a cross-cutting function that informs and is infused throughout the ot
OK
Q: Which of them is described as cross-cutting?
pages [32, 5, 43] -> Governance is described as a cross-cutting function (page 5). It informs and is infused throughout the other three functions, namely GOVERN, MAP, MEASURE, and MANAGE.
OK
Q: What characteristics of trustworthy AI does the framework list?
pages [17, 5, 7] -> The framework lists the following characteristics of trustworthy AI systems: 1. Valid and reliable 2. Safe 3. Secure and resilient 4. Accountable and transparent 5. Expla
OK
Q: Which one is shown as the base for the others?
pages [32, 43, 26] -> The base for the others is "software or hardware systems" (page 26). It serves as a foundation for the other categories, such as data, and is also subject to similar cont
MISSED
Q: What fines does the EU AI Act set for violations?
pages [11, 21, 34] -> Not found in the document.
OK
4 of 5 answered as expected
=== retrieval with the rewritten question
Q: What are the four functions of the AI RMF Core?
pages [25, 25, 4] -> The four functions of the AI RMF Core are GOVERN, MAP, MEASURE, and MANAGE (page 25). Governance is a cross-cutting function that informs and is infused throughout the ot
OK
Q: Which of them is described as cross-cutting?
searched for: Is governance the cross-cutting function among GOVERN, MAP, MEASURE, and MANAGE?
pages [5, 26, 41] -> Governance is described as a cross-cutting function (page 5). It informs and is infused throughout the other three functions.
OK
Q: What characteristics of trustworthy AI does the framework list?
searched for: What are the characteristics of trustworthy AI listed by the framework, specifically in relation to the four functions of the AI RMF Core (GOVERN, MAP, MEASURE, and MANAGE)?
pages [7, 25, 17] -> The Framework lists the following characteristics of trustworthy AI systems: 1. Valid and reliable 2. Safe 3. Secure and resilient 4. Accountable and transparent 5. Expla
OK
Q: Which one is shown as the base for the others?
searched for: What characteristic of trustworthy AI systems is described as the base or foundation for the others, according to the framework?
pages [5, 17, 17] -> Valid & Reliable is shown as the base for other trustworthiness characteristics (page 17).
OK
Q: What fines does the EU AI Act set for violations?
searched for: What are the specific fines imposed by the EU AI Act for non-compliance with its regulations?
pages [47, 27, 7] -> Not found in the document.
OK
5 of 5 answered as expectedWhat the output shows
- First questions are easy. Both runs found the four functions, GOVERN, MAP, MEASURE and MANAGE, on page 25.
- A vague follow-up broke the naive run. "Which one is shown as the base for the others?" retrieved pages 32, 43 and 26, none of which answer it. The model still answered, confidently and with a page citation: "software or hardware systems". It was wrong, and the citation made it look reliable.
- Rewriting fixed it. The rewritten query, "What characteristic of trustworthy AI systems is described as the base or foundation for the others", retrieved page 17. That page holds the figure caption stating that Valid and Reliable is the base, and the answer was right.
- Some follow-ups survive anyway. "Which of them is described as cross-cutting?" worked both ways, because "cross-cutting" is a rare, specific word. Follow-ups made of pronouns and comparisons are the ones that fail.
- Both runs declined the off-topic question about EU AI Act fines: "Not found in the document."
- Rewrites can overreach. One rewritten question added a detail the user never asked about. It did no harm here, but rewriting is itself a model call that needs testing.
This rewrite step is common enough to be built into frameworks. LlamaIndex's "condense question" chat mode looks at the chat history and rewrites the user's message into a query before searching (LlamaIndex: chat engines).
Design notes
Keep page numbers with every chunk, so answers can cite them and users can check. A citation is only useful if it leads to the right page.
Retrieve with the rewritten question, answer with the conversation. The search needs a self-contained query; the answer benefits from knowing what was said before.
Limit the history you rewrite from. The script uses only the last four messages. Very long conversations should be summarised first, or the rewrite drifts towards older topics.
Log both versions of every question, the user's words and the rewritten query, so that a wrong answer can be traced to retrieval, to rewriting or to the answer itself.
Give the model a way out. A fixed phrase for "not in the document" is easy to detect in code and to show in the interface.
Test conversations, not just questions. Build test cases as sequences with follow-ups, pronouns and changes of topic, because that is where chatbots fail. See retrieval evaluation.
Check citations. The wrong answer above cited a real page that did not support it. Verifying that the cited text contains the claim catches this. See how to reduce LLM hallucinations.
Watch the ingestion. Tables, multi-column layouts and scanned pages need more than plain text extraction. For splitting choices, see chunking strategies for RAG.
Common mistakes
- Searching with raw follow-up questions.
- Dropping page numbers during chunking.
- Trusting citations without checking them.
- Testing single questions only.
- Assuming every PDF has extractable text.
- No answer for "not in the document", so the model fills the gap.
For the general architecture behind this chatbot, see RAG architecture explained.
How do I build a chatbot that answers questions about PDFs?
Why does my RAG chatbot fail on follow-up questions?
What is query rewriting or condensing in RAG?
How do I make a document chatbot cite its sources?
Building RAG-based Q&A systems and chatbots is part of the Building Gen AI Applications module of the Generative AI course.
Sources
- Artificial Intelligence Risk Management Framework (AI RMF 1.0), NIST AI 100-1 — National Institute of Standards and Technology
- AI Risk Management Framework (NIST) — National Institute of Standards and Technology
- Extract Text from a PDF (pypdf documentation) — pypdf
- Chat engine usage pattern (LlamaIndex documentation) — LlamaIndex
GloryTecks Team
Training & Placement Faculty
The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.
Want to master Generative AI?
Join GloryTecks’ expert-led Generative AI training in Hyderabad with real-time projects and 100% placement support.
Related articles
RAG Architecture Explained: Retrieval-Augmented Generation
RAG gives a language model the right pages to read before it answers. This guide walks through the four stages with a pipeline that runs on a laptop, using open models and public documents, and shows three outcomes: a correct cited answer, a retrieval miss the model admits to, and a question outside the documents.
LangGraph Tutorial: Building Stateful AI Agents
Build a small support-ticket agent with LangGraph, using only plain Python and no API key: typed state and reducers, conditional edges, a retry loop, checkpoints with thread IDs, and a human approval step with interrupt. Every output shown was produced by running the code.
What Is Generative AI? A Complete Beginner's Guide
What generative AI is, in plain language: how language models produce text one token at a time (shown with a tiny runnable model), how chat models are trained, how image models differ, what the technology is reliable for, and why it confabulates.