LocalMode
Core

Embeddings

Generate embeddings for text and perform semantic search.

Embeddings convert text into numerical vectors that capture semantic meaning. Use them for similarity search, clustering, and RAG applications.

See it in action

Try the Knowledge Base block for a working demo of these APIs.

embed()

Generate an embedding for a single value:

import { embed } from '@localmode/core';
import { transformers } from '@localmode/transformers';

const model = transformers.embedding('Xenova/bge-small-en-v1.5');

const { embedding, usage, response } = await embed({
  model,
  value: 'Hello, world!',
});

console.log('Dimensions:', embedding.length); // 384
console.log('Tokens:', usage.tokens); // 4
console.log('Model:', response.modelId); // 'Xenova/bge-small-en-v1.5'
const controller = new AbortController();

setTimeout(() => controller.abort(), 5000); // Cancel after 5s

const { embedding } = await embed({
  model,
  value: 'Hello, world!',
  abortSignal: controller.signal,
});

EmbedOptions

Prop

Type

EmbedResult

Prop

Type

embedMany()

Generate embeddings for multiple values efficiently:

import { embedMany } from '@localmode/core';

const { embeddings, usage } = await embedMany({
  model,
  values: ['Hello', 'World', 'AI', 'Machine Learning'],
});

console.log('Count:', embeddings.length); // 4
console.log('Total tokens:', usage.tokens); // ~8
import { streamEmbedMany } from '@localmode/core';

const stream = streamEmbedMany({
  model,
  values: largeArrayOfTexts,
  onBatch: (progress) => {
    console.log(`Processed ${progress.index + progress.count}/${progress.total}`);
  },
});

for await (const { embedding } of stream) {
  // Process each embedding as it arrives
}

For progress tracking, use streamEmbedMany() with the onBatch callback. embedMany() does not support progress callbacks.

const controller = new AbortController();

// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);

try {
  const { embeddings } = await embedMany({
    model,
    values: largeArray,
    abortSignal: controller.signal,
  });
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Operation cancelled');
  }
}

EmbedManyOptions

Prop

Type

streamEmbedMany()

Stream embeddings as they're generated:

import { streamEmbedMany } from '@localmode/core';

const stream = streamEmbedMany({
  model,
  values: texts,
});

for await (const { index, embedding } of stream) {
  console.log(`Embedding ${index}:`, embedding.length);
}

semanticSearch()

Search for semantically similar documents:

import { semanticSearch, createVectorDB } from '@localmode/core';

const db = await createVectorDB({ name: 'docs', dimensions: 384 });

// Add documents to the database first (e.g. via ingest())...

const { results } = await semanticSearch({
  db,
  model,
  query: 'What is machine learning?',
  k: 5,
});

results.forEach((result) => {
  console.log(`Score: ${result.score.toFixed(3)}`);
  console.log(`Text: ${result.text}`);
});

semanticSearch() returns { results, usage }results is an array of { id, score, text?, metadata? } items, and usage reports embeddingTokens, embedDurationMs, and searchDurationMs.

Result Text Extraction

Each result's text is extracted from its stored metadata by checking these fields in priority order and returning the first string value found:

  1. text
  2. content
  3. body
  4. _text — the TEXT_METADATA_FIELD constant, the key ingest()/ingestChunks() write chunk text under
  5. __text
  6. pageContent

If none match, text is undefined (the result is still returned). Because the RAG ingest() pipeline stores chunk text under TEXT_METADATA_FIELD ('_text'), an ingest → search round-trip populates results[].text out of the box — and metadata you stored yourself under text always wins over the ingest-written key:

import { TEXT_METADATA_FIELD } from '@localmode/core';

const { results } = await semanticSearch({ db, model, query, k: 5 });
results[0].text; // chunk text stored by ingest()
results[0].metadata?.[TEXT_METADATA_FIELD]; // the same text, via the raw key

With Filters

const { results } = await semanticSearch({
  db,
  model,
  query: 'AI applications',
  k: 5,
  filter: {
    category: { $eq: 'technology' },
    year: { $gte: 2023 },
  },
});

Options

interface SemanticSearchOptions {
  db: VectorDB;
  model: EmbeddingModel | string;
  query: string;
  k?: number;
  filter?: Record<string, unknown>;
  threshold?: number;
  abortSignal?: AbortSignal;
}

Distance Functions

Compare vectors directly:

import { cosineSimilarity, euclideanDistance, dotProduct } from '@localmode/core';

const similarity = cosineSimilarity(embedding1, embedding2);
console.log('Similarity:', similarity); // 0.0 to 1.0

const distance = euclideanDistance(embedding1, embedding2);
console.log('Distance:', distance);

const dot = dotProduct(embedding1, embedding2);
console.log('Dot product:', dot);

Middleware

Wrap embedding models with middleware for caching, logging, etc.:

import { wrapEmbeddingModel, cachingMiddleware, loggingMiddleware } from '@localmode/core';

const baseModel = transformers.embedding('Xenova/bge-small-en-v1.5');

const model = wrapEmbeddingModel(baseModel, [
  cachingMiddleware({ maxSize: 1000 }),
  loggingMiddleware({ logger: console.log }),
]);

// Now all embed calls will be cached and logged
const { embedding } = await embed({ model, value: 'Hello' });

See Middleware for more details.

Implementing Custom Models

Create your own embedding model by implementing the EmbeddingModel interface:

import type { EmbeddingModel, DoEmbedOptions } from '@localmode/core';

class MyCustomEmbedder implements EmbeddingModel {
  readonly modelId = 'custom:my-embedder';
  readonly provider = 'custom';
  readonly dimensions = 768;
  readonly maxEmbeddingsPerCall = 100;
  readonly supportsParallelCalls = true;

  async doEmbed(options: DoEmbedOptions) {
    const { values } = options;

    // Your embedding logic here
    const embeddings = values.map(() => new Float32Array(768));

    return {
      embeddings,
      usage: { tokens: values.length * 10 },
      response: { modelId: this.modelId, timestamp: new Date() },
    };
  }
}

// Use with core functions
const model = new MyCustomEmbedder();
const { embedding } = await embed({ model, value: 'Hello' });

Best Practices

Performance Tips

  1. Batch embeddings - Use embedMany() instead of multiple embed() calls
  2. Use caching - Add cachingMiddleware() for repeated queries
  3. Choose the right model - Smaller models (MiniLM-L6) are faster, larger ones more accurate
  4. Preload models - Load models during app initialization

For recommended models, provider-specific configuration, and practical recipes, see the Transformers Embeddings guide.

Next Steps

Blocks

AppDescriptionLinks
Knowledge Base (Semantic Search)Embed documents and search by meaningLive block · Source
Photo Search (Cross-Modal Search)Embed text and images for cross-modal retrievalLive block · Source
Photo Search (Product Search)Embed product catalog for semantic product discoveryLive block · Source
Photo Search (Smart Gallery)Embed images for intelligent photo organizationLive block · Source
Privacy (PII Redactor)Embed text with differential privacy noiseLive block · Source
Knowledge Base (PDF Search)Embed PDF chunks for document Q&ALive block · Source
Knowledge Base (LangChain RAG)Embed documents in a LangChain RAG pipelineLive block · Source
Photo Search (Duplicate Finder)Embed image features to detect near-duplicatesLive block · Source

On this page