LangChain
Full LangChain integration with LLM, embeddings, TTS, and STT. Build chains, agents, and voice-enabled pipelines with local models.
| Class | Capability |
|---|---|
| GerbilLLM | Text generation + Vision |
| GerbilEmbeddings | Vector embeddings |
GerbilLLM and GerbilEmbeddings use native models (Qwen3.5-0.8B, EmbeddingGemma-300M), running on the WebGPU engine. For speech (Kani-TTS-2 TTS, Moonshine STT) and browser inference, see the WebGPUEngine.Installation
npm install @tryhamster/gerbil langchainQuick Start
01import {02 GerbilLLM,03 GerbilEmbeddings,04} from "@tryhamster/gerbil/langchain";05
06// Text generation07const llm = new GerbilLLM({ model: "qwen3.5-0.8b" });08const result = await llm.invoke("Write a haiku about coding");09
10// Embeddings11const embeddings = new GerbilEmbeddings();12const vector = await embeddings.embedQuery("Hello world");GerbilLLM
Text generation with optional vision support:
01import { GerbilLLM } from "@tryhamster/gerbil/langchain";02
03const llm = new GerbilLLM({04 // Model configuration05 model: "qwen3.5-0.8b",06 device: "auto", // "auto" | "webgpu"07 dtype: "q4", // quantization08
09 // Generation options10 maxTokens: 500,11 temperature: 0.7,12 topP: 0.9,13 topK: 50,14
15 // Thinking mode (Qwen3.5)16 thinking: false,17});invoke()
01// Simple invocation02const result = await llm.invoke("Explain recursion");03
04// With options05const result = await llm.invoke("Write a poem", {06 maxTokens: 200,07 temperature: 0.9,08});09
10// With stop sequences11const result = await llm.invoke("List 3 items:\n1.", {12 stop: ["\n4."],13});Streaming
01// Stream tokens02const stream = llm.stream("Tell me a story");03
04for await (const chunk of stream) {05 process.stdout.write(chunk);06}07
08// With options09for await (const chunk of llm.stream("Explain hooks", { maxTokens: 200 })) {10 process.stdout.write(chunk);11}Vision
Use vision-capable models to analyze images:
01import { GerbilLLM } from "@tryhamster/gerbil/langchain";02
03// Use a vision-capable model04const llm = new GerbilLLM({ model: "qwen3.5-0.8b" });05
06// Check if model supports vision07const hasVision = await llm.supportsVision(); // true08
09// Analyze an image10const description = await llm.invokeWithImages(11 "Describe this image in detail",12 [{ source: "https://example.com/photo.jpg" }]13);14
15// Compare multiple images16const diff = await llm.invokeWithImages(17 "What changed between these two screenshots?",18 [19 { source: beforeScreenshot },20 { source: afterScreenshot },21 ]22);23
24// Use with local files (base64)25import { readFileSync } from "fs";26const imageData = readFileSync("photo.jpg").toString("base64");27const result = await llm.invokeWithImages(28 "What's in this photo?",29 [{ source: `data:image/jpeg;base64,${imageData}` }]30);GerbilEmbeddings
01import { GerbilEmbeddings } from "@tryhamster/gerbil/langchain";02
03// Vectors come from EmbeddingGemma-300M on the native engine.04const embeddings = new GerbilEmbeddings();05
06// Single query07const vector = await embeddings.embedQuery("What is the meaning of life?");08// Returns: number[] (768 dimensions, L2-normalized)09
10// Multiple documents11const vectors = await embeddings.embedDocuments([12 "First document",13 "Second document",14 "Third document",15]);16// Returns: number[][] (array of vectors)Speech & Audio
Speech runs on the native WebGPU engine rather than a LangChain wrapper. Text-to-speech uses Kani-TTS-2 via engine.speak(), and speech-to-text uses Moonshine via MoonshineSTT, both running on-device on WebGPU. See the Text-to-Speech and Speech-to-Text docs.
Chains
Use Gerbil with LangChain chains:
01import { GerbilLLM } from "@tryhamster/gerbil/langchain";02import { PromptTemplate } from "@langchain/core/prompts";03
04const llm = new GerbilLLM({ model: "qwen3.5-0.8b" });05
06// Format a LangChain prompt template, then invoke the model.07// GerbilLLM is a lightweight class (invoke / stream / batch /08// invokeWithImages), not a Runnable, so call it directly:09const prompt = PromptTemplate.fromTemplate(10 "You are a helpful assistant. Answer this question: {question}"11);12
13const formatted = await prompt.format({14 question: "What is the capital of France?",15});16const result = await llm.invoke(formatted);17
18console.log(result); // "The capital of France is Paris."Structured Output
01import { json } from "@tryhamster/gerbil";02import { z } from "zod";03
04// Define schema05const personSchema = z.object({06 name: z.string(),07 age: z.number(),08 city: z.string(),09});10
11// Validated structured output from the same local model12const result = await json(13 "Extract: John is 32 years old and lives in New York",14 { schema: personSchema }15);16
17console.log(result);18// { name: "John", age: 32, city: "New York" }Vector Stores
01import { GerbilEmbeddings } from "@tryhamster/gerbil/langchain";02import { MemoryVectorStore } from "langchain/vectorstores/memory";03import { Document } from "@langchain/core/documents";04
05const embeddings = new GerbilEmbeddings();06
07// Create documents08const docs = [09 new Document({ pageContent: "Gerbil is a local LLM library" }),10 new Document({ pageContent: "It supports WebGPU acceleration" }),11 new Document({ pageContent: "Works with the Vercel AI SDK" }),12];13
14// Create vector store15const vectorStore = await MemoryVectorStore.fromDocuments(docs, embeddings);16
17// Similarity search18const results = await vectorStore.similaritySearch("What is Gerbil?", 2);19console.log(results);RAG Pipeline
Build a complete Retrieval-Augmented Generation pipeline:
01import { GerbilLLM, GerbilEmbeddings } from "@tryhamster/gerbil/langchain";02import { MemoryVectorStore } from "langchain/vectorstores/memory";03
04// Initialize05const llm = new GerbilLLM({ model: "qwen3.5-0.8b" });06const embeddings = new GerbilEmbeddings();07
08// Create vector store from documents09const vectorStore = await MemoryVectorStore.fromTexts(10 [11 "Gerbil runs LLMs locally in Node.js",12 "It supports GPU acceleration via WebGPU",13 "Models are cached on-device",14 "Works offline after first download",15 ],16 [{}, {}, {}, {}],17 embeddings18);19
20// Retrieve, then generate with the context21const question = "Does Gerbil work offline?";22const relevantDocs = await vectorStore.similaritySearch(question, 2);23const context = relevantDocs.map((d) => d.pageContent).join("\n");24
25const answer = await llm.invoke(`26Answer the question based on the context below.27
28Context: ${context}29
30Question: ${question}31
32Answer:33`);34
35console.log(answer);36// "Yes, Gerbil works offline after the first download..."Agents
01// LangChain's agent executors need a full Runnable LLM, which the02// lightweight GerbilLLM shim is not. For tool-calling agents on the03// same local model, use Gerbil's own agent loop instead:04import { WebGPUEngine } from "@tryhamster/gerbil/gpu";05
06const engine = await WebGPUEngine.create({07 repo: "mlx-community/Qwen3.5-0.8B-4bit",08});09
10const { text, steps } = await engine.generateWithTools("What is 25 * 4 + 10?", {11 tools: [calculatorTool], // defineTool(...), see the Tools docs12});13
14console.log(text);See Tool Calling for the full agent-loop API.
Conversation Memory
01import { GerbilLLM } from "@tryhamster/gerbil/langchain";02
03const llm = new GerbilLLM({ model: "qwen3.5-0.8b" });04
05// Keep the transcript yourself and replay it each turn:06const history: string[] = [];07
08async function chat(input: string) {09 history.push(`User: ${input}`);10 const reply = await llm.invoke(11 `${history.join("\n")}\nAssistant:`12 );13 history.push(`Assistant: ${reply}`);14 return reply;15}16
17await chat("My name is Alice");18const result = await chat("What's my name?");19console.log(result); // "Your name is Alice!"Document Loaders
01import { GerbilLLM, GerbilEmbeddings } from "@tryhamster/gerbil/langchain";02import { TextLoader } from "langchain/document_loaders/fs/text";03import { PDFLoader } from "langchain/document_loaders/fs/pdf";04import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";05import { MemoryVectorStore } from "langchain/vectorstores/memory";06
07// Load documents08const textLoader = new TextLoader("./docs/readme.txt");09const pdfLoader = new PDFLoader("./docs/manual.pdf");10
11const textDocs = await textLoader.load();12const pdfDocs = await pdfLoader.load();13
14// Split into chunks15const splitter = new RecursiveCharacterTextSplitter({16 chunkSize: 500,17 chunkOverlap: 50,18});19
20const splitDocs = await splitter.splitDocuments([...textDocs, ...pdfDocs]);21
22// Create vector store23const embeddings = new GerbilEmbeddings();24const vectorStore = await MemoryVectorStore.fromDocuments(splitDocs, embeddings);25
26// Query27const results = await vectorStore.similaritySearch("How do I install?", 3);Voice-Enabled Pipeline
Build a complete voice-to-voice agent with STT → LLM → TTS. The LangChain LLM handles text; speech is the native WebGPU engine (Moonshine for STT, Kani-TTS-2 for TTS):
01import { GerbilLLM } from "@tryhamster/gerbil/langchain";02import { MoonshineSTT, WebGPUEngine } from "@tryhamster/gerbil/gpu";03
04const llm = new GerbilLLM({ model: "qwen3.5-0.8b" });05const stt = await MoonshineSTT.create({ repo: "UsefulSensors/moonshine-base" });06const tts = await WebGPUEngine.create({ repo: "nineninesix/kani-tts-450m-0.2-ft" });07
08// Voice input → LLM → Voice output09async function voiceChat(pcm16kMono: Float32Array) {10 // 1. Transcribe user speech (raw 16 kHz mono PCM)11 const { text: userMessage } = await stt.transcribe(pcm16kMono);12 console.log("User said:", userMessage);13
14 // 2. Generate response15 const response = await llm.invoke(userMessage);16 console.log("AI response:", response);17
18 // 3. Speak response19 const { pcm, sampleRate } = await tts.speak(response); // single built-in voice20
21 return { pcm, sampleRate, text: response };22}23
24// Combine with RAG for voice-enabled knowledge base25import { MemoryVectorStore } from "langchain/vectorstores/memory";26import { GerbilEmbeddings } from "@tryhamster/gerbil/langchain";27
28const embeddings = new GerbilEmbeddings();29const vectorStore = await MemoryVectorStore.fromTexts(docs, metadata, embeddings);30
31async function voiceRAG(pcm16kMono: Float32Array) {32 // Transcribe question33 const { text: question } = await stt.transcribe(pcm16kMono);34
35 // Retrieve relevant documents36 const relevantDocs = await vectorStore.similaritySearch(question, 3);37 const context = relevantDocs.map(d => d.pageContent).join("\n");38
39 // Generate answer with context40 const answer = await llm.invoke(41 `Context: ${context}\n\nQuestion: ${question}\n\nAnswer:`42 );43
44 // Speak the answer45 const { pcm } = await tts.speak(answer);46 return { pcm, answer };47}