Documents, nodes, indexes and query engines
LlamaIndex is built around one pipeline: load documents, split them into nodes, index the nodes, query the index. Each stage is swappable.
| Stage | Object | Does |
|---|---|---|
Load |
Reader | Turns a source into Documents |
Parse |
NodeParser | Splits Documents into Nodes |
Embed |
EmbedModel | Vectorises each Node |
Index |
Index | Stores Nodes for retrieval |
Retrieve |
Retriever | Selects candidate Nodes |
Postprocess |
NodePostprocessor | Reranks, filters, expands |
Synthesise |
ResponseSynthesizer | Turns Nodes into an answer |
| Index | Good for | Cost |
|---|---|---|
| VectorStoreIndex | Semantic search — the default | One embedding per node |
| SummaryIndex | Questions needing the whole corpus | Reads everything, every query |
| DocumentSummaryIndex | Routing to the right document first | One summary per document, built once |
| KeywordTableIndex | Exact terms, codes and identifiers | Cheap, no embeddings |
| PropertyGraphIndex | Relationships between entities | Expensive extraction pass |
| Mode | Behaviour | Calls |
|---|---|---|
| compact | Packs nodes into as few prompts as fit — the default | Fewest |
| refine | Answers with the first node, then refines with each next | One per node |
| tree_summarize | Summarises in a tree, bottom up | Logarithmic; good for many nodes |
| simple_summarize | Truncates everything into one prompt | One, and lossy |
| no_text | Retrieves only, no generation | Zero — useful for debugging retrieval |
The whole pipeline in its shortest honest form, with the retriever made explicit.
from llama_index.core import (
VectorStoreIndex, SimpleDirectoryReader, StorageContext, load_index_from_storage,
)
from llama_index.core.node_parser import SentenceSplitter
docs = SimpleDirectoryReader("./docs").load_data()
nodes = SentenceSplitter(chunk_size=512, chunk_overlap=64).get_nodes_from_documents(docs)
index = VectorStoreIndex(nodes)
index.storage_context.persist("./storage") # embedding once is the expensive part
engine = index.as_query_engine(
similarity_top_k=5,
response_mode="compact",
)
response = engine.query("How do I rotate an API key?")
print(response)
for node in response.source_nodes: # always show your sources
print(node.metadata.get("file_name"), round(node.score, 3))
VectorStoreIndex. It is the default because it is right for most retrieval. Reach for SummaryIndex or a graph index only when you can name the question they answer better.
Check the response mode. `refine` makes one model call per retrieved node, so a top_k of 10 is ten calls. `compact` packs nodes into as few prompts as fit.