LocalMode
Core

Knowledge Base

Provider-agnostic RAG engine — ingest, search, and grounded Q&A over your own documents.

The Knowledge Base engine wraps the RAG building blocks — chunking, embedding, in-browser vector search, and grounded generation — behind one small KnowledgeBaseEngine contract. You inject an embedding model and a lazily-resolved language model; the engine owns chunk → embed → store on ingest(), vector retrieval on search(), and retrieve → generate on ask().

See it in action

Try the knowledge blocks for a working demo of this engine — the Ingest, Search, Ask, and Data experiences running entirely in the browser.

The engine is provider-agnostic: it constructs no transformers.* (or any provider) instance, so @localmode/core gains RAG-engine surface but no new runtime dependency. Nothing loads on construction — the embedding model runs on the first ingest()/search(), and the language model downloads only on the first ask().

Quick Start

import { createKnowledgeBaseEngine } from '@localmode/core';
import type { RawDocument } from '@localmode/core';
import { transformers } from '@localmode/transformers';

// Inject the embedding model; the answer model is lazily constructed (loads on first ask()).
const engine = createKnowledgeBaseEngine({
  embeddingModel: transformers.embedding('Xenova/bge-small-en-v1.5'),
  getLanguageModel: () =>
    transformers.languageModel('onnx-community/granite-4.0-350m-ONNX-web'),
});

const docs: RawDocument[] = [
  {
    id: crypto.randomUUID(),
    title: 'Privacy Overview',
    text: 'LocalMode runs models entirely in the browser. Data never leaves the device...',
    source: 'text',
    addedAt: Date.now(),
  },
];

// 1. Ingest (chunk → embed → store)
await engine.ingest(docs, { chunking: 'recursive', chunkSize: 500 });

// 2. Search (vector-ranked hits only)
const hits = await engine.search('privacy and encryption', { topK: 10 });

// 3. Ask (retrieve → grounded answer; downloads the language model on first call)
const { answer, sources } = await engine.ask('What is encrypted?');

Type names

Inside the module the contract keeps the verbatim names ChunkMetadata, IngestOptions, and SearchOptions. Because those collide with the existing core RAG/VectorDB exports, @localmode/core re-exports them from its barrel under KnowledgeBase* aliases. When you import the types from @localmode/core, use the aliased names:

import type {
  KnowledgeBaseChunkMetadata, // = ChunkMetadata
  KnowledgeBaseIngestOptions, // = IngestOptions
  KnowledgeBaseSearchOptions, // = SearchOptions
} from '@localmode/core';

The other contract types — KnowledgeBaseEngine, RawDocument, KBSearchResult, AskOptions, AskResult, EngineStats, ChunkingMode, DocumentSource — are exported verbatim.

API Reference

createKnowledgeBaseEngine(options)

Creates the provider-agnostic core KnowledgeBaseEngine (kind: 'core') bound to one embedding space. Switching embedding models means a new engine plus a re-ingest of your raw-document store.

Prop

Type

KnowledgeBaseEngine

The frozen engine contract. Both the core engine and the LangChain-adapter engine implement exactly this shape over their own session-scoped stores; kind identifies the active implementation.

Prop

Type

RawDocument

The raw (pre-chunking) input to ingest(). Chunk vector ids are derived as `${id}:${chunkIndex}`.

Prop

Type

DocumentSource is 'text' | 'sample' | 'pdf' | 'ocr' | 'import'.

KnowledgeBaseIngestOptions

Options for ingest() (the module's IngestOptions).

Prop

Type

KnowledgeBaseSearchOptions

Options for search() (the module's SearchOptions).

Prop

Type

AskOptions

Options for ask().

Prop

Type

AskResult

Prop

Type

KBSearchResult

One search hit surfaced to search() and ask().

Prop

Type

KnowledgeBaseChunkMetadata

Chunk-level metadata stored alongside every vector (the module's ChunkMetadata).

Prop

Type

KnowledgeBaseChunkMetadata carries an index signature ([key: string]: unknown), so engines may store additional fields alongside the ones above.

EngineStats

Prop

Type

KnowledgeBaseAskConfig

Overridable ask-time generation parameters, passed as askConfig to createKnowledgeBaseEngine.

Prop

Type

KnowledgeBaseChunkDefaults (chunkSize?, chunkOverlap?) supplies default recursive-chunker sizes applied when a per-call KnowledgeBaseIngestOptions omits them.

For recommended embedding and generation models, provider-specific options, and practical recipes, see the Transformers Embeddings and Transformers Text Generation guides. This page owns the API reference only — model tables live in the provider guides.

Cancellation Support

Every method accepts an AbortSignal. ingest() honors it between the chunking and embedding phases and per document — the underlying addMany store step has no per-call signal, so an in-flight store batch completes. search() and ask() call throwIfAborted() between stages (embed → retrieve → generate).

const controller = new AbortController();

// Cancel after 10 seconds
setTimeout(() => controller.abort(), 10_000);

try {
  await engine.ingest(docs, {
    chunking: 'recursive',
    chunkSize: 500,
    abortSignal: controller.signal,
    onProgress: ({ phase, completed, total }) => {
      console.log(`${phase}: ${completed}/${total}`);
    },
  });
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Ingestion was cancelled');
  }
}

Lazy Loading

The engine keeps self-contained lazy singletons in its closure, so it upholds the no-model-download-on-page-load invariant:

  • VectorDB — created on the first call that has a real embedding in hand; the embedding's length fixes the collection's dimensionality exactly.
  • Language model — resolved via getLanguageModel() only when ask() first runs, so the answer-model download is triggered by the first question, never by construction or ingestion.

An engine or embedding-model switch means a new engine plus a re-ingest of your raw-document store — a single engine instance is bound to one embedding space.

Custom Engine Implementation

Implement the KnowledgeBaseEngine interface to back a knowledge base with your own stack (a different vector store, a remote index, a custom retrieval pipeline) while keeping the same session contract:

import type {
  KnowledgeBaseEngine,
  RawDocument,
  KBSearchResult,
  AskResult,
  EngineStats,
  KnowledgeBaseIngestOptions,
  KnowledgeBaseSearchOptions,
  AskOptions,
} from '@localmode/core';

class MyEngine implements KnowledgeBaseEngine {
  readonly kind = 'core';

  async ingest(
    docs: RawDocument[],
    opts: KnowledgeBaseIngestOptions,
  ): Promise<{ chunks: number }> {
    // chunk → embed → store in your backing index
    return { chunks: 0 };
  }

  async search(
    query: string,
    opts: KnowledgeBaseSearchOptions,
  ): Promise<KBSearchResult[]> {
    // vector-ranked hits from your index
    return [];
  }

  async ask(question: string, opts?: AskOptions): Promise<AskResult> {
    // retrieve → generate; strip reasoning tags before returning
    return { answer: '', sources: [], durationMs: 0 };
  }

  async removeDocument(docId: string): Promise<void> {}

  async clear(): Promise<void> {}

  async stats(): Promise<EngineStats> {
    return { documents: 0, chunks: 0, dimensions: 0 };
  }
}

The LangChain-adapter engine createLangChainKnowledgeBaseEngine in @localmode/langchain (kind: 'langchain') is a result-equivalent implementation of this same contract over a LangChain retrieval stack — see the LangChain guide.

In React, the useKnowledgeBase hook owns a session raw-document store and re-ingests it through the selected engine on an engine-kind or embedding-model toggle, exposing ingest/search/ask state with built-in loading, progress, and cancellation.

Composed Block

BlockDescriptionLinks
RAG ChatStreaming grounded RAG over your own text/PDF corpus with inline citations, on a core ⇄ LangChain engine toggleLive · Install: npx shadcn add @localmode/ui/blocks/knowledge/rag-chat

On this page