In Part 3, you will run the most basic version of RAG over Les Misérables.
RAG means retrieval-augmented generation.
The whole idea is:
find useful text first -> send that text to Gemini -> get an answerAn LLM does not automatically know what is inside your local text file. RAG is one way to give it useful text at the moment you ask a question.
In this tutorial, we will:
- read Les Misérables line by line,
- retrieve a few lines that look relevant,
- place those lines inside the prompt,
- ask Gemini to answer using that context.
This is not a full search engine. It is the smallest useful version of the idea.
Part 3 uses Gemini, so use the same separate environment as the homework.
From the session3 folder:
python3 -m venv .venv_homework
source .venv_homework/bin/activate
pip install -r requirements-homework.txtWindows PowerShell:
python -m venv .venv_homework
.venv_homework\Scripts\Activate.ps1
pip install -r requirements-homework.txtDownload the dataset:
hf download Birkbeck/les-miserables-txt les_miserables.txt \
--repo-type dataset \
--local-dir .Set your Gemini API key in the same terminal where you will run Python.
Replace PASTE_YOUR_KEY_HERE with your real key. Do not include spaces around =.
export GEMINI_API_KEY="PASTE_YOUR_KEY_HERE"Windows PowerShell:
$env:GEMINI_API_KEY="PASTE_YOUR_KEY_HERE"Check that Python can see it:
python3 -c 'import os; print("GEMINI_API_KEY set:", bool(os.getenv("GEMINI_API_KEY")))'Windows PowerShell:
python -c "import os; print('GEMINI_API_KEY set:', bool(os.getenv('GEMINI_API_KEY')))"Before Gemini, remember that a file object is already an iterator.
with open("les_miserables.txt", "r", encoding="utf-8") as file:
first_line = next(file)
second_line = next(file)
print(first_line)
print(second_line)This reads only the first two lines. It does not load the whole book into memory.
You can also loop over the file:
with open("les_miserables.txt", "r", encoding="utf-8") as file:
for line in file:
print(line)
breakThat loop is streaming. Python gives you one line at a time.
yield lets us create our own iterator.
def non_empty_lines(path):
with open(path, "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
if line != "":
yield line
for line in non_empty_lines("les_miserables.txt"):
print(line)
breakThis generator is useful because it hides the file-reading details. The rest of the program can simply ask for the next useful line.
For RAG, this matters because we often do not want the whole document. We want a small useful chunk.
Create:
session3/solutions/exercise-03-03.pyCopy this code:
import os
from google import genai
TEXT_FILE = "les_miserables.txt"
QUESTION = "Who is Bishop Myriel?"
KEYWORDS = ["bishop", "myriel", "digne"]
MAX_LINES = 8
def useful_lines(path):
"""Yield non-empty lines from a text file."""
with open(path, "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
if line != "":
yield line
def retrieve_context(path, keywords, max_lines):
"""Find a small chunk of text that matches the question."""
matches = []
extra_lines = 0
for line in useful_lines(path):
line_lower = line.lower()
if any(keyword in line_lower for keyword in keywords):
matches.append(line)
# Keep two lines after a match so Gemini has a little context.
extra_lines = 2
elif extra_lines > 0:
matches.append(line)
extra_lines -= 1
if len(matches) >= max_lines:
break
return "\n".join(matches)
context = retrieve_context(TEXT_FILE, KEYWORDS, MAX_LINES)
# Send only the retrieved context to Gemini.
prompt = f"""Use only this context to answer the question.
Question:
{QUESTION}
Context:
{context}
"""
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt,
)
print(response.text)Run:
python3 solutions/exercise-03-03.pyThis is RAG in its simplest form:
useful_lines()streams the book and yields one non-empty line at a time.retrieve_context()keeps lines that mentionbishop,myriel, ordigne.- It also keeps a couple of lines after each match for context.
- The retrieved text becomes the context in the prompt.
- Gemini answers using that context.
The generator keeps the reading logic small and reusable. It also means the program can stop once it has enough context.
You could do this:
with open("les_miserables.txt", "r", encoding="utf-8") as file:
text = file.read()That loads the whole file into memory. Sometimes that is fine, but it is not the habit we want for large files.
For this task, streaming is enough:
for line in useful_lines("les_miserables.txt"):
if "myriel" in line.lower():
print(line)
breakThis can stop as soon as it finds useful text.
Change the question and keywords.
Try this:
QUESTION = "Who is Fantine?"
KEYWORDS = ["fantine"]Then run:
python3 solutions/exercise-03-03.pyAnswer these questions in a few lines under your code:
# 1. Which lines were retrieved?
# 2. Did Gemini have enough context?
# 3. What would improve this tiny RAG system?At the moment, the script stops when it has 8 matching/context lines.
Try giving Gemini a little more context e.g. 14 lines. Run the script again and compare the answer.
Questions:
- Did Gemini give a more detailed answer?
- What is the trade-off of sending more context?
Write a short reflection, around 150-250 words. Answer these questions:
- In your own words, what is RAG?
- What is the context window? Why can we not always send a whole book, website, or dataset to an LLM?
- How do iterators and
yieldhelp us build the context gradually instead of loading everything at once? - What is one trade-off between sending more context and sending less context to Gemini?
Hint: Think about answer quality, missing useful evidence, prompt length, cost, and noise.
Use this small use case in your answer:
A student has 50 lecture transcripts and wants to ask:
"What did we say about dynamic programming?"Explain how a simple RAG system could:
- stream through the transcript files,
- retrieve only chunks that mention useful keywords,
- put those chunks into the prompt,
- ask Gemini for an answer.
Also explain one possible problem. For example, the system might retrieve too little context and miss the answer, or retrieve too much context and make the prompt noisy or expensive.
Post your answer: Reflection and Homework forum