Clients
| Client | Use |
|---|---|
chromadb.EphemeralClient() | In-memory; tests |
chromadb.PersistentClient(path="./chroma") | Embedded with local storage; single process |
chromadb.HttpClient(host="chroma", port=8000) | Client-server; production |
chromadb.AsyncHttpClient(...) | async apps (await calls) |
import chromadb
client = chromadb.HttpClient(host="chroma.internal", port=8000)
client.heartbeat()
client.get_version()
Collections
col = client.get_or_create_collection(
name="kb_chunks",
configuration={"hnsw": {"space": "cosine", "ef_construction": 200, "ef_search": 100}}, # 1.x
# 0.x: metadata={"hnsw:space": "cosine"}
embedding_function=None, # None if you always pass your own embeddings
)
client.list_collections()
client.get_collection("kb_chunks")
client.delete_collection("kb_chunks")
col.count()
col.peek(5) # first few items
col.modify(name="kb_chunks_old") # rename
Collection names: 3–512 characters, letters, digits, ., _, -; must start and end with a letter or digit.
Add, upsert, update
col.add(
ids=["doc42#0", "doc42#1"],
embeddings=[emb0, emb1],
documents=["chunk text 0", "chunk text 1"],
metadatas=[{"doc_id": "doc42", "source": "wiki", "year": 2026},
{"doc_id": "doc42", "source": "wiki", "year": 2026}],
)
col.upsert(ids=[...], embeddings=[...], documents=[...], metadatas=[...]) # insert or replace
col.update(ids=["doc42#0"], metadatas=[{"doc_id": "doc42", "reviewed": True}])
addwith an existing ID doesn't overwrite it; useupsertfor idempotent ingestion.- If you omit
embeddings, Chroma embedsdocumentswith the collection's embedding function. - Metadata values:
str,int,float,bool(newer versions add array support; check your version). NoNone, no nested dicts. - Very large batches can hit the server's maximum batch size:
client.get_max_batch_size()(newer versions) tells you the limit.
Query
res = col.query(
query_embeddings=[qvec],
n_results=10,
where={"source": "wiki"},
where_document={"$contains": "VPN"},
include=["documents", "metadatas", "distances"],
)
for i, d, m, dist in zip(res["ids"][0], res["documents"][0], res["metadatas"][0], res["distances"][0]):
print(i, round(dist, 3), m["doc_id"])
Results are lists per query embedding, hence [0]. include options: documents, metadatas, distances, embeddings (embeddings are large; include only when needed).
Metadata filters (where)
| Operator | Example |
|---|---|
| Equal | {"source": "wiki"} or {"source": {"$eq": "wiki"}} |
| Not equal | {"status": {"$ne": "archived"}} |
| Greater / less | {"year": {"$gte": 2025}}, $gt, $lt, $lte |
| In / not in | {"source": {"$in": ["wiki", "jira"]}}, $nin |
| AND | {"$and": [{"source": "wiki"}, {"year": {"$gte": 2025}}]} |
| OR | {"$or": [{"source": "wiki"}, {"source": "jira"}]} |
A where with two keys at the top level ({"a": 1, "b": 2}) raises an error; use $and.
Document filters (where_document)
| Operator | Example |
|---|---|
| Contains substring | {"$contains": "ORA-01555"} |
| Doesn't contain | {"$not_contains": "deprecated"} |
| Regex (newer versions) | {"$regex": "KB50[0-9]{5}"} |
| Combine | {"$and": [{"$contains": "VPN"}, {"$not_contains": "legacy"}]} |
$contains is case-sensitive substring matching, not ranked full-text search.
Get (no vector search)
col.get(ids=["doc42#0"], include=["documents", "metadatas"])
col.get(where={"doc_id": "doc42"}, include=["metadatas"])
col.get(limit=500, offset=1000, include=["embeddings"]) # paging for exports
Delete
col.delete(ids=["doc42#0", "doc42#1"])
col.delete(where={"doc_id": "doc42"})
Caution
In some versions
col.delete()with neitheridsnorwherecould delete everything or raise; never call it without arguments. To empty a collection, delete and recreate it.
Distances
| Space | Value | Similarity |
|---|---|---|
l2 (default) | Squared L2 | lower is better |
cosine | 1 − cosine similarity | 1 - distance |
ip | 1 − dot product | 1 - distance |
Set space explicitly when creating a collection; it can't be changed later.
Common errors
| Error | Cause / fix |
|---|---|
Collection [x] does not exist | Name typo; tenant/database mismatch on the client |
Embedding dimension 3072 does not match collection dimensionality 1536 | Wrong model; one model per collection |
Expected where to have exactly one operator | Two keys at top level; wrap in $and |
Expected metadata value to be a str, int, float or bool | None or nested value in metadata |
Batch size ... exceeds maximum batch size | Split batches |
| Client/server API errors after upgrade | Client and server versions differ |
Tenants and databases
The server has tenants and databases (defaults default_tenant / default_database). Pass them when you use more than the default:
client = chromadb.HttpClient(host="chroma", port=8000, tenant="team-a", database="kb")
Prevent mistakes next time
- Use
upsertwith deterministic IDs (doc_id#chunk). - Set
spaceexplicitly; passquery_embeddingsfrom the same model as ingestion. - Keep metadata flat and typed; no
None. - Never call
delete()without a selector.