Collection is the high-level, Chroma-compatible surface in ELIPS. Obtain one from a Client via client.get_or_create_collection("docs"). It exposes the canonical verbs — add, upsert, get, update, delete, query, count, and peek — plus ELIPS-specific maintenance extensions.
Overview
A Collection is a named set of records that share a dimension and distance metric. Each record carries an id (its key), a vector embedding, optional document text, and arbitrary metadata. Reads return typed Hit and Row objects whose fields are .key, .distance, .meta, and .text.
Opening a collection
Collections live inside a Client. Open an in-memory Client (which requires a dimension) or an on-disk PersistentClient, then get or create a collection by name.
import elips
# Ephemeral, in-memory
client = elips.Client(dimension=384, metric="cosine")
# Or persistent on disk (dimension restored from the manifest when reopening)
client = elips.PersistentClient("/tmp/db", dimension=384)
docs = client.get_or_create_collection("docs")add() → str | list[str]
# Column form (Chroma)
collection.add(
*,
ids: str | list[str] | None = None,
embeddings: Sequence | None = None,
documents: str | list[str] | None = None,
metadatas: dict | list[dict] | None = None,
)
# Single-record form (ELIPS convenience)
collection.add(
*,
embedding: Sequence[float] | None = None, # alias: vector=
text: str | None = None,
metadata: dict | None = None,
id: str | None = None,
)Insert one or more records. documents= are raw text strings (or a DocumentAttachment). Returns the assigned id as a str for a single record, or a list[str] for the column form. add has upsert semantics: re-adding the same id overwrites the existing record.
client = elips.Client(dimension=2)
docs = client.get_or_create_collection("docs")
# Column form — bulk insert
ids = docs.add(
ids=["doc-1", "doc-2"],
embeddings=[[1.0, 0.0], [0.0, 1.0]],
documents=["Alpha design note", "Beta runbook"],
metadatas=[{"kind": "design"}, {"kind": "ops"}],
)
# Single-record form
rid = docs.add(embedding=[0.5, 0.5], text="Gamma spec", metadata={"kind": "spec"})
client.close()upsert() → str | list[str]
Identical to add() in ELIPS — both have upsert semantics. Provided for Chroma compatibility, to make the intent to overwrite-or-insert explicit.
docs.upsert(
ids=["doc-1"],
embeddings=[[0.9, 0.1]],
metadatas=[{"kind": "design", "rev": 2}],
)get() → list[Row]
collection.get(
ids: str | list[str] | None = None,
*,
where: dict | None = None,
limit: int | None = None,
offset: int = 0,
include: list[str] | None = None,
) -> list[Row]Fetch records by id, or scan with a metadata where filter. Returns typed Row objects. Pass include=["embeddings"] to hydrate stored vectors.
# By id
rows = docs.get(ids=["doc-1", "doc-2"])
print(rows[0].key, rows[0].text, rows[0].meta)
# Scan with a metadata filter + pagination
page = docs.get(where={"kind": "design"}, limit=50, offset=0)
# Include stored vectors
rows = docs.get(ids=["doc-1"], include=["embeddings"])
print(rows[0].vector)update() → None
collection.update(
*,
ids: str | list[str],
embeddings: Sequence | None = None,
documents: str | list[str] | None = None,
metadatas: dict | list[dict] | None = None,
)Update existing records in place. Raises NotFoundError if an id does not exist — unlike add() / upsert(), it never creates new records.
docs.update(
ids=["doc-1"],
metadatas=[{"kind": "design", "status": "approved"}],
)delete() → int
collection.delete(
ids: str | list[str] | None = None,
*,
where: dict | None = None,
) -> intRemove records by id, by metadata where filter, or both. Returns the count of records removed. At least one of ids or where is required — there is no "delete everything" shorthand.
# By id
removed = docs.delete(ids=["doc-1", "doc-2"])
# By metadata filter
removed = docs.delete(where={"kind": "ops"})
print(removed)query() → list[Hit] | list[list[Hit]]
collection.query(
query_embeddings: Sequence | None = None,
query_texts: str | list[str] | None = None,
*,
n_results: int = 10, # aliases: k=, limit=
where: dict | None = None,
include: list[str] | None = None,
max_distance: float | None = None,
lexical_weight: float = 0.25,
) -> list[Hit] | list[list[Hit]]Nearest-neighbour search. A single query returns a flat list[Hit]; a batch of queries returns a list[list[Hit]] aligned to the inputs. Hits are sorted by ascending distance.
query_embeddings— one query vector, or a list of vectors for a batch.query_texts— query text, embedded by the collection's embedder. Pass bothquery_embeddingsandquery_textsfor hybrid retrieval, blended bylexical_weight.n_results— maximum hits (aliasesk/limit).where— metadatafilter.max_distance— drop hits beyond this distance.include— e.g.["embeddings"]to return vectors on each hit.
client = elips.Client(dimension=2)
docs = client.get_or_create_collection("docs")
docs.add(
ids=["a", "b"],
embeddings=[[1.0, 0.0], [0.0, 1.0]],
documents=["Alpha", "Beta"],
metadatas=[{"kind": "a"}, {"kind": "b"}],
)
# Single vector query -> list[Hit]
hits = docs.query(query_embeddings=[1.0, 0.0], n_results=1)
print(hits[0].text) # Alpha
print(hits[0].distance) # ~ 0.0
# Filtered query
hits = docs.query(query_embeddings=[0.0, 1.0], n_results=5, where={"kind": "b"})
print(hits[0].text) # Beta
# Batch query -> list[list[Hit]]
batch = docs.query(query_embeddings=[[1.0, 0.0], [0.0, 1.0]], n_results=1)
print(batch[0][0].text, batch[1][0].text) # Alpha Beta
# Hybrid (vector + text)
hits = docs.query(query_embeddings=[1.0, 0.0], query_texts="alpha", lexical_weight=0.3)
client.close()count() → int
Return the number of live (non-tombstoned) records.
print(docs.count()) # e.g. 2peek() → list[Row]
Return the first n records (default 10) as Row objects — a quick way to inspect a collection's contents.
for row in docs.peek(n=5):
print(row.key, row.meta)ELIPS extensions
Beyond the Chroma surface, a Collection exposes maintenance and introspection methods specific to ELIPS.
explain(vector, ...)→QueryPlan— return the planner's decision for a hypothetical query without running it.health()→CollectionHealth— a point-in-time snapshot (live count, dimension, metric, tombstone ratio, read-only / sealed state).freeze(frozen=True)— refuse writes without reopening the database read-only;freeze(False)re-enables them.vacuum()— reclaim graph nodes held by deleted records.rebuild()— rebuild the HNSW graph from the record store (higher recall than incremental inserts; run after a bulk load).compress()/train()— train the quantization codebook for the collection.
Properties: name, native (the low-level Vault escape hatch), pending_removals, read_only, and sealed.
# Health snapshot
h = docs.health()
print(h.live, h.dimension, h.metric, h.tombstone_ratio)
# Maintenance after a bulk load
docs.rebuild()
docs.vacuum()
# Freeze writes during a read-only serving window
docs.freeze(True)
docs.freeze(False)
# Drop to the native handle for a low-level capability
vault = docs.nativeResult types
Reads return typed records, never raw dicts. Both types live in the models reference.
Hit(key, distance, meta, document=None, vector=None, ...)— returned byquery(). Fields:.key,.distance,.meta, and.text(shortcut fordocument.text).Row(key, meta, document=None, vector=None, ...)— returned byget()andpeek(). Fields:.key,.meta,.text, and.vector(when included).
Note the field names: records expose .key and .meta — not .id or .metadata.
hits = docs.query(query_embeddings=[1.0, 0.0], n_results=3)
for hit in hits:
print(hit.key, hit.distance, hit.meta, hit.text)
rows = docs.get(ids=["a", "b"], include=["embeddings"])
for row in rows:
print(row.key, row.meta, row.vector)Thread safety
A Collection carries no locks of its own. Thread safety is provided by the underlying native Vault and Database, which serialize concurrent writes and allow concurrent reads. If you pass a Python embedder, make sure the callable is itself thread-safe.