Troubleshooting
On this page
This page documents error conditions and quirks that were confirmed by reading the source code, including two apparent defects worth knowing about before you hit them in production.
Chroma add() legacy style: NameError {#chroma-add-legacy-style-namerror}
Symptom: calling ChromaVectorDatabase.add() the way its own docstring shows for backward
compatibility —
db.add(ids=["doc1", "doc2"], documents=["Hello world", "ML"], metadatas=[{"source": "web"}, {}])
— raises NameError, not a successful upsert.
Why: add()'s actual signature is def add(self, chunks, embeddings=None). There is no
ids, documents, or metadatas parameter, so passing them as keyword arguments would itself
raise TypeError: add() got an unexpected keyword argument 'ids' — that's the first way this
fails. If, hypothetically, a caller worked around that by monkeypatching or the surrounding
code happened to have module/enclosing-scope variables literally named documents and
metadatas, the "legacy" branch inside add() reads:
ids = chunks
documents = documents # not a parameter — refers to an outer-scope or undefined name
metadatas = [sanitize_metadata(m) for m in (metadatas or [])] # same problem
which is a self-referential assignment to an undefined name in the normal case, causing
NameError: name 'documents' is not defined (and likewise for metadatas) the moment that
branch executes.
Workaround: use the unified calling style instead, passing a list of DocumentChunk
objects:
from zerda.chunks import DocumentChunk
chunks = [
DocumentChunk(chunk_id="doc1", doc_id="doc1", text="Hello world", metadata={"source": "web"}),
DocumentChunk(chunk_id="doc2", doc_id="doc2", text="ML", metadata={}),
]
db.add(chunks)
This path is fully functional and is what the rest of this documentation recommends throughout.
FAISS: IndexIVFFlat errors before training
Symptom: using index_type='ivf' and calling add()/search() while total vectors are
still below DEFAULT_IVF_TRAIN_SIZE = 100 can surface a FAISS-level error about an untrained
index.
Why: _should_train_ivf() only triggers training once cumulative vectors reach 100. Below
that, self.index.is_trained stays False, and FAISS's own IndexIVFFlat.add()/search()
require training first.
Workaround: either use index_type='flat' for small datasets, or add at least 100 vectors
in your first batch(es) so training triggers, or add vectors in a batch large enough to satisfy
the threshold on the very first call.
Pinecone: new index creation can block for minutes
Symptom: PineconeVectorDatabase(...) against a new index_name appears to "hang" for up
to five minutes, or raises TimeoutError.
Why: _wait_for_index_ready() polls once per second until Pinecone reports the newly
created index as ready, up to DEFAULT_INDEX_READY_TIMEOUT = 300 seconds.
Workaround: create the index ahead of time (out of the hot path of request handling), or budget for a multi-minute first call when standing up a new index.
Pinecone: distance_metric='ip' is rejected
Symptom: PineconeVectorDatabase(distance_metric='ip') raises
ValueError: Unsupported distance metric: 'ip'.
Why: 'ip' exists in METRIC_MAPPING (mapped to 'dotproduct') but is absent from
SUPPORTED_METRICS, and _validate_metric() checks against SUPPORTED_METRICS first.
Workaround: use 'dotproduct' or 'dot' instead of 'ip' for this backend specifically.
(Note this differs from FAISS and Chroma, both of which do accept 'ip'.)
Pinecone: get_stats() can silently change shape
Symptom: code that reads stats['total_vector_count'] sometimes gets a KeyError.
Why: PineconeVectorDatabase.get_stats() catches every exception internally and returns
{'error': str(e)} instead of propagating — unlike FAISS/Chroma's get_stats(), which don't
have a try/except around their return.
Workaround: check for an 'error' key before assuming the full stats shape is present:
stats = db.get_stats()
if "error" in stats:
... # handle the failure
else:
print(stats["total_vector_count"])
"No embedder available" ValueError
Symptom: calling add()/search()/add_chunk() with a text query or chunk that has no
pre-computed embedding, on a database constructed with embedder=None.
Why: every backend requires either an embedder at construction time or fully
pre-computed embeddings passed explicitly to skip embedding generation.
Workaround: either pass an embedder to the constructor, or always supply pre-computed
embeddings=... to add()/add_chunk() and only ever query with vectors (np.ndarray), never
raw text, when embedder is None.
Dimension mismatch ValueError
Symptom: ValueError: Embedding dimension mismatch. Expected X, got Y.
Why: all three backends fix embedding_dim at construction/first-use time and validate
every subsequent add()/search() call's embeddings against it.
Workaround: ensure a single embedder/model (and thus a single output dimension) is used consistently for a given database instance; don't mix embedding models across calls.