Choosing an Embedder
The embedder turns chunk text into vectors during stage 06, Embed+Pack. Those vectors are what make a knowledge space searchable.
This guide helps you pick an embedder and explains what happens when you change it. The model’s identity is recorded into the archive, so consumers always know which model produced the vectors.
- Default:
openai:text-embedding-3-small. Cloud-backed, light to install, dim 1536. Usebge-m3when you need a fully local profile. - Lighter local English:
e5. - No local GPU, or already paying for an API:
openaiorcohere. - Local embedders are the heaviest optional path, since they pull Torch. API embedders stay light.
- The model identity (
name) anddimare pinned into the archive manifest. Changing the embedder requires a full re-embed.
The Embedder protocol
Section titled “The Embedder protocol”Every embedder satisfies the same typed protocol, whether built-in or third-party. The pipeline never needs to know which one is active.
@runtime_checkableclass Embedder(Protocol): """Turns text into vectors. Default: openai:text-embedding-3-small.""" name: str dim: int def embed(self, texts: list[str]) -> list[list[float]]: ...Three members matter for selection:
nameis the embedder’s stable identifier, for exampletext-embedding-3-small. It is recorded into the manifest asembedding_model.embed(texts)takes a list of strings and returns one vector (list[float]) per input. indx always calls it in batches (see Batching).dimis the vector dimensionality. It is read once and pinned into the manifest asembedding_dim. That lets indx validate query-time compatibility.
The options
Section titled “The options”| Embedder | Runs | Strengths | Best for | Extra |
|---|---|---|---|---|
openai:text-embedding-3-small (default) | API | No local GPU, no model download, dim 1536 | Cloud-backed default and lightweight installs | indx[openai] |
bge-m3 | Local | Multilingual, long inputs, strong open-license retrieval, dim 1024 | Local / air-gapped profile; mixed-language and document-heavy corpora | indx[bge] (pulls Torch) |
e5 | Local | Lighter than BGE-M3, strong English retrieval | English-only corpora where you want a smaller local footprint | indx[e5] (pulls Torch) |
openai | API | No local GPU, no model download, managed quality | Teams already on OpenAI, or machines without a GPU | indx[openai] (light, HTTP only) |
cohere | API | No local GPU, strong multilingual API models | Teams already on Cohere | indx[cohere] (light, HTTP only) |
litellm:<provider/model> | API / local | One adapter for 100+ providers — on-prem (Ollama, vLLM) and AWS/Azure/GCP/cloud | Teams standardized on LiteLLM, or multi-vendor setups | indx[litellm] (light, HTTP only) |
Why BGE-M3 is the local profile’s embedder
Section titled “Why BGE-M3 is the local profile’s embedder”BGE-M3 anchors indx’s opt-in local profile, and it is also a strong general embedder:
- Fully local. It needs no API key and works air-gapped (see Local & air-gapped).
- Multilingual, and supports long inputs. This suits arbitrary directory contents such as code, docs, and mixed languages.
- Strong retrieval quality among openly licensed models. It uses native dense embeddings, with hybrid and multi-vector modes available. It works well as a default without per-corpus tuning.
- Dim 1024. That is what
space.stats.embed_dimand the manifest report on a local-profile build. The cloud default records dim 1536.
When to switch
Section titled “When to switch”- Choose
e5if your corpus is English-only and you want a lighter local model than BGE-M3. - Choose
openaiorcoherein three cases: the build machine has no GPU, you don’t want to download model weights, or your team already pays for those APIs. API embedders avoid the Torch dependency entirely.
Local vs. API: the dependency story
Section titled “Local vs. API: the dependency story”This is the single biggest practical difference between the options.
- Local embedders (
bge-m3,e5) load through FlagEmbedding (BGE’s reference implementation) or sentence-transformers. Install them viaindx[bge]/indx[e5], or get the whole air-gapped profile via theindx[local]bundle (docling + ollama + bge + qdrant). They pull in Torch plus model weights — the heaviest optional path in the whole project. - API embedders (
openai,cohere) just make HTTP calls. Their extras (indx[openai],indx[cohere]) stay light, with no Torch and no weights.
# Recommended local default stack (docling + local embeddings + qdrant):pip install "indx[local]"
# Just a local embedder runtime (Torch comes with it):pip install "indx[bge]" # BGE-M3pip install "indx[e5]" # E5
# Light API embedders — no Torch:pip install "indx[openai]"pip install "indx[cohere]"If you select an embedder whose extra is not installed, indx raises a MissingDependencyError naming the exact pip install "indx[...]" to run. See the full extras matrix.
Selecting an embedder
Section titled “Selecting an embedder”The embedder slot is resolved with the standard precedence: explicit code argument / use() → CLI flag → indx.toml → documented default.
indx ./docs --out ./ai-ready --embedder e5indx.toml
Section titled “indx.toml”[embed]model = "openai:text-embedding-3-small" # any registered embedder name; use "bge-m3" for localSDK — by name or by object
Section titled “SDK — by name or by object”from indx import DirectoryPipeline
# By name stringpipeline = DirectoryPipeline(embedder="bge-m3", store="qdrant")
# Or swap laterpipeline.use(embedder="openai")
# Or pass a custom object satisfying the Embedder protocolclass MyEmbedder: name = "my-embedder" dim = 768 def embed(self, texts: list[str]) -> list[list[float]]: ...
pipeline.use(embedder=MyEmbedder())For authoring your own embedder backend, see Custom components and Adding a backend.
Batching
Section titled “Batching”Embedding is batched, and this is the single biggest performance lever for the stage. Stage 06 hands the full chunk list to Embedder.embed(list[str]) in one call; each adapter then sub-batches internally with its own default batch size — OpenAI 256, BGE-M3 and E5 12 (the local models tune this via the adapter sub-table). The resulting vectors are then written to the store with batched upsert calls (Qdrant upserts 256 points per request).
| Param | Default |
|---|---|
| Embed sub-batch size (OpenAI) | 256 |
| Embed sub-batch size (BGE-M3 / E5) | 12 |
| Embed max concurrency | --jobs |
Local models are far more efficient on batches, thanks to CPU/GPU vectorization. API embedders amortize round-trips the same way, and use a bounded concurrency limit to respect rate limits.
Tune batch size via the embedder’s adapter sub-table or kwargs. See Performance.
Model identity is pinned into the archive
Section titled “Model identity is pinned into the archive”When stage 06 seals the .indx archive, the embedder’s identity is written into two places:
The archive-root manifest.json (the serialized Manifest model):
{ "embedding_model": "text-embedding-3-small", "embedding_dim": 1536, "components": { "embedder": "openai:text-embedding-3-small", "store": "qdrant" }}and a dedicated embeddings/manifest.json alongside the raw vector matrix in the expanded output directory:
ai-ready/├── handbook.indx # sealed archive (manifest + documents/chunks/relations jsonl + checksums)├── index.json├── chunks/└── embeddings/ ├── manifest.json # { model, dim, count, backend } └── vectors.f32 # contiguous little-endian float32 matrix (count × dim)This makes the archive self-describing: a consumer knows exactly which model produced the vectors. Vectors are stored as little-endian float32, so the matrix is count × dim.
Changing the embedder means re-embedding
Section titled “Changing the embedder means re-embedding”Vectors from one model are not comparable with vectors from another. So changing the embedder requires a full re-embed. This is reflected in the cache and resume behavior:
- With
--resume, changing the embedder invalidates only the Embed stage. Walk, Parse, Chunk, Relate, and Enrich outputs are reused from cache. Changing the parser, by contrast, invalidates Parse and everything downstream. - The resolved config snapshot (including the embedder name) is recorded in
index.json.metadataand the manifest for auditability.
# Switch embedders; everything upstream is reused, only vectors are recomputed.indx ./docs --out ./ai-ready --embedder e5 --resumeRelated pages
Section titled “Related pages”- Embed+Pack stage — what stage 06 does end to end.
- The
.indxarchive — full archive layout and manifest fields. - Extras reference — every
pip install indx[...]option. - Choosing a store — where the vectors land.
- Bring your own stack — how slots and protocols fit together.