Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

LlamaIndex Example: Retrieval-Augmented Generation on Real Text

import sys

print(sys.executable)
/Users/mohitsalur/Documents/AtoZ/Causify_AI/gpsaggese.github.io/tutorials/tutorial_llamaindex/venv/bin/python

LlamaIndex Example: Retrieval-Augmented Generation on Real Text

In this notebook, we build a complete Retrieval-Augmented Generation (RAG) system using real-world data from Project Gutenberg.

We will:

  • Automatically download public domain books
  • Preprocess and clean the text
  • Build a vector index
  • Query across multiple documents
  • Inspect retrieved context
  • Evaluate retrieval behavior

This example demonstrates how LlamaIndex can be applied to real textual corpora.

Model Setup

We configure:

  • A lightweight embedding model for semantic similarity
  • A local instruction-tuned language model for generation

Both models run locally and do not require API keys.

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.core.node_parser import SentenceSplitter
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.huggingface import HuggingFaceLLM
import os
import requests

# Embedding model
embed_model = HuggingFaceEmbedding(
    model_name="sentence-transformers/all-MiniLM-L6-v2"
)

# LLM model
llm = HuggingFaceLLM(
    model_name="TinyLlama/TinyLlama-1.1B-Chat-v1.0",
    tokenizer_name="TinyLlama/TinyLlama-1.1B-Chat-v1.0",
    context_window=2048,
    max_new_tokens=128,
    generate_kwargs={"temperature": 0.0},
    device_map="auto",
)

Settings.embed_model = embed_model
Settings.llm = llm

print("Models configured successfully.")
/Users/mohitsalur/Documents/AtoZ/Causify_AI/gpsaggese.github.io/tutorials/tutorial_llamaindex/venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
Some parameters are on the meta device because they were offloaded to the disk.
Models configured successfully.

Download Project Gutenberg Books

We download two public domain books:

  • Pride and Prejudice
  • The Adventures of Sherlock Holmes

These texts will serve as our knowledge base.

We store them locally inside the data/gutenberg directory.

# Create dataset directory
os.makedirs("data/gutenberg", exist_ok=True)

books = {
    "pride_and_prejudice.txt": "https://www.gutenberg.org/files/1342/1342-0.txt",
    "sherlock_holmes.txt": "https://www.gutenberg.org/files/1661/1661-0.txt",
}


def clean_gutenberg_text(text):
    start_marker = "*** START OF"
    end_marker = "*** END OF"

    start_idx = text.find(start_marker)
    end_idx = text.find(end_marker)

    if start_idx != -1 and end_idx != -1:
        text = text[start_idx:end_idx]

    return text


for filename, url in books.items():
    response = requests.get(url)
    raw_text = response.text
    cleaned_text = clean_gutenberg_text(raw_text)

    with open(f"data/gutenberg/{filename}", "w", encoding="utf-8") as f:
        f.write(cleaned_text)

    print(f"Downloaded and cleaned: {filename}")
Downloaded and cleaned: pride_and_prejudice.txt
Downloaded and cleaned: sherlock_holmes.txt
for filename in books.keys():
    path = f"data/gutenberg/{filename}"
    size_kb = os.path.getsize(path) / 1024
    print(f"{filename}: {size_kb:.2f} KB")
pride_and_prejudice.txt: 720.70 KB
sherlock_holmes.txt: 574.04 KB

Load Documents

We load the cleaned text files using SimpleDirectoryReader.

Each file becomes a Document object containing:

  • Text content
  • Metadata (file name, path, etc.)

These documents will be parsed into nodes for indexing.

documents = SimpleDirectoryReader("data/gutenberg").load_data()

print(f"Number of documents loaded: {len(documents)}")

for doc in documents:
    print(doc.metadata["file_name"])
Number of documents loaded: 2
pride_and_prejudice.txt
sherlock_holmes.txt

Chunking Large Documents

Large documents must be split into smaller chunks before indexing.

Why?

  • Embedding models operate on limited context windows
  • Retrieval happens at the chunk (node) level
  • Smaller chunks allow more precise retrieval

We configure a reasonable chunk size for long-form text.

parser = SentenceSplitter(
    chunk_size=512,
    chunk_overlap=50,
)

nodes = parser.get_nodes_from_documents(documents)

print(f"Total nodes created: {len(nodes)}")
Total nodes created: 765

Build the Vector Index

We now construct a VectorStoreIndex from the parsed nodes.

This step:

  • Computes embeddings for each node
  • Stores them in a vector index
  • Enables semantic similarity search
index = VectorStoreIndex(nodes)

print("Vector index built successfully.")
Vector index built successfully.

Query the Combined Corpus

We now query across both books using semantic retrieval.

The system will:

  1. Embed the question
  2. Retrieve the most relevant chunks
  3. Inject them into the prompt
  4. Generate a response
query_engine = index.as_query_engine(similarity_top_k=3)

response = query_engine.query("Who is Mr. Darcy?")

print(response)
The following generation flags are not valid and may be ignored: ['temperature']. Set `TRANSFORMERS_VERBOSITY=info` for more details.
Mr. Darcy is a character in Jane Austen's novel Pride and Prejudice.

Inspect Retrieved Chunks

To understand how the system arrived at its answer, we inspect the retrieved source nodes.

This allows us to see:

  • Which document was used
  • What text supported the response
  • The similarity score
for i, source_node in enumerate(response.source_nodes):
    print(f"\n--- Retrieved Node {i + 1} ---")
    print("File:", source_node.node.metadata.get("file_name"))
    print("Score:", source_node.score)
    print("\nText snippet:\n")
    print(source_node.node.text[:500])
    print("\n" + "=" * 60)

--- Retrieved Node 1 ---
File: pride_and_prejudice.txt
Score: 0.6697164685590338

Text snippet:

Darcy’s steward. Let me recommend you, however, as a friend, not to give
implicit confidence to all his assertions; for, as to Mr. Darcy’s using
him ill, it is perfectly false: for, on the contrary, he has been always
remarkably kind to him, though George Wickham has treated Mr. Darcy in a
most infamous manner. I do not know the particulars, but I know very
well that Mr. Darcy is not in the least to blame; that he cannot bear
to hear George Wickham mentioned; and that though my brother thought h

============================================================

--- Retrieved Node 2 ---
File: pride_and_prejudice.txt
Score: 0.6398331739944533

Text snippet:

“What does Mr. Darcy mean,” said she to Charlotte, “by listening to my
conversation with Colonel Forster?”

“That is a question which Mr. Darcy only can answer.”

“But if he does it any more, I shall certainly let him know that I see
what he is about. He has a very satirical eye, and if I do not begin by
being impertinent myself, I shall soon grow afraid of him.”

[Illustration: “The entreaties of several” [_Copyright 1894 by George
Allen._]]

On his approaching them soon afterwards, though with

============================================================

--- Retrieved Node 3 ---
File: pride_and_prejudice.txt
Score: 0.6383003474736233

Text snippet:

But I ought to beg his pardon, for I have no right to suppose
that Bingley was the person meant. It was all conjecture.”

“What is it you mean?”

“It is a circumstance which Darcy of course could not wish to be
generally known, because if it were to get round to the lady’s family it
would be an unpleasant thing.”

“You may depend upon my not mentioning it.”

“And remember that I have not much reason for supposing it to be
Bingley. What he told me was merely this: that he congratulated himself
on

============================================================

Query a Different Book

We now query about Sherlock Holmes to verify that retrieval works across multiple documents.

response_holmes = query_engine.query("Describe Sherlock Holmes' personality.")

print(response_holmes)
Holmes is a complex character with a range of emotions and
personality traits. He is a brilliant detective, but he is also a
complex and flawed individual. Holmes is often portrayed as cold,
detached, and distant, but he is also deeply empathetic and compassionate. He is often seen as a loner, but he is also deeply
interested in the lives of others and is often seen as a mentor to his
clients. Holmes is also known for his humor and his sense of humor is
a key aspect of his character. He is

Effect of Retrieval Depth (top_k)

We compare responses using different retrieval depths.

Higher top_k includes more context, which may improve completeness but increase latency.

# top_k = 1
query_engine_k1 = index.as_query_engine(similarity_top_k=1)
response_k1 = query_engine_k1.query("Who is Mr. Darcy?")

print("=== top_k = 1 ===")
print(response_k1)
print("\n")

# top_k = 3
query_engine_k3 = index.as_query_engine(similarity_top_k=3)
response_k3 = query_engine_k3.query("Who is Mr. Darcy?")

print("=== top_k = 3 ===")
print(response_k3)
=== top_k = 1 ===
Mr. Darcy is a character in Jane Austen's novel Pride and Prejudice.


=== top_k = 3 ===
Mr. Darcy is a character in Jane Austen's novel Pride and Prejudice.

Summary

In this example, we built a complete retrieval-augmented generation (RAG) system using real-world text.

We demonstrated:

  • Automatic dataset download
  • Text preprocessing
  • Chunking large documents
  • Vector index construction
  • Cross-document querying
  • Retrieval inspection
  • The effect of retrieval depth (top_k)

This example illustrates how LlamaIndex can be applied to real textual corpora to build grounded question-answering systems.