elips/docs
Reference · Python

Collection

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.

python
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]

python
# 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.

python
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.

python
docs.upsert(
    ids=["doc-1"],
    embeddings=[[0.9, 0.1]],
    metadatas=[{"kind": "design", "rev": 2}],
)

get() → list[Row]

python
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.

python
# 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

python
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.

python
docs.update(
    ids=["doc-1"],
    metadatas=[{"kind": "design", "status": "approved"}],
)

delete() → int

python
collection.delete(
    ids: str | list[str] | None = None,
    *,
    where: dict | None = None,
) -> int

Remove 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.

python
# 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]]

python
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 both query_embeddings and query_texts for hybrid retrieval, blended by lexical_weight.
  • n_results — maximum hits (aliases k / limit).
  • where — metadata filter.
  • max_distance — drop hits beyond this distance.
  • include — e.g. ["embeddings"] to return vectors on each hit.
python
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.

python
print(docs.count())   # e.g. 2

peek() → list[Row]

Return the first n records (default 10) as Row objects — a quick way to inspect a collection's contents.

python
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.

python
# 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.native

Result types

Reads return typed records, never raw dicts. Both types live in the models reference.

  • Hit(key, distance, meta, document=None, vector=None, ...) — returned by query(). Fields: .key, .distance, .meta, and .text (shortcut for document.text).
  • Row(key, meta, document=None, vector=None, ...) — returned by get() and peek(). Fields: .key, .meta, .text, and .vector (when included).

Note the field names: records expose .key and .meta — not .id or .metadata.

python
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.