Skip to main content

Use embedded SABLE for local development and tests

TL;DR

Embedded SABLE runs VectorAmp's native vector index inside your Python or Node.js process for development and test workflows. It needs no API key, network request, external database service, or compiler on the target machine. Give the SDK a local directory, create a dataset with a fixed vector dimension, insert precomputed vectors, and use the normal VectorAmp dataset API for vector search, metadata filters, index status, optimization, persistence, and reopen.

Use embedded SABLE for local development, evaluation, CI, automated tests, benchmarks, demos, and non-production staging. Production applications must use a separately licensed hosted or customer-operated VectorAmp SABLE system.

Development and test license​

The Python and Node.js SDKs remain available under Apache-2.0. Their optional native embedded runtime is distributed under the separate proprietary VectorAmp Embedded SABLE Development License.

The runtime permits internal non-production development and test use. It does not permit production traffic, customer-facing deployment, hosted or managed-service use, resale, or external redistribution. It performs no license-network request or telemetry call. Contact VectorAmp for hosted, customer-operated, OEM, edge, or other production rights.

The technical limits below are engineering safety boundaries, not commercial entitlements. In particular, the five-million-vector ceiling does not permit production use below that size.

Supported platforms​

The package installs a precompiled native runtime. It does not compile Rust or download an executable the first time your application starts.

Operating systemArchitecturePythonNode.js
Linux with glibc 2.17+x86-64CPython 3.9–3.15Node.js 18, 20, 22, 24, or 26
Linux with glibc 2.17+arm64CPython 3.9–3.15Node.js 18, 20, 22, 24, or 26
macOSApple Silicon arm64CPython 3.9–3.15Node.js 18, 20, 22, 24, or 26

Windows, Linux musl distributions such as Alpine, and Intel macOS are not currently supported.

Install​

Python​

Install the SDK with its embedded runtime extra:

python -m pip install "vectoramp[embedded]"

Node.js​

Install the VectorAmp SDK. npm selects the native package for the current supported platform:

npm install @vectoramp/vectoramp

Python quickstart​

This example creates a durable local development database, inserts vectors, waits for automatic index training, performs a filtered search, checkpoints the optimized index, and reopens the same dataset.

from __future__ import annotations

import math
import time
from pathlib import Path

from vectoramp import VectorAmp

root = Path.home() / ".local" / "share" / "my-app" / "sable"
client = VectorAmp.embedded(root)

dataset = client.datasets.create(
name="products",
dim=4,
metric="cosine",
metadata_schema={
"tenant": "string",
"price": "f64",
},
tuning={"training_threshold": 256},
)

vectors = [
{
"id": vector_id,
"values": [
math.cos((vector_id - 1) * 0.01),
math.sin((vector_id - 1) * 0.01),
0.0,
0.0,
],
"metadata": {
"tenant": "acme" if vector_id % 2 == 0 else "other",
"price": str(vector_id / 10),
},
}
for vector_id in range(1, 257)
]

print(dataset.insert(vectors)) # {"inserted": 256}

results = dataset.search(
[1.0, 0.0, 0.0, 0.0],
top_k=10,
filters={"tenant": "acme"},
advanced_filters=[
{"field": "price", "op": "gte", "value": 1.0},
{"field": "price", "op": "lte", "value": 20.0},
],
include_metadata=True,
)
print(results["results"])

# Search works immediately in exact mode. At the configured threshold, SABLE
# trains the compressed index in the background and then reports "indexed".
deadline = time.monotonic() + 120
status = dataset.index_status()
while status["state"] not in {"indexed", "failed"} and time.monotonic() < deadline:
time.sleep(0.25)
status = dataset.index_status()

if status["state"] != "indexed":
raise RuntimeError(f"SABLE training failed or timed out: {status}")

# Explicit optimization builds or checkpoints the durable index.
print(dataset.optimize())

dataset_id = dataset.id
client.close()

# A new client can reopen the catalog and dataset from the same root directory.
reopened = VectorAmp.embedded(root)
restored = reopened.datasets.get(dataset_id)
print(reopened.datasets.stats(dataset_id))
print(restored.search([1.0, 0.0, 0.0, 0.0], top_k=5))
reopened.close()

Use a persistent local directory when a development fixture must survive process restarts. A temporary directory is useful for isolated tests, but its database disappears when the directory is deleted.

Node.js quickstart​

The Node SDK exposes the same dataset workflow. Native SABLE operations run through a worker so training, search, and checkpoint work do not block the main event loop.

import { mkdir } from 'node:fs/promises';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { VectorAmp } from '@vectoramp/vectoramp';

const root = join(homedir(), '.local', 'share', 'my-app', 'sable');
await mkdir(root, { recursive: true });

const client = await VectorAmp.embedded(root);
const dataset = await client.datasets.create({
name: 'products',
dim: 4,
metric: 'cosine',
metadataSchema: {
tenant: 'string',
price: 'f64'
},
tuning: { trainingThreshold: 256 }
});

const vectors = Array.from({ length: 256 }, (_, index) => ({
id: index + 1,
values: [Math.cos(index * 0.01), Math.sin(index * 0.01), 0, 0],
metadata: {
tenant: (index + 1) % 2 === 0 ? 'acme' : 'other',
price: String((index + 1) / 10)
}
}));

console.log(await dataset.insert(vectors)); // { inserted: 256 }

const results = await dataset.search({
vector: [1, 0, 0, 0],
topK: 10,
filters: { tenant: 'acme' },
advancedFilters: [
{ field: 'price', op: 'gte', value: 1 },
{ field: 'price', op: 'lte', value: 20 }
],
includeMetadata: true
});
console.log(results.results);

const deadline = Date.now() + 120_000;
let status = await dataset.indexStatus();
while (!['indexed', 'failed'].includes(String(status.state)) && Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 250));
status = await dataset.indexStatus();
}

if (status.state !== 'indexed') {
throw new Error(`SABLE training failed or timed out: ${JSON.stringify(status)}`);
}

console.log(await dataset.optimize());

const datasetId = dataset.id;
await client.close();

const reopened = await VectorAmp.embedded(root);
const restored = await reopened.datasets.get(datasetId);
console.log(await restored.indexStatus());
console.log(await restored.search({ vector: [1, 0, 0, 0], topK: 5 }));
await reopened.close();

Index lifecycle​

indexStatus() in Node.js or index_status() in Python reports one of four states:

StateMeaning
exactVectors are durable and searchable before the compressed index has been trained.
trainingSABLE is building its trained index in the background. Searches continue while training runs.
indexedThe trained index is active.
failedBackground training failed. Inspect trainingError in Node.js or training_error in Python.

The default training threshold is 10,000 vectors. You can lower it for small local datasets or tests, but it cannot be lower than 256. Calling optimize() explicitly trains when enough vectors exist and checkpoints the durable index.

status = dataset.index_status()
print(status["state"], status["vector_count"], status["training_threshold"])
```ts
const status = await dataset.indexStatus();
console.log(status.state, status.vectorCount, status.trainingThreshold);

Metadata filters​

Declare metadata fields when you create the dataset. Embedded SABLE supports string, u32, i32, i64, f32, and f64 fields.

  • Use filters for exact equality constraints.
  • Use advanced_filters in Python or advancedFilters in Node.js for typed numeric comparisons: eq, gt, gte, lt, and lte.
  • Each advanced filter uses field, op, and value. The SDK infers the numeric kind from the dataset's declared metadata schema; an explicit kind, when supplied, must match it.
  • Multiple equality and advanced filters are combined with AND semantics.
  • Invalid operators, missing values, undeclared fields, and type mismatches fail the search instead of being silently ignored.
  • Metadata values are stored with each vector; use a consistent representation when inserting and filtering.
  • Result metadata is included by default. Set include_metadata=False in Python or includeMetadata: false in Node.js to omit it.

Each result contains id, distance, score, and—when requested—metadata. score follows the hosted VectorAmp contract and equals distance; smaller values rank first. For cosine datasets, distance is 1 - cosine_similarity, so identical or positively collinear vectors have distance 0.0.

Python:

results = dataset.search(
query=[0.2, 0.4, 0.6, 0.8],
top_k=20,
filters={"tenant": "acme"},
advanced_filters=[
{"field": "price", "op": "gte", "value": 10.0},
{"field": "price", "op": "lt", "value": 50.0},
],
)

Node.js:

const results = await dataset.search({
vector: [0.2, 0.4, 0.6, 0.8],
topK: 20,
filters: { tenant: 'acme' },
advancedFilters: [
{ field: 'price', op: 'gte', value: 10 },
{ field: 'price', op: 'lt', value: 50 }
]
});

Persistence and shutdown​

The root directory passed to embedded(...) owns the local catalog and every dataset created by that client.

  • Reuse the same root path to reopen existing datasets.
  • Store the entire root directory together; do not edit catalog.json or individual dataset files.
  • Call close() during an orderly shutdown and before copying or backing up the directory.
  • Keep the vector dimension, metric, and metadata schema stable for an existing dataset.
  • Pin compatible SDK/runtime versions in development and CI environments, and test fixture reopen before upgrading.
  • Deleting a dataset removes its local dataset directory and catalog entry.

Embedded SABLE uses write-ahead durability for acknowledged inserts and versioned index checkpoints. A reopened database recovers durable vectors even if the trained checkpoint is older than the latest inserts.

Embedded and hosted capabilities​

Embedded mode deliberately implements the portable local dataset/vector subset of the VectorAmp SDK.

Available locallyHosted VectorAmp only
Create, list, get, and delete datasetsManaged embedding models and text embedding
Insert precomputed vectorsadd_texts / addTexts
Vector searchIngestion sources, pipelines, and schedules
Equality and typed numeric comparison filtersIntelligence ask, agents, and cited answers
Dataset statistics and index statusOrganization secrets and OAuth connections
Automatic training and explicit optimizationRetained source-document APIs
Durable close and reopenMulti-node scaling and managed operations

Embedded mode therefore requires numeric vectors for both inserts and queries. Generate embeddings in your application or another model service, then pass those vectors to the dataset.

Use hosted or customer-operated VectorAmp SABLE for every production workload, including workloads that need managed ingestion, embedding, Intelligence, multi-user API access, or distributed scale. Because both modes use the normal SDK dataset objects, application code that stays inside the shared vector subset can switch transports with a small initialization change.

Runtime limits​

The embedded edition enforces these per-dataset limits:

  • Vector dimension: 1–4,096.
  • Vectors: up to 5,000,000.
  • Search results: top_k up to 10,000.
  • Declared metadata fields: up to 64.
  • Metadata: up to 64 KiB per vector; every declared field is required.
  • Training threshold: 256–5,000,000 vectors.
  • Metrics: cosine and l2.
  • Vector IDs: non-negative integers in Python; non-negative safe integers or bigint values in Node.js.
  • Ownership: one process per dataset.
  • Mutation: insert-only vector IDs; duplicate IDs, per-vector delete, and upsert are unsupported.
  • Schema: vector dimension, metric, and declared metadata schema are fixed at dataset creation.

Local development and CI checklist​

  • Choose a durable application-data directory with enough space for vectors, metadata, write-ahead data, and checkpoints.
  • Ensure only the intended application instance owns the directory.
  • Validate vector dimensions before insertion.
  • Declare every field used by typed advanced filters.
  • Monitor indexStatus() / index_status() and surface the failed state.
  • Call close() on normal shutdown.
  • Copy the complete root directory only while the client is closed.
  • Test database reopen whenever you change the SDK/runtime version.