This llamaindex citation query engine tutorial walks through building a retrieval-augmented generation (RAG) system that grounds responses in sourced documents. We’ll use LlamaIndex’s CitationQueryEngine to attach verifiable citations to generated answers, so your users can trace every claim back to the original text.
Prerequisites
- Python 3.10+
llama-index(core) andllama-index-llms-openaiinstalled- An OpenAI API key, or any OpenAI-compatible endpoint. If you want one endpoint that covers 240+ models with automatic fallback when a provider is rate-limited, n4n.ai exposes an OpenAI-compatible API you can drop into
Settings. - A small corpus of text files in a
data/directory.
pip install llama-index llama-index-llms-openai llama-index-embeddings-openai
Load documents and build an index
Point SimpleDirectoryReader at your corpus. For deterministic results, fix the chunk size.
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex, Settings
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding
Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
docs = SimpleDirectoryReader("data/").load_data()
index = VectorStoreIndex.from_documents(docs, chunk_size=512)
If your files are absent, create a sample:
mkdir -p data
echo "LlamaIndex supports citation query engines for RAG." > data/a.txt
echo "Citations map generated text to source nodes." > data/b.txt
Configure the CitationQueryEngine
The citation engine subclasses the standard query engine. It post-processes the LLM output to inject markers like [1] and binds them to retrieved nodes.
from llama_index.core.query_engine import CitationQueryEngine
query_engine = CitationQueryEngine.from_args(
index,
similarity_top_k=3,
citation_chunk_size=512,
generate_localized_citations=True,
)
generate_localized_citations=True makes the engine pass cited chunks to the LLM prompt so the model stays grounded. Without it, you get less reliable markers.
Run a query and inspect output
Execute a query and print the response plus the source mapping.
response = query_engine.query("How does LlamaIndex handle citations in RAG?")
print(response.response)
print("---")
for i, src in enumerate(response.source_nodes):
print(f"[{i+1}] score={src.score:.3f} file={src.node.metadata.get('file_name')}")
print(src.node.get_content()[:120])
Expected output:
LlamaIndex handles citations in RAG by using a CitationQueryEngine that injects markers such as [1] into the response and binds them to retrieved source nodes [1][2].
---
[1] score=0.81 file=a.txt
LlamaIndex supports citation query engines for RAG.
[2] score=0.77 file=b.txt
Citations map generated text to source nodes.
The markers in the text correspond to the indexed source_nodes list, in order.
Extract structured citations
For a UI, you need a clean mapping. The response object exposes source_nodes with metadata. Build a payload:
def build_citations(response):
out = []
for node in response.source_nodes:
out.append({
"citation_id": node.id_,
"file": node.metadata.get("file_name"),
"score": node.score,
"text": node.get_content()[:200],
})
return out
import json
print(json.dumps(build_citations(response), indent=2))
This yields JSON your frontend can render as footnotes.
How the citation parser binds markers
The engine prefixes each retrieved chunk with a numeric tag in the prompt, e.g., Context [1]: .... After generation, a regex extracts [n] spans and maps them to source_nodes[n-1]. If the model emits [0] or out-of-range, the parser drops it. Understanding this helps when citations look off.
import re
markers = re.findall(r"\[(\d+)\]", response.response)
valid = [int(m) for m in markers if 1 <= int(m) <= len(response.source_nodes)]
Streaming with citations
CitationQueryEngine does not natively stream markers token-by-token in current releases, but you can stream the underlying response and attach citations after. For production, generate the full response then stream the text while lazy-loading sources from a cache.
# Pattern: query, then stream stored string
def stream_response(text):
for chunk in text.split(" "):
yield chunk + " "
for tok in stream_response(response.response):
print(tok, end="", flush=True)
Production considerations
- Citation integrity: The LLM may hallucinate a
[3]with no matching node. Validate markers againstlen(response.source_nodes)before rendering. - Chunk size: Smaller
citation_chunk_sizeyields finer-grained citations but more nodes. Tune to your corpus. - Cost: Each citation query sends retrieved chunks to the LLM. Use a cheap model for drafting and a stronger one for final answer if needed.
- Routing: If you use a gateway that honors client routing directives, you can pin citation generation to a specific model with low latency.
In this llamaindex citation query engine tutorial we kept the stack minimal, but the same pattern works with any BaseLLM implementation.
Custom prompt for stricter citations
Override the citation prompt to force the model to only cite provided context.
from llama_index.core.prompts import PromptTemplate
citation_prompt = PromptTemplate(
"Given context: {context_str}\n"
"Answer the question: {query_str}\n"
"Only use citations [n] that map to context. If unsure, say 'no source'.\n"
)
query_engine = CitationQueryEngine.from_args(
index,
text_qa_template=citation_prompt,
similarity_top_k=2,
)
This reduces unsupported claims in our testing on internal docs.
Evaluate with a quick test
Add a pytest to catch out-of-range markers in CI:
import re
def test_citations_in_range():
resp = query_engine.query("What does LlamaIndex support?")
ids = [int(m) for m in re.findall(r"\[(\d+)\]", resp.response)]
assert all(1 <= i <= len(resp.source_nodes) for i in ids)
Expose as an HTTP endpoint
Wrap the engine in FastAPI to serve it:
from fastapi import FastAPI
app = FastAPI()
@app.post("/ask")
def ask(q: str):
resp = query_engine.query(q)
return {"answer": resp.response, "citations": build_citations(resp)}
Run with uvicorn main:app --reload. This closes the loop for a production RAG service.
You now have a runnable llamaindex citation query engine tutorial implementation: indexed docs, a citation-aware query engine, and a structured citation extractor. Swap the LLM backend to your preferred provider and ship it behind your API.