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 whenask()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
| Block | Description | Links |
|---|---|---|
| RAG Chat | Streaming grounded RAG over your own text/PDF corpus with inline citations, on a core ⇄ LangChain engine toggle | Live · Install: npx shadcn add @localmode/ui/blocks/knowledge/rag-chat |