LlamaIndex StorageContext mismatch — reloading indexes with wrong embed_model (2026) — Fix Guide (2026) | AI Error Hub
Home Providers LlamaIndex Storage context mismatch
LlamaIndex Storage · StorageContext Severity: High HTTP n/a

LlamaIndex StorageContext — index reloaded with mismatched embed_model

Persistence and reload look simple until a config change swaps the embed_model behind the scenes. Retrieval scores go from meaningful to random with no visible error.

By Sana K. · Cloud AI Reliability Engineer Published 2026-07-26 Verified 2026-07-26

Quick fix (TL;DR)

Resolution: StorageContext holds the docstore, index_store, and vector_store. Reloading via load_index_from_storage requires the same embed_model as was used during indexing. Fix by (a) persisting embed_model config alongside the index, (b) explicitly passing embed_model on reload, (c) validating dimension match and model identity, and (d) using an ingestion pipeline's docstore for incremental updates.

Real error messages you'll see

These are the exact strings returned by the LlamaIndex framework and its integrations when this error occurs. Copy-paste-searching any of them should land on this page.

Retrieval scores random after reload
# Ingested with text-embedding-3-small
# Reloaded and Settings.embed_model changed to text-embedding-3-large
# Queries return nodes with cosine ~0.1 for what should be exact matches
Dimension mismatch on query
ValueError: Query vector dimension 1536 does not match index dimension 3072.
# text-embedding-3-small (1536-d) index queried with text-embedding-3-large (3072-d)
Load from persist_dir fails
FileNotFoundError: [Errno 2] No such file or directory: './storage/docstore.json'
# persist_dir path wrong, or vector_store was remote and not persisted locally

Reference

What lives in StorageContext

StoreContainsPersistence
docstoreOriginal Documents / Nodes with metadataJSON file in persist_dir
index_storeIndex-specific data (tree structure, keyword tables)JSON file in persist_dir
vector_storeEmbeddings + metadataDepends on backend (local JSON or remote)
graph_storeKnowledge graph (for KG indexes)JSON file in persist_dir
property_graph_storeProperty graph nodes/edgesJSON or Neo4j etc.

Root causes, ranked by frequency

Based on developer reports across LlamaIndex forums, GitHub issues, and Discord community during 2025–2026.

  • 26%
    Settings.embed_model changed between ingest and reload. Config drift; no error on reload.
  • 18%
    Vector store dimension differs from current embed_model. Hard error; the loud version.
  • 14%
    persist_dir path different or missing. Fresh index built accidentally; data loss.
  • 10%
    Remote vector store not preserved on persist. persist saves docstore locally but relies on remote server for vectors.
  • 8%
    Multiple environments using different Settings. Dev uses one embed_model; prod uses another.
  • 7%
    Custom node parser not deterministic. Same doc parsed differently across sessions; node IDs drift.
  • 7%
    Docstore corruption from concurrent writes. Two processes persisting to same dir.
  • 10%
    load_index_from_storage without explicit embed_model. Falls back to Settings default; may be wrong.

Fixes — copy-paste solutions

Fix #1

Persist embed_model config alongside the index

Config lives beside data — never trust global Settings.

Write a small metadata file next to persist_dir that records the embed_model class, model name, and dimension. Validate on load.

safe_persist_and_load.py
import json
from pathlib import Path
from llama_index.core import VectorStoreIndex, StorageContext, Settings, load_index_from_storage
from llama_index.embeddings.openai import OpenAIEmbedding

def persist_index(index, persist_dir: str):
    """Persist index AND its embedding config for safe reload."""
    p = Path(persist_dir)
    p.mkdir(parents=True, exist_ok=True)

    # Standard index persist
    index.storage_context.persist(persist_dir=persist_dir)

    # Also record the embed_model config
    embed_model = index._embed_model
    config = {
        "embed_model_class": type(embed_model).__name__,
        "embed_model_name": getattr(embed_model, "model_name", None) or getattr(embed_model, "model", None),
        "embed_dim": getattr(embed_model, "embed_dim", None),
        # Include any provider-specific config that affects embeddings
        "additional_kwargs": getattr(embed_model, "additional_kwargs", {}),
    }
    (p / "embed_config.json").write_text(json.dumps(config, indent=2))
    print(f"Persisted index and config to {persist_dir}")


def load_index_safely(persist_dir: str, embed_model=None):
    """Load index, verifying embedding config matches what was persisted."""
    p = Path(persist_dir)

    # Load the config
    config_path = p / "embed_config.json"
    if not config_path.exists():
        raise ValueError(
            f"No embed_config.json in {persist_dir}. This index was persisted "
            f"without safety metadata; cannot verify embed_model match."
        )
    saved_config = json.loads(config_path.read_text())

    # Verify current embed_model matches
    if embed_model is None:
        embed_model = Settings.embed_model

    current_class = type(embed_model).__name__
    current_model = getattr(embed_model, "model_name", None) or getattr(embed_model, "model", None)

    if current_class != saved_config["embed_model_class"]:
        raise ValueError(
            f"embed_model class mismatch: index was built with {saved_config['embed_model_class']}, "
            f"but current is {current_class}. Retrieval will be broken."
        )
    if current_model != saved_config["embed_model_name"]:
        raise ValueError(
            f"embed_model name mismatch: index was built with {saved_config['embed_model_name']}, "
            f"but current is {current_model}. Retrieval will be broken."
        )

    storage_context = StorageContext.from_defaults(persist_dir=persist_dir)
    return load_index_from_storage(storage_context, embed_model=embed_model)

# Usage
embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.embed_model = embed_model

# Persist
persist_index(index, "./storage/my_index")

# Later — safe load
index = load_index_safely("./storage/my_index", embed_model=embed_model)
The embed_config.json pattern is a small safety belt with large payoff. It converts silent quality regressions into loud errors — always preferable.
Fix #2

Pass embed_model explicitly to load_index_from_storage

Never trust global Settings for critical config.

On load, always pass embed_model= explicitly. Even if Settings happen to be correct, being explicit prevents accidents.

explicit_reload.py
from llama_index.core import StorageContext, load_index_from_storage, Settings
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI

# --- Explicit reload with all config ---
def rebuild_query_engine(persist_dir: str):
    """Rebuild query engine from persisted storage with explicit config."""

    # Recreate the exact embed_model used at ingest
    embed_model = OpenAIEmbedding(
        model="text-embedding-3-small",
        embed_batch_size=32,
    )

    llm = OpenAI(model="gpt-4o-mini")

    # Set as Settings (helpful for downstream)
    Settings.embed_model = embed_model
    Settings.llm = llm

    # Load with explicit reference (do not rely on Settings)
    storage_context = StorageContext.from_defaults(persist_dir=persist_dir)
    index = load_index_from_storage(
        storage_context,
        embed_model=embed_model,      # ← explicit
        llm=llm,                       # ← explicit
    )

    return index.as_query_engine(similarity_top_k=6, llm=llm)

# --- For remote vector stores, reload the store connection ---
from llama_index.vector_stores.chroma import ChromaVectorStore
import chromadb

def reload_with_remote_vector_store(persist_dir: str):
    # Reconnect to the remote vector store
    client = chromadb.PersistentClient(path="./chroma_db")
    collection = client.get_collection("my_docs")
    vector_store = ChromaVectorStore(chroma_collection=collection)

    # persist_dir only contains docstore + index_store; vector_store is remote
    storage_context = StorageContext.from_defaults(
        vector_store=vector_store,     # ← from remote
        persist_dir=persist_dir,        # ← docstore, index_store from local
    )

    embed_model = OpenAIEmbedding(model="text-embedding-3-small")
    return load_index_from_storage(storage_context, embed_model=embed_model)
Global Settings is convenient for prototyping but a foot-gun in production. Explicit config is worth the extra line — it makes intent auditable.
Fix #3

Detect dimension mismatch early with a smoke test

A single query catches most reload bugs before real traffic.

After every load, run a canonical smoke query and check that a known-good node comes back with a reasonable score. Fail loud if it does not.

smoke_test_load.py
from llama_index.core import VectorStoreIndex, load_index_from_storage
from typing import Tuple

# --- Canonical smoke test defined at ingest time, stored with index ---

def run_smoke_test(index, canonical_query: str, expected_source: str,
                  min_score: float = 0.4) -> Tuple[bool, str]:
    """Return (passed, message). Raises on hard failures like dimension mismatch."""
    try:
        retriever = index.as_retriever(similarity_top_k=5)
        nodes = retriever.retrieve(canonical_query)
    except ValueError as e:
        if "dimension" in str(e).lower():
            return False, f"DIMENSION MISMATCH: {e}"
        raise

    if not nodes:
        return False, "Retriever returned 0 nodes — likely empty index or filter issue"

    top_score = nodes[0].score
    if top_score < min_score:
        return False, (
            f"Top score {top_score:.3f} below threshold {min_score:.3f}"
            f"embed_model may be different from ingest"
        )

    top_source = nodes[0].metadata.get("source", "")
    if expected_source not in top_source:
        return False, (
            f"Top result {top_source} does not match expected {expected_source}. "
            f"Index may be corrupted or embed_model wrong."
        )

    return True, f"OK — top score {top_score:.3f}, source {top_source}"

# --- Usage after every load ---
index = load_index_safely("./storage/my_index", embed_model=embed_model)

passed, msg = run_smoke_test(
    index,
    canonical_query="canonical smoke test query",
    expected_source="doc_smoke_canonical_source",
    min_score=0.4,
)
if not passed:
    raise RuntimeError(f"Index smoke test failed: {msg}")
print(f"Smoke test: {msg}")
Ship the smoke test with your deploy pipeline. Any deploy that changes embed_model config without also rebuilding the index will fail the smoke test — catch it before serving real users.

Prevention checklist

Ship these seven safeguards once and this error stops appearing in your logs.

  • Persist embed_model config alongside the index; validate on every load.
  • Pass embed_model= explicitly to load_index_from_storage.
  • For remote vector stores, remember persist_dir only saves docstore + index_store.
  • Run a canonical smoke test after every load; fail deploy if it fails.
  • Track embed_model as part of your infra config; changes require index rebuild.
  • Version-pin embedding models with their dates (text-embedding-3-small is versioned).
  • For CI, include a "rebuild index" step whenever embed_model or corpus changes.

Frequently asked questions

No — vectors were computed with the old embed_model. Queries with a different embed_model produce vectors in a different space; cosine similarity is meaningless. Rebuild is required.
Use index.insert(node) or index.insert_nodes(nodes) — the new nodes are embedded with the current embed_model. Just make sure it matches the original.
Docstore stores raw text and metadata; index_store stores small structure. For 10K nodes with ~500 tokens each, expect 20-50 MB. Vector store space is separate and depends on backend.
Not directly — persist writes local files. For cloud storage, either use a remote vector store (Pinecone, Qdrant Cloud) and persist docstore locally + sync to cloud, or use LlamaCloud managed indexes.
For a local vector store, index is gone; rebuild from source. For a remote vector store, vectors are still there; you can rebuild the docstore by re-parsing documents (but node IDs will differ, breaking any external references).

Get the weekly AI-error digest

New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.