LangChain
Drop-in local inference for existing LangChain.js applications.
LangChain Integration
@localmode/langchain provides adapter classes so existing LangChain.js applications can swap cloud providers for 100% local inference — by changing just 3 imports.
See it in action
Try the Knowledge Base block — its LangChain engine toggle runs LocalModeEmbeddings, LocalModeVectorStore, and ChatLocalMode end-to-end in the browser, behind the same UI as the core pipeline.
Adapters
| LangChain Base Class | LocalMode Adapter | Wraps | Provider |
|---|---|---|---|
Embeddings | LocalModeEmbeddings | EmbeddingModel | @localmode/transformers |
BaseChatModel | ChatLocalMode | LanguageModel | @localmode/webllm |
VectorStore | LocalModeVectorStore | VectorDB | @localmode/core |
BaseDocumentCompressor | LocalModeReranker | RerankerModel | @localmode/transformers |
Each adapter is a thin, stateless wrapper — it converts between LangChain and LocalMode data formats and delegates all work to the underlying model or database.
Installation
pnpm install @localmode/langchain @localmode/core @localmode/transformers
# Add webllm if using ChatLocalMode:
pnpm install @localmode/webllmQuick Start — Full RAG Chain
import { LocalModeEmbeddings, ChatLocalMode, LocalModeVectorStore } from '@localmode/langchain';
import { transformers } from '@localmode/transformers';
import { webllm } from '@localmode/webllm';
import { createVectorDB } from '@localmode/core';
// 1. Create local models
const embeddingModel = transformers.embedding('Xenova/bge-small-en-v1.5');
const llmModel = webllm.languageModel('Qwen3-1.7B-q4f16_1-MLC');
// 2. Wrap in LangChain adapters
const embeddings = new LocalModeEmbeddings({ model: embeddingModel });
const llm = new ChatLocalMode({ model: llmModel });
// 3. Create vector store backed by local IndexedDB
const db = await createVectorDB({ name: 'docs', dimensions: 384 });
const store = new LocalModeVectorStore(embeddings, { db });
// 4. Add documents (embeds automatically)
await store.addDocuments([
{ pageContent: 'LocalMode runs AI in the browser.', metadata: { source: 'docs' } },
{ pageContent: 'Data never leaves the device.', metadata: { source: 'docs' } },
]);
// 5. Search
const results = await store.similaritySearch('privacy', 3);
// 6. Generate with context
const context = results.map((r) => r.pageContent).join('\n');
const answer = await llm.invoke(`Based on: ${context}\n\nQuestion: How does LocalMode handle privacy?`);Everything runs locally. No API keys, no servers, no data leaves the device.
Knowledge Base Engine
createLangChainKnowledgeBaseEngine() returns a kind: 'langchain' engine that implements the same frozen KnowledgeBaseEngine contract exported from @localmode/core (chunk → embed → store, vector search, grounded ask) — but runs it through the real LocalModeEmbeddings, LocalModeVectorStore, and ChatLocalMode adapters over an in-memory createVectorDB. It exists so an app can toggle the whole pipeline between the core implementation and the LangChain adapters without changing a line of calling code.
import { createLangChainKnowledgeBaseEngine, ChatLocalMode } from '@localmode/langchain';
import { transformers } from '@localmode/transformers';
const engine = createLangChainKnowledgeBaseEngine({
embeddingModel: transformers.embedding('Xenova/bge-small-en-v1.5'),
getChatModel: () =>
new ChatLocalMode({
model: transformers.languageModel('onnx-community/granite-4.0-350m-ONNX-web'),
maxTokens: 512,
}),
});
await engine.ingest(docs, { chunking: 'recursive', chunkSize: 500 });
const hits = await engine.search('privacy and encryption', { topK: 10 });
const { answer, sources } = await engine.ask('How is data encrypted?');Options
CreateLangChainKnowledgeBaseEngineOptions:
| Option | Type | Default | Notes |
|---|---|---|---|
embeddingModel | EmbeddingModel | — (required) | Defines the corpus space; wrapped internally in LocalModeEmbeddings. |
getChatModel | () => Promise<ChatLocalMode> | ChatLocalMode | — (required) | Lazy — resolved (and awaited) only on the first ask(), so nothing loads on construction. The caller owns model construction, any device pre-probe, and the ChatLocalMode maxTokens / temperature budget. |
storage | 'memory' | 'indexeddb' | StorageAdapter | 'memory' | VectorDB storage backend (in-memory session store by default). |
chunkDefaults | LangChainKnowledgeBaseChunkDefaults | { chunkSize: 500, chunkOverlap: 50 } | Applied when IngestOptions omits sizes. |
askConfig | LangChainKnowledgeBaseAskConfig | { topK: 4 } | systemPrompt? grounds the answer; topK? is the retrieval depth when AskOptions.topK is omitted. |
Because the models are injected, @localmode/langchain gains no provider dependency — apps that never toggle the LangChain engine never pull it.
Equivalence with the core engine
The LangChain engine is result-equivalent to @localmode/core's createKnowledgeBaseEngine: given the same corpus, embedding model, and query, both return the same ranked contract ids and the same cosine scores (higher-is-better, in [0, 1]). To hold that guarantee:
- Chunking uses a faithful local port of LangChain's
RecursiveCharacterTextSplitter(same default separators,keepSeparator, and merge/overlap semantics).'semantic'mode falls back to recursive — equivalence is defined at the result level, not byte-for-byte chunk parity. - Contract ids (
${docId}:${chunkIndex}) are reconstructed from chunk metadata, becauseLocalModeVectorStoregenerates its own UUIDs for stored vectors. - Grounded answers strip
<think>…</think>reasoning blocks before returning, matching the core engine.
New exports: createLangChainKnowledgeBaseEngine, CreateLangChainKnowledgeBaseEngineOptions, LangChainKnowledgeBaseAskConfig, LangChainKnowledgeBaseChunkDefaults.
The frozen contract and full KnowledgeBaseEngine API reference live in core — see Core Knowledge Base. For the React session hook, see useKnowledgeBase. The Knowledge Base block toggles the core and LangChain engines live over one shared corpus.
Key Design Decisions
- User provides model instances — You create the LocalMode model and pass it to the adapter. The adapter doesn't know about provider packages.
- Float32Array to number[] — LangChain uses
number[][]for embeddings. The adapter converts automatically viaArray.from(). - Streaming fallback —
ChatLocalMode._stream()uses the model'sdoStream()if available, otherwise falls back to generating the full response and yielding it as a single chunk. - No tool calling — Local models have limited tool-calling ability.
ChatLocalModereturns text-only content.
Package Details
| Property | Value |
|---|---|
| Package | @localmode/langchain |
| Dependencies | @langchain/core (>=0.3.0) |
| Peer Dependencies | @localmode/core (>=1.0.0) |
| Bundle | ESM + CJS, tree-shakeable |
| Side Effects | None |
For individual adapter docs, see: Embeddings, Chat Model, Vector Store, Migration Guide.
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 |