Skip to content

Bring Your Own Stack

indx owns orchestration; you choose the components.

  • Orchestration is the six-stage pipeline. indx owns it.
  • Components are the expensive or opinionated capabilities: parsing, LLM, vision, embedding, vector storage, and output. You choose them.

Each component is a typed slot with a default that you can replace by name or by object. We call this Bring Your Own Stack (BYOS): the ingestion pipeline adapts to your environment instead of forcing one vendor stack.

indx composes parsers, models, and stores. It does not replace them. A file parser is not a competitor; it is one swappable slot inside indx. The same holds for the LLM that writes summaries, the embedder that vectorizes chunks, the database that stores them, and the writer that serializes the result.

Each slot is a typed interface with a named default. Swapping a backend is a config change, never a rewrite. The pipeline you write today survives the model you will use next year. Re-embed, re-store, re-export with the same code.

The six stages of the pipeline use six replaceable slots. Each slot has one runtime default. The default stack is cloud-backed: openai:gpt-5-mini for enrichment and openai:text-embedding-3-small for embeddings. For offline work, use either --offline for the zero-dependency core stack or the opt-in local profile (ollama:qwen2.5 + bge-m3) for richer local models.

SlotDefaultOther options
Parserdoclingunstructured, llamaparse, markitdown, custom
LLMopenai:gpt-5-mini (cloud)ollama, vllm, anthropic, azure
VLMnoneqwen-vl, gpt-4o, local
Embeddingopenai:text-embedding-3-small (dim 1536)bge-m3, e5, cohere
Vector storeqdrantpgvector, chroma, lancedb, jsonl
Output.indxjsonl, langchain, llamaindex

Each slot has a dedicated guide that covers the trade-offs in depth:

The slot design expresses three product principles that recur across indx.

There are two offline paths:

  • The opt-in local profile runs with richer local components: docling parses locally, ollama:qwen2.5 runs enrichment with no API key, and bge-m3 embeds locally.
  • The zero-dependency floor ships in the core package: plaintext, none, hash, jsonl, and .indx. Use it from the CLI with --offline, or select those slots explicitly in the SDK.

Slots are defined as typing.Protocol classes, which means structural typing. A backend satisfies a slot by having the right methods and signatures, not by inheriting from an indx base class. Third-party authors never import or subclass anything from indx to plug in; they just implement the protocol.

The protocol names are stable contracts: Parser, LLM, VLM, Embedder, Store, OutputWriter, and the Stage protocol that the pipeline itself uses. See the full signatures in the protocols reference.

This is why a community package like indx-weaviate can ship a new store, advertise it through a Python entry point, and have store = "weaviate" simply work, with no fork of indx. First-party builtins always win on name collisions, so a plugin can never silently shadow a default.

pip install indx stays small and fast. The core depends only on Typer, Rich, Click, Pydantic v2, and pydantic-settings (TOML parsing is stdlib).

Every heavy backend — Docling, Torch, vector-DB clients, cloud SDKs — is an optional extra, never a core dependency.

ExtraInstalls
indx[docling]the default Docling parser
indx[bge]local bge-m3 embeddings (FlagEmbedding + Torch)
indx[qdrant]the Qdrant client
indx[openai] / indx[anthropic]cloud LLM SDKs
indx[cloud]the default cloud runtime stack: Docling + OpenAI + Qdrant
indx[local] / indx[defaults]the opt-in local profile stack in one line — the two names are aliases for the same bundle
indx[all]every backend, for convenience or CI

If you select a backend whose extra is not installed, indx fails fast with an actionable error. The error names the exact pip install "indx[...]" to run, and it never breaks an unrelated code path. See the extras reference for the full matrix.

A slot can be filled either by name (in config or on the CLI) or by object (in code). The two are equivalent; the name simply resolves through the registry to a class.

In indx.toml, each section names the backend for a slot. Names resolve through the registry to the right implementation:

[parser]
backend = "docling" # docling | unstructured | llamaparse | markitdown | custom
[llm]
backend = "ollama" # ollama | vllm | openai | anthropic | azure
model = "qwen2.5"
# api_key comes from $INDX_LLM__API_KEY, never the file
[embedding]
backend = "bge-m3" # bge-m3 | e5 | openai | cohere
[store]
backend = "qdrant" # qdrant | pgvector | chroma | lancedb | jsonl
[output]
writer = "indx" # indx | jsonl | langchain | llamaindex

The same names work as CLI flags, which take precedence over the config file:

Terminal window
indx ./docs --out ./ai-ready --parser markitdown --store jsonl --format langchain

Precedence is CLI flag > indx.toml > built-in default. See the configuration guide and the configuration reference for the complete schema.

In the SDK, pass a constructed component straight into the pipeline. This is the same selection, expressed as an object instead of a string:

from indx import DirectoryPipeline
from indx.parsers.docling import DoclingParser
from indx.store.qdrant import QdrantStore
# fully local, on-prem — nothing leaves the building
pipeline = DirectoryPipeline(
parser=DoclingParser(),
llm="ollama:qwen2.5",
embedder="bge-m3",
store=QdrantStore(url="http://localhost:6333"),
)
space = pipeline.run("./docs", "./ai-ready")

Custom objects only need to satisfy the protocol

Section titled “Custom objects only need to satisfy the protocol”

Because slots use structural typing, your own class drops straight in as long as it matches the protocol. There is no base class and no registration ceremony:

from pathlib import Path
from indx.core import ParsedDoc
class MyParser:
def parse(self, path: Path) -> ParsedDoc:
...
pipeline = DirectoryPipeline(parser=MyParser())

For the full walkthrough of building components and stages of your own, see custom components and the protocols reference.

A folder already encodes how an organization thinks: what sits next to what, what supersedes what, what points where. indx keeps that map, and BYOS makes sure the map is yours — built on a light core, runnable entirely offline, and free of any single vendor’s roadmap.

Whether you run on a laptop, a GPU server, or an air-gapped enterprise network, the same pipeline code applies. Only the slots change.

Next, explore the core objects that flow through these slots, or the pipeline and stages that drive them.