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
| Field | Required | Description |
|---|---|---|
id | Yes | Unique string identifier (insert-or-replace key) |
fields | Yes | Map of field name → value. One entry per field you populate (dense, sparse, or multi-vector) |
meta | No | Arbitrary metadata object (returned in search results) |
filter | No | Key-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 type | Value 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.
Python
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.
Python
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.
| Precision | Storage | Speed | Accuracy |
|---|---|---|---|
binary | Smallest | Fastest | Lower |
int8 | Small | Fast | Good |
int8e | Small | Fast | Good (extended int8) |
int16 | Medium | Medium | Higher |
float16 | Medium | Medium | High |
float32 | Largest | Slower | Highest |
Python
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 workloadsfloat32: use when maximum recall accuracy is critical and memory is not a concernbinary: 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).
Python
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.
Python
collection.delete_object("doc1") # {"deleted": "doc1"}Delete Objects by Filter
Delete every object matching the given filter conditions.
Python
collection.delete_by_filter([{"category": {"$eq": "footwear"}}]) # {"deleted": <count>}For filter expressions, see Filtering: Operators.