Skip to content

Bring Your Own Stack

indx owns the orchestration — the six-stage pipeline — and hands you the components. Every expensive or opinionated capability (parsing, LLM, vision, embedding, vector store, output) is a typed slot with a sensible default that you can replace by name or by object. This is what we mean by Bring Your Own Stack (BYOS): your environment is the constraint that matters, so the tool bends to it rather than the other way around.

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 is true of the LLM that writes summaries, the embedder that vectorizes chunks, the database that stores them, and the writer that serializes the result.

Because 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 — same code.

The six stages of the pipeline read and write capabilities through six replaceable slots. Each slot has exactly one default — the shipped, zero-config stack is cloud-backed (openai:gpt-5-mini for the LLM, openai:text-embedding-3-small for embeddings). For an offline, air-gapped run, the zero-dependency core fallbacks and the opt-in local profile (ollama:qwen2.5 + bge-m3) swap those slots out so a complete run works with no network and no API key.

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 is not decoration — it is the structural expression of three product principles that recur across indx.

The local profile runs on a developer’s laptop with no external services. docling parses locally, ollama:qwen2.5 runs enrichment with no API key, bge-m3 embeds locally, and the store and output writers can both stay entirely offline. There is also a zero-dependency floor that ships in the core package itself — the plaintext parser, the jsonl store, the none VLM, and the .indx + jsonl writers — so even before you install a single extra, a full run is possible offline.

Slots are defined as typing.Protocol classes — 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 also 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[local] / indx[defaults]the recommended local-first 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 naming the exact pip install "indx[...]" to run — 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, just expressed as an object instead of a string:

from indx import DirectoryPipeline
from indx.parsers import DoclingParser
from indx.store 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 — no base class, no registration ceremony:

from pathlib import Path
from indx.core import ParsedDoc, SpaceContext
class MyParser:
def parse(self, source: Path, ctx: SpaceContext) -> 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. 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.