Ingestion pipelines

LlamaIndex · Practical · 12 min

Repeatable, incremental ingestion is what keeps a RAG index correct as source data changes.

An `IngestionPipeline` applies a list of transformations to documents, in order, and can write the result straight to a vector store. Transformations include node parsers, metadata extractors and the embedding model itself.

pipeline = IngestionPipeline(
    transformations=[SentenceSplitter(chunk_size=512), TitleExtractor(), embed_model],
    vector_store=vector_store,
    docstore=SimpleDocumentStore(),
)
pipeline.run(documents=documents)

Why use it instead of from_documents:

  • Caching. Each transformation's output is cached by input, so re-running on unchanged data skips the expensive steps.
  • De-duplication. With a docstore attached, the pipeline remembers each document's id and content hash. Unchanged documents are skipped; changed ones are re-processed and their old nodes replaced (the default upserts strategy). Stable document ids, such as from filename_as_id=True, make this work.
  • One place for your ingestion logic, which you can run from a scheduled job.

Metadata extractors use an LLM to add metadata to nodes, for example TitleExtractor, SummaryExtractor or QuestionsAnsweredExtractor. They can improve retrieval but cost an LLM call per node, so use them where they measurably help.

Save the pipeline's cache and docstore with pipeline.persist("./pipeline_storage") and load them with pipeline.load(...) on the next run. pipeline.run(..., num_workers=4) processes in parallel.

After ingestion, query with VectorStoreIndex.from_vector_store(vector_store).

Example

from llama_index.core import Document, Settings, VectorStoreIndex
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.storage.docstore import SimpleDocumentStore
import chromadb
from llama_index.vector_stores.chroma import ChromaVectorStore

collection = chromadb.EphemeralClient().get_or_create_collection("docs")   # in-memory demo
vector_store = ChromaVectorStore(chroma_collection=collection)   # or Qdrant, pgvector...
pipeline = IngestionPipeline(
    transformations=[SentenceSplitter(chunk_size=512, chunk_overlap=50),
                     Settings.embed_model],   # embedding is the last step
    docstore=SimpleDocumentStore(),          # enables de-duplication
    vector_store=vector_store,
)
docs = [Document(text="Refunds take 5 days.", id_="faq.md"),
        Document(text="Office in Oslo.", id_="about.md")]
print(len(pipeline.run(documents=docs)))    # 2 nodes processed
print(len(pipeline.run(documents=docs)))    # 0  (unchanged, skipped)

docs[0] = Document(text="Refunds take 3 days.", id_="faq.md")
print(len(pipeline.run(documents=docs)))    # 1  (only the changed doc)
pipeline.persist("./pipeline_storage")

index = VectorStoreIndex.from_vector_store(vector_store)

Remember

  • Pipelines chain parsers, extractors and the embedding model
  • A docstore enables skipping unchanged documents and replacing changed ones
  • Persist cache and docstore between runs

Watch out for

  • Running a pipeline with a docstore but unstable document ids (random UUIDs), so every run looks like new documents.

Check yourself

  1. You re-run a pipeline (with a docstore) on 1,000 documents where only 3 changed. Roughly what gets re-processed?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developers.llamaindex.ai/python/framework/module_guides/loading/ingestion_pipeline/