Asteroid Cloud

Asteroid Cloud is a memory-friendly vector database for applications that need fast similarity search at lower infrastructure cost. You bring embeddings; Asteroid stores vectors and JSON metadata, then serves k-NN search with optional metadata filters.

In the pilot, you access your Asteroid instance over HTTPS with an API key. Use the Python client or call the HTTP API directly from any language.

Your endpoint and API key are provided when your pilot is set up. You choose the index dimension yourself — see Create your index. Don't have them yet? Request a pilot.

Before you start

For your pilot instance, we provide:

You bring:

One pilot instance is one vector index. Insert vectors, attach metadata, and search against that index over the API.

Quickstart

Install the client, connect, create your index, insert vectors, and search. The example below uses 3-dimensional toy vectors so you can copy and run it directly. Pick one path: bring your own vectors (below) or bring text (next section) — each creates an index at its own dimension, and an instance holds one index at a time.

Install the Python client (requires Python 3.9+):

pip install lsmvec-client
from lsmvec_client import Client

client = Client(api_key="sk-live-...",
                base_url="https://api.lsmvec.com")
assert client.health()

# Create the index once, at your embedding dimension (3 here for the toy vectors).
client.create_index(dim=3, metric="l2")

client.insert(1, [0.10, 0.20, 0.30], metadata={"category": "docs"})
client.insert(2, [0.12, 0.18, 0.33], metadata={"category": "blog"})

hits = client.search([0.11, 0.19, 0.31], k=2)
for h in hits:
    print(h.id, h.distance)

One dimension per index. The toy vectors above are 3-dimensional for readability. Every inserted and searched vector must match the dimension you set with create_index. To switch dimensions (e.g. for the text path below), call delete_index() first, then create_index() again — see Create your index.

Storing text instead of vectors

Storing text? Install the embedding extra. The Python client embeds text before sending vectors to Asteroid.

A separate, 384-d index. This path uses a dim=384 index (bge-small's output). Run create_index(dim=384, metric="l2") first. This is a different index from the toy-vector Quickstart above — if you already created one at another dimension in this instance, call delete_index() first.

pip install "lsmvec-client[embed]"
from lsmvec_client import Client
from lsmvec_client.ingest import LocalEmbedder, ingest_text, search_text

client   = Client(api_key="sk-live-...", base_url="https://api.lsmvec.com")
client.create_index(dim=384, metric="l2")            # once, before any write
embedder = LocalEmbedder("BAAI/bge-small-en-v1.5")   # 384-d, runs locally

ingest_text(client, "doc-1", "Refunds are processed within 30 days.", embedder, start_id=0)
hits = search_text(client, "how long do refunds take?", embedder, k=3)
print(hits[0].text)

Full flow in Embeddings.

Create your index

Set your index's dimension and distance metric once, before your first write. The dimension must match your embedding model's output (e.g. 384 for bge-small-en-v1.5, 1536 for OpenAI text-embedding-3-small). Supported range is 1–4000, which covers common embedding sizes (384, 768, 1024, 1536, 3072). Pick the metric — l2 or cosine — to match how your embeddings were trained.

client.create_index(dim=384, metric="l2")   # "l2" or "cosine"

The dimension is fixed once set. Inserting or searching before you create the index returns 409 index_not_initialized; calling create_index again returns 409 index_already_initialized.

Check the current state

client.get_index()   # {"initialized": True, "dim": 384, "metric": "l2"}

Re-dimension (destructive)

There is no in-place dimension change. To switch dimensions, delete the index and create a new one. This permanently removes all vectors and payloads — there is no undo.

client.delete_index()                          # wipe -> uninitialized
client.create_index(dim=768, metric="cosine")  # re-create at a new dimension

Insert & upsert vectors

insert adds a vector by ID with optional JSON metadata. upsert inserts or replaces the vector for an existing ID.

Use sequential IDs. Asteroid keys vectors by sequential, dense integer IDs — assign them in order starting at 0 (0, 1, 2, …). If your records are keyed by hashes, timestamps, or other large/sparse values, keep that mapping in your application and hand Asteroid a sequential counter; large or sparse IDs are not recommended on the pilot.

client.insert(1, vector, metadata={"title": "intro", "year": 2026})
client.upsert(1, new_vector)   # insert or replace the vector for ID 1

Fetch & delete

rec = client.get(1)       # {"id": 1, "vector": [...]}
client.delete(1)

Vectors are stored with SQ8 compression. get returns a dequantized vector, so values may differ slightly from the original input.

Metadata & payloads

Attach JSON metadata to each vector: document text, source, tags, timestamps, tenant IDs, or any fields your app needs for retrieval. Read, replace, or merge metadata without changing the vector.

client.get_payload(1)                       # -> {...}
client.set_payload(1, {"category": "docs"}) # replace
client.merge_payload(1, {"views": 42})      # merge-patch

On an existing id, a non-empty payload replaces the stored payload, an explicit empty {} clears it, and omitting metadata on insert/upsert leaves the existing payload unchanged. set_payload always replaces; merge_payload applies an RFC-7396 merge-patch (a null value deletes a key).

Search returns the nearest vector IDs by distance. Increase ef_search when you want higher recall and can spend more time per query.

hits = client.search(query_vector, k=10, ef_search=128)
for h in hits:
    print(h.id, h.distance)

Filter by metadata

Combine vector search with metadata filters in one query. Supported operators:

Use caseFilter
Match one field{"source": {"$eq": "handbook"}}
Filter by time or version{"year": {"$gte": 2026}}
Match one of several tags{"tag": {"$in": ["docs", "support"]}}
Combine conditions{"$and": [{"category": {"$eq": "docs"}}, {"year": {"$gte": 2026}}]}
hits = client.search(
    query_vector, k=10,
    filter={"$and": [
        {"category": {"$eq": "docs"}},
        {"year": {"$gte": 2026}},
    ]},
)

Embeddings

The Python client embeds text before sending vectors to Asteroid. It chunks documents, embeds each chunk, and stores the vectors with payloads.

For raw documents, start here with ingest_text. Use Bulk & batch import only when you already have vectors.

pip install "lsmvec-client[embed]"

Connect

from lsmvec_client import Client
client = Client(api_key="sk-live-...",
                base_url="https://api.lsmvec.com")   # your pilot endpoint
assert client.health()
client.create_index(dim=384, metric="l2")   # once per index; bge-small is 384-d

If this instance already has an index at another dimension, call delete_index() first — see Create your index.

Choose an embedder

Ingest a document

from pathlib import Path
from lsmvec_client.ingest import ingest_text

next_id, doc_chunks = 0, {}          # see the persistence note below
ids = ingest_text(client, "handbook-2026", Path("handbook.md").read_text(),
                  embedder, start_id=next_id,
                  payload={"source": "handbook", "year": 2026})
next_id += len(ids)
doc_chunks["handbook-2026"] = ids

ingest_text chunks the text, embeds each chunk, and stores its vector plus a payload {doc_id, chunk_index, text, model, …your fields}; it returns the assigned ids.

Persist your id counter. Asteroid ids are a contiguous range you allocate via start_id. Save next_id and the doc_id → ids map in your own database — if you restart ingestion from start_id=0 you will overwrite the vectors you stored before.

Ingest multiple documents

from pathlib import Path

for path in sorted(Path("corpus").glob("*.md")):
    doc_id = path.stem
    ids = ingest_text(
        client,
        doc_id,
        path.read_text(),
        embedder,
        start_id=next_id,
        payload={"source": str(path)},
    )
    next_id += len(ids)
    doc_chunks[doc_id] = ids

Store next_id and doc_chunks in your own database before the next run.

For larger imports, keep using the same id counter. If you already have embeddings, use insert_batch.

Retrieve

from lsmvec_client.ingest import search_text

hits = search_text(client, "what is the refund policy?", embedder,
                   k=5, filter={"source": {"$eq": "handbook"}})
context = "\n".join(h.text for h in hits)
# answer = your_llm(question, context)

search_text embeds the query, runs filtered k-NN, and returns hits enriched with the stored payload: h.text, h.doc_id, h.chunk_index, h.payload.

Update or delete a document

from lsmvec_client.ingest import delete_doc
delete_doc(client, doc_chunks["handbook-2026"])   # remove the doc's chunks
# re-ingest with fresh ids to update

Document updates are delete-then-ingest. Chunk counts can change, so do not assume old ids still match the new document.

Chunking

Control chunk size and overlap via max_tokens (default 512) and overlap_tokens (default 64) on ingest_text.

Asteroid does not embed text server-side. Embedding happens in the Python client, using either a local model or your embedding provider API.

Bulk & batch import

There are two build modes. Choose based on whether the index is empty.

Bulk build (in-memory build)

bulk_build builds a new, empty index from all vectors at once — use it for the fastest initial load.

import numpy as np
vectors = np.random.rand(100_000, 128).astype(np.float32)
report = client.bulk_build(vectors, payloads=[{"source": "handbook"} for _ in vectors], threads=4)
print(report)   # {'n':…, 'vectors_per_sec':…, 'payloads_written':…}

Initial-load only. bulk_build requires an empty index and assigns ids in input order, starting at 0. It can attach payloads, but it cannot keep existing ids.

Incremental build

Incremental build adds vectors through normal writes. Use it when the index already has data, when memory is limited, or when vectors arrive over time.

items = [(i, vec, {"source": "handbook"}) for i, vec in enumerate(vectors)]
n = client.insert_batch(items, chunk_size=1000)   # one request per chunk

Choose a build mode

Import existing vectors

If you already have embeddings in another system, you can import them into Asteroid without changing your embedding model.

  1. Export your vectors from the current system.
  2. Use the same embedding dimension when you call create_index.
  3. Insert records with your existing numeric IDs, or use bulk build for a fresh, empty-index load.
  4. Store document text, source, tags, or other retrieval fields as JSON metadata.
  5. Search with the same query embeddings your application already uses.

For pilot workloads, we can help review your current data format and choose between incremental build and bulk build.

Troubleshooting

SymptomWhat to check
401 UnauthorizedCheck that the request includes Authorization: Bearer <api-key>.
400 on insert or searchCheck that the vector has the dimension you set with create_index.
409 Conflict on insert or searchThe index has no dimension yet — call create_index first. (409 on create_index itself means it is already initialized; delete_index to re-dimension.)
404 Not FoundThe vector ID or payload does not exist yet.
413 Payload Too LargeReduce the request size or split the load into smaller requests.
429 Rate LimitedRetry with backoff, or contact us if the pilot needs a higher request rate.
Bulk build failsBulk build is for a new, empty index. Use incremental build for existing indexes.

Next: Asteroid Database (AsterVec) — embed or self-host the engine · Reference & Deployment.