← All @molecule/* packages · App templates
@molecule/api-ai-embeddings-localProvider bond · ai-embeddings · API (Node) · v1.0.1 · Apache-2.0
Local offline embeddings provider for molecule.dev — runs bge-small (384-dim) in-process via Transformers.js; no API key or network at query time
npm install @molecule/api-ai-embeddings-localnpm · Source on GitHub · Implements @molecule/api-ai-embeddings
@molecule/api-ai-embeddings-local is a provider bond on the API (Node) side: it implements the ai-embeddings core interface (@molecule/api-ai-embeddings) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { setProvider } from '@molecule/api-ai-embeddings'
import { provider } from '@molecule/api-ai-embeddings-local'
setProvider(provider) // at startup
// anywhere after:
import { requireProvider } from '@molecule/api-ai-embeddings'
const vector = await requireProvider().embedQuery('some text') // number[] (384 dims)
const vectors = await requireProvider().embedDocuments(['a', 'b']) // number[][]Works with: @molecule/api-ai-embeddings
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Local (offline) ai-embeddings provider for molecule.dev.
Runs a small sentence-embedding model (default bge-small-en-v1.5, 384-dim)
in-process via Transformers.js (onnxruntime) — no API key, no per-call cost, and
no network at query time. Bond it once at startup, then use the
@molecule/api-ai-embeddings core anywhere.
import { setProvider } from '@molecule/api-ai-embeddings'
import { provider } from '@molecule/api-ai-embeddings-local'
setProvider(provider) // at startup
// anywhere after:
import { requireProvider } from '@molecule/api-ai-embeddings'
const vector = await requireProvider().embedQuery('some text') // number[] (384 dims)
const vectors = await requireProvider().embedDocuments(['a', 'b']) // number[][]
provider
npm install @molecule/api-ai-embeddings-local @huggingface/transformers @molecule/api-ai-embeddings
LocalEmbeddingsConfigConfiguration for the local embeddings provider. Every field is optional and has an env-var fallback, so the provider works with zero configuration.
interface LocalEmbeddingsConfig {
/**
* Model id (Transformers.js / HuggingFace). Defaults to `Xenova/bge-small-en-v1.5`
* (384-dim) or the `MOL_EMBEDDINGS_LOCAL_MODEL` env var.
*/
model?: string
/** Pooling strategy. Defaults to `cls` — bge models are trained for CLS pooling. */
pooling?: LocalEmbeddingsPooling
/** L2-normalize outputs so a dot product equals cosine similarity. Defaults to `true`. */
normalize?: boolean
/**
* Directory Transformers.js caches downloaded weights in (or the
* `MOL_EMBEDDINGS_LOCAL_CACHE_DIR` env var). Use a persistent path so the
* one-time download survives restarts.
*/
cacheDir?: string
/**
* Directory holding a pre-bundled model for fully-offline / air-gapped use (or
* the `MOL_EMBEDDINGS_LOCAL_MODEL_PATH` env var). Setting it disables remote
* fetch unless {@link allowRemoteModels} is explicitly `true`.
*/
localModelPath?: string
/**
* Allow downloading the model from HuggingFace on first use. Defaults to `true`,
* or `false` when {@link localModelPath} is set.
*/
allowRemoteModels?: boolean
/**
* How many texts to run through the model per forward pass (or the
* `MOL_EMBEDDINGS_LOCAL_BATCH_SIZE` env var). Defaults to 32.
*
* This is a MEMORY bound, not a throughput knob. Inference is in-process, so
* the whole batch's activations are resident at once and attention allocates
* on the order of `batch × heads × sequence²`. Handing the model an entire
* corpus in one call therefore scales peak RSS with the corpus: indexing 898
* documents unbatched peaked at ~3.8 GiB and was OOM-killed under a 1–2 GiB
* container limit — and since the kernel delivers that kill, no `catch` in
* the calling code can degrade gracefully.
*
* Raise it if you have headroom and want fewer, larger passes; lower it for a
* tighter memory ceiling. Values below 1 are clamped to 1.
*/
batchSize?: number
}
LocalEmbeddingsPoolingPooling strategy applied to the model's token embeddings to produce one vector per input.
type LocalEmbeddingsPooling = 'cls' | 'mean' | 'none'
createProvider(config)Create a local embeddings provider. Config falls back to env vars, so
createProvider() with no arguments works out of the box.
function createProvider(config?: LocalEmbeddingsConfig): AIEmbeddingsProvider
config — Optional model / pooling / cache configuration.Returns: An {@link AIEmbeddingsProvider} backed by an in-process ONNX model.
providerThe provider implementation. Bond it with the ai-embeddings core's
setProvider. Model loading is deferred until the first embedding call.
const provider: AIEmbeddingsProvider
Implements @molecule/api-ai-embeddings interface.
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/api-ai-embeddings'
import { provider } from '@molecule/api-ai-embeddings-local'
export function setupAiEmbeddingsLocal(): void {
setProvider(provider)
}
Peer dependencies:
@molecule/api-ai-embeddings >=1.0.1@huggingface/transformers
@molecule/api-ai-embeddings
The model loads lazily on the first embed call (~a few seconds) and then stays resident (~200–300 MB RAM). Nothing loads if you never embed.
First use downloads the model (~34 MB) and caches it. For fully-offline /
air-gapped deployments, bundle the model and set localModelPath (or the
MOL_EMBEDDINGS_LOCAL_MODEL_PATH env var) — that disables the remote fetch.
Configure via createProvider({ model, pooling, cacheDir, localModelPath }) or
the MOL_EMBEDDINGS_LOCAL_* env vars. Outputs are L2-normalized, so a dot
product equals cosine similarity.
Pulls @huggingface/transformers + onnxruntime-node (~350 MB installed) — a
real third-party dependency, unlike most @molecule/* packages. Add it only
where you actually embed.
Integration checklist — drive the real flow (no mocks), adapt each item to this app's actual data and features, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip. Embeddings are infrastructure, so PROVE them through the feature they power (semantic search / "related items" / dedup) AND with a direct property check on the vectors:
embedQuery(text) returns a non-empty numeric number[] of the model's
fixed dimension, and every vector from embed/embedDocuments has that SAME
length — no empty arrays, no NaN/null entries, and the length is identical
across calls (a query and a document must be comparable).embedDocuments([a, b, c]) (or embed({ input }))
returns exactly one vector per input in the SAME order — embeddings[i] is the
vector for input[i], never shuffled, merged, or dropped.