Skip to Content
v2ConceptsObjects

Objects

Objects are the fundamental data units stored in a collection. Each object has a unique id, a fields map holding one value per field you populate, and optional meta and filter.

Object Structure

FieldRequiredDescription
idYesUnique string identifier (insert-or-replace key)
fieldsYesMap of field name → value. One entry per field you populate (dense, sparse, or multi-vector)
metaNoArbitrary metadata object (returned in search results)
filterNoKey-value tags used to filter results during search

meta vs filter: Use meta for data you want returned with results (titles, URLs, display text). Use filter for fields you intend to query against; any key not declared in filter at upsert time cannot be used in a filter expression later.

Per-field value formats

Field typeValue format
vector[0.1, 0.2, ...] — a flat array of floats
sparse{"indices": [3, 17], "values": [0.9, 0.5]}
multi_vector[[...], [...], ...] — an array of equal-length float arrays

Upserting Objects

upsert is insert-or-replace by id. You don’t have to fill every field on every object. Set whatever subset you have.

collection = client.get_collection("my_collection") collection.upsert([ { "id": "doc1", "meta": {"title": "Introduction to ML"}, "filter": {"category": "tech", "year": 2024}, "fields": {"embedding": [0.12, -0.34, 0.89, ...]}, }, { "id": "doc2", "meta": {"title": "Deep Learning Basics"}, "filter": {"category": "tech", "year": 2023}, "fields": {"embedding": [0.55, 0.21, -0.10, ...]}, }, ])

Cosine vector / multi_vector values are L2-normalized client-side before sending; the original norm is stored, so get_objects returns the original (pre-normalization) vectors.

Combining Field Types in One Object

A single object can populate any combination of the collection’s fields in one call. They’re stored together under the same id and can be searched independently or fused together.

collection.upsert([ { "id": "p1", "meta": {"name": "Wireless Headphones", "price": 99}, "filter": {"category": "electronics"}, "fields": { "embedding": [0.1, 0.2, 0.3, ...], # dense "keywords": {"indices": [3, 17, 42], "values": [0.9, 0.5, 0.2]}, # sparse "colbert": [[0.1, ...], [0.2, ...]], # multi-vector }, }, { "id": "p2", "meta": {"name": "Running Shoes"}, "filter": {"category": "footwear"}, "fields": { # An object may set only SOME fields — here just dense + sparse. "embedding": [0.5, 0.4, 0.3, ...], "keywords": {"indices": [5, 17, 90], "values": [0.7, 0.6, 0.1]}, }, }, ])

Sparse field values. indices and values must have the same length: each position in indices maps to the weight at the same position in values. The sparse_model you set at collection creation controls how Endee interprets these: use endee_bm25 to send TF weights only (Endee applies IDF server-side), or default to send final scores as-is for SPLADE or custom BM25 models. See the Sparse Vectors (BM25) guide.

Maximum batch size is 10,000 objects per upsert call. Validation runs client-side and fails fast (no duplicate ids in a batch, dense/multi dimensions must match the field, sparse indices/values lengths must match, filter keys ≤ 128 bytes / string values ≤ 1024 bytes).

Precision

The precision parameter (set per vector / multi_vector field in params) controls how vectors are stored internally. Lower precision reduces memory and speeds up search at the cost of some accuracy.

PrecisionStorageSpeedAccuracy
binarySmallestFastestLower
int8SmallFastGood
int8eSmallFastGood (extended int8)
int16MediumMediumHigher
float16MediumMediumHigh
float32LargestSlowerHighest
from endee import Precision # int16 — recommended for most use cases client.create_collection( name="my_collection", fields=[{"name": "embedding", "type": "vector", "params": {"dimension": 384, "space_type": "cosine", "precision": Precision.INT16}}], ) # float32 — maximum accuracy client.create_collection( name="precise_collection", fields=[{"name": "embedding", "type": "vector", "params": {"dimension": 384, "space_type": "cosine", "precision": Precision.FLOAT32}}], ) # binary — minimum memory client.create_collection( name="large_collection", fields=[{"name": "embedding", "type": "vector", "params": {"dimension": 384, "space_type": "cosine", "precision": Precision.BINARY}}], )

Recommendations:

  • int16: best balance of speed, memory, and accuracy for most use cases (recommended)
  • int8 (default) / int8e: faster than int16 with slightly lower accuracy; good for latency-sensitive workloads
  • float32: use when maximum recall accuracy is critical and memory is not a concern
  • binary: use for very large collections where memory is the primary constraint

Precision is set per field at collection creation time and cannot be changed without rebuilding the field.


Get Objects by ID

Retrieve full stored objects by id, including meta, filter, and the stored vectors (vectors, sparses, multi_vectors).

objs = collection.get_objects(["doc1", "doc2"]) # [ # {"id": "doc1", "meta": {...}, "filter": {...}, # "vectors": {"embedding": [...]}, # original, pre-normalization # "sparses": {"keywords": {"indices": [...], "values": [...]}}, # "multi_vectors": {"colbert": [[...], [...]]}}, # ... # ]

Delete Object by ID

Deletion is irreversible.

collection.delete_object("doc1") # {"deleted": "doc1"}

Delete Objects by Filter

Delete every object matching the given filter conditions.

collection.delete_by_filter([{"category": {"$eq": "footwear"}}]) # {"deleted": <count>}

For filter expressions, see Filtering: Operators.