Back
intermediate

Tools for AI application development

Getting started with LlamaIndex

Follow documents into nodes and retrieval, keeping metadata and model configuration explicit.

Lesson 38 of 44About 28 min with practice

You load a document, call an index builder, and receive an answer. What happened to the document between those steps? LlamaIndex provides useful abstractions, but a reliable application needs to inspect the data inside them.

Before you begin: Understand chunking, embeddings, and retrieval. Python knowledge is useful for the optional example.

Follow the data, not just the helper calls

A document represents source material and metadata. A node is a unit derived from that material, often a text chunk with links back to its source. An index organizes nodes for retrieval. A retriever chooses candidates; a query engine can combine retrieval with response generation.

These are separate responsibilities. If the wrong chunk is retrieved, changing the writer's tone will not repair the evidence. If parsing dropped a table header, a good embedding model cannot recover its missing meaning.

Consider two fictional notices. N1 says W1 has capacity 12. N2 says W2 has capacity 8. Keep IDs attached throughout indexing so that “capacity 12” can be traced to W1. A paragraph without its source metadata is harder to verify or update.

What text did the index actually receive?

A retrieval library cannot recover a timetable that the document parser scrambled before indexing. Inspect extracted text and chunk boundaries before tuning the answer prompt. Tables, repeated headers and reading order can change the meaning of the content that enters the index.

Trace one known fact from its PDF location to the parsed text and then to its indexed node. Preserve the source reference along that path. The challenge makes ingestion observable so a downstream answer failure does not send you straight to the wrong component.

Example

A PDF table has classes in rows and days in columns.

What changes

Inspect the extracted reading order before embedding the text.

Result

You can catch a class being paired with the wrong day before retrieval and generation use it.

Retrieval quality depends on the evidence that ingestion preserves, not just the embedding model.

Inspect nodes before making a model call

This complete package example requires llama-index-core. It performs local document splitting, with no LLM or embedding provider. Its API follows the official document and node guides reviewed on September 10, 2026; package execution is not implied by the course's standard-library test count.

python
from llama_index.core import Document
from llama_index.core.node_parser import SentenceSplitter

documents = [
    Document(
        text="W1 has capacity 12. Booking status is unknown.",
        metadata={"source_id": "N1", "version": "1"},
    ),
    Document(
        text="W2 has capacity 8. It starts at 10:00.",
        metadata={"source_id": "N2", "version": "1"},
    ),
]
splitter = SentenceSplitter(chunk_size=128, chunk_overlap=16)
nodes = splitter.get_nodes_from_documents(documents)
for node in nodes:
    print(node.metadata["source_id"], node.get_content())
assert {node.metadata["source_id"] for node in nodes} == {"N1", "N2"}

The chunk limits refer to the splitter's token accounting, not a promise of exactly 128 words per node. Short source documents can remain much smaller. Inspect the produced boundaries instead of assuming a setting guarantees coherent chunks.

Add retrieval deliberately

The VectorStoreIndex abstraction can embed and index documents or nodes. Choose the embedding integration explicitly and record its model identifier. The query embedding must be compatible with the indexed vectors. Do not rely on an old tutorial's default provider or model being the right choice today.

When adding a query engine, configure its generation model separately from the embedding model. Embeddings choose evidence; generation writes an answer from that evidence. A provider may charge for both operations. Keeping them separate also lets you evaluate retrieval without generating a paragraph for every test.

Persistence is another decision. Saving an index can avoid repeating ingestion, but you must also preserve source versions, transformation settings, and compatible embedding configuration. A saved index is not automatically a complete per-user application database.

Find the upstream defect

A retrieved node says “12” but has lost the workshop ID and the word “capacity.” Would a larger generation model reliably fix the answer?

Inspect the missing context

No. The evidence no longer identifies what 12 measures. Repair parsing or chunk construction so that the number stays with its label and source. Then verify retrieval and answer behavior separately. A larger model might guess the original relationship, but guessing is the defect the application was meant to avoid.

For practice, add a revised N1 and decide whether to replace the old version, retain both with dates, or expose an explicit conflict. Test that a deleted or unauthorized source cannot remain available through a stale index.

Practice with feedback

Lesson challenge

Inspect what actually entered the index

Answers about a PDF timetable are wrong. You have tuned the prompt three times.

Follow the data through loading and node parsing before making any model call.

Check your understanding

Question 1 of 3
What should you inspect first?
Score: 0/0

Your task

Write the ingestion inspection you run before any query.

These notes stay on this page. Download them before leaving. Code in this field is not executed.

What to include

  • Both document and node level statistics are printed
  • Random nodes are shown as text you actually read
  • Metadata presence is asserted, not assumed
  • Thresholds are stated in advance
Compare with a worked answer

Here is one way to answer. Check how it uses the information in the task.

import random, statistics as st
from llama_index.core import SimpleDirectoryReader
from llama_index.core.node_parser import SentenceSplitter

docs = SimpleDirectoryReader('data/timetables').load_data()
print(f'{len(docs)} documents')
for d in docs[:5]:
    print(' ', d.metadata.get('file_name'), len(d.text), 'chars')

nodes = SentenceSplitter(chunk_size=800, chunk_overlap=100).get_nodes_from_documents(docs)
lens = [len(n.text) for n in nodes]
print(f'{len(nodes)} nodes | min {min(lens)} median {st.median(lens)} max {max(lens)}')

for n in random.sample(nodes, 3):
    print('---', n.metadata.get('file_name'), 'p', n.metadata.get('page_label'))
    print(n.text[:300])

for n in nodes:
    assert n.metadata.get('file_name'), 'node without a source file'
    assert n.metadata.get('page_label'), 'node without a page'

# Health thresholds that stop me before I touch a prompt:
#   - fewer than 2 nodes per page of PDF: extraction is failing
#   - median node length under 150 chars: the splitter is receiving fragments
#   - any printed sample that reads as interleaved columns or is mostly
#     whitespace: the loader is wrong for this file type
# My 12-page timetable produced 3 nodes and the samples were column-
# interleaved gibberish. Three prompt rewrites could never have fixed that;
# switching to a table-aware extractor did.

When you are signed in, opening the challenge carries your edited working notes into its draft in this browser. The challenge has its own completion record. Practising here does not award points or mark it complete.

Next, explore how to present generated output while preserving the distinction between partial text and a completed answer.

Sources

Documents and nodes explains the data units. VectorStoreIndex describes indexing. Node parser usage covers splitting.

Practise this lesson

Inspect what actually entered the index

Follow the data through loading and node parsing before making any model call.

About 10 min55 points3 checks and one applied task
Loading your lesson progress...