@memvid/sdk
v2.0.124
Published
Single-file AI memory system for Node.js. Store, search, and query documents with built-in RAG.
Maintainers
Readme
@memvid/sdk
Single-file AI memory system for Node.js. Store documents, search with BM25 ranking, and run retrieval-augmented generation (RAG) queries from a portable .mv2 file.
Built on Rust via N-API for native performance. No database setup, no network dependencies, no configuration files.
Install
npm install @memvid/sdkQuick start
import { use, create } from "@memvid/sdk";
// Create a new memory file
const mv = await use("basic", "notes.mv2", { mode: "auto" });
// Store a document
await mv.put({
title: "Project kickoff",
label: "meeting",
text: "Discussed timeline, assigned tasks to team members.",
metadata: { date: "2024-01-15" },
});
// Search by keyword
const results = await mv.find("timeline");
console.log(results.hits);
// Ask a question (retrieves relevant context)
const answer = await mv.ask("What was discussed in the kickoff?");
console.log(answer.context);
// Commit and close
await mv.seal();Core API
Opening a memory
const mv = await use(kind, path, options?)| Parameter | Type | Description |
|-----------|------|-------------|
| kind | string | Adapter type: "basic", "langchain", "llamaindex", "openai", "crewai", "vercel-ai", "autogen" |
| path | string | Path to .mv2 file |
| options.mode | string | "open" (default), "create", or "auto" |
| options.enableLex | boolean | Enable lexical index (default: true) |
| options.enableVec | boolean | Enable vector index (default: true) |
| options.readOnly | boolean | Open in read-only mode (default: false) |
| options.lockTimeoutMs | number | Lock acquisition timeout (ms) |
| options.force | "stale_only" | Force-open when a stale lock exists |
| options.forceWritable | boolean | Force writable open (dangerous) |
| options.memvidApiKey | string | Memvid API key for capacity beyond free tier (mv2_*) |
Storing documents
await mv.put({
title: "Document title",
label: "category",
text: "Document content...",
metadata: { key: "value" },
uri: "mv2://custom/path",
tags: ["tag1", "tag2"],
});Automatic embeddings (optional)
To enable semantic search without supplying pre-computed vectors, set enableEmbedding: true on put():
await mv.put({
title: "Document title",
label: "category",
text: "Document content...",
enableEmbedding: true,
// Optional (defaults to "bge-small")
embeddingModel: "bge-small", // or "openai-small"
});Notes:
- Local embedding models use
fastembedand cache weights underMEMVID_MODELS_DIR(first run may download). - OpenAI embedding models require
OPENAI_API_KEY(and optionallyOPENAI_BASE_URL). - If embeddings cannot be generated, the SDK throws
MV015(it will not silently ingest without embeddings).
Batch ingestion
For bulk imports, putMany processes documents in parallel:
const docs = [
{ title: "Doc 1", text: "First document content..." },
{ title: "Doc 2", text: "Second document content..." },
// ... thousands more
];
const frameIds = await mv.putMany(docs, { compressionLevel: 3 });
console.log(`Ingested ${frameIds.length} documents`);To auto-generate embeddings during batch ingest, enable embeddings in the options (requests that already include embedding are left unchanged):
await mv.putMany(docs, {
enableEmbedding: true,
embeddingModel: "bge-small", // or "openai-small"
});If you provide pre-computed embeddings, also attach the embedding identity so the CLI/SDKs can auto-detect the correct query embedding model later:
import { OpenAIEmbeddings } from "@memvid/sdk";
const embedder = new OpenAIEmbeddings({ model: "text-embedding-3-small" });
const vectors = await embedder.embedDocuments(docs.map((d) => d.text));
await mv.putMany(
docs.map((doc, i) => ({
...doc,
embedding: vectors[i],
embeddingIdentity: {
provider: "openai",
model: embedder.modelName,
dimension: embedder.dimension,
},
}))
);External embedding providers (bring your own)
For developer-friendly “use any embedding model/API” workflows, pass an embedder:
import { OpenAIEmbeddings } from "@memvid/sdk";
const embedder = new OpenAIEmbeddings({ model: "text-embedding-3-small" });
// Ingest: computes embeddings for requests that omit `embedding` and stores embedding identity metadata.
await mv.putMany(docs, { embedder });
// Query: computes query embeddings when `queryEmbedding` is omitted.
const results = await mv.find("timeline", { mode: "sem", embedder });
const context = await mv.ask("What happened?", { mode: "sem", contextOnly: true, embedder });Built-in external providers (API keys via env vars):
OpenAIEmbeddings(OPENAI_API_KEY)CohereEmbeddings(COHERE_API_KEY)VoyageEmbeddings(VOYAGE_API_KEY)NvidiaEmbeddings(NVIDIA_API_KEY, default base URLhttps://integrate.api.nvidia.com)
Notes:
- If
embedderis set,putMany({ enableEmbedding: true })is ignored (embeddings are computed in JS). put()does not accept pre-computed vectors; useputMany([singleDoc], { embedder })for external embeddings.
Searching
const results = await mv.find(query, options?)| Option | Type | Description |
|--------|------|-------------|
| mode | "lex" \| "sem" \| "auto" | Search mode (default: "lex"). "sem" uses vector search. "auto" reranks lexical hits when embeddings are available. |
| queryEmbedding | number[] | Pre-computed query embedding (offline-safe). If omitted, the SDK tries to auto-detect the embedding model from the .mv2 and embed the query. |
| queryEmbeddingModel | string | Override the query embedding model (e.g. "openai-small", "bge-small"). |
| k | number | Number of results (default: 10) |
| snippetChars | number | Snippet length (default: 240) |
| scope | string | Filter by URI prefix |
| adaptive | boolean | Enable adaptive retrieval (semantic-only) |
| minRelevancy | number | Minimum relevancy ratio vs top score (default: 0.5, adaptive-only) |
| maxK | number | Max candidates to consider (default: 100, adaptive-only) |
| adaptiveStrategy | "relative" \| "absolute" \| "cliff" \| "elbow" \| "combined" | Adaptive cutoff strategy (default: "relative", adaptive-only) |
Vector search (pre-computed query embedding)
Use vecSearch() when you already have a query embedding vector (offline-safe):
const queryEmbedding = [/* ... floats ... */];
const results = await mv.vecSearch("my query", queryEmbedding, { k: 10 });
console.log(results.hits);Retrieval-augmented generation
const response = await mv.ask(question, options?)| Option | Type | Description |
|--------|------|-------------|
| k | number | Documents to retrieve (default: 6) |
| mode | string | "lex", "sem", or "auto"/"hybrid" |
| queryEmbedding | number[] | Pre-computed query embedding for semantic/hybrid ask (offline-safe). If omitted, the SDK tries to auto-detect the embedding model from the .mv2 and embed the query. |
| queryEmbeddingModel | string | Override the query embedding model (e.g. "openai-small", "bge-small"). |
| adaptive | boolean | Enable adaptive retrieval (semantic/hybrid only) |
| minRelevancy | number | Minimum relevancy ratio vs top score (default: 0.5, adaptive-only) |
| maxK | number | Max candidates to consider (default: 100, adaptive-only) |
| adaptiveStrategy | "relative" \| "absolute" \| "cliff" \| "elbow" \| "combined" | Adaptive cutoff strategy (default: "relative", adaptive-only) |
Best practices: Adaptive retrieval
For best search quality, enable adaptive retrieval with the combined strategy. This dynamically adjusts result counts based on relevance scores rather than returning a fixed k:
// Recommended for find()
const results = await mv.find("your query", {
mode: "sem", // or "auto"
adaptive: true,
adaptiveStrategy: "combined",
});
// Recommended for ask()
const answer = await mv.ask("your question", {
mode: "auto",
adaptive: true,
adaptiveStrategy: "combined",
});The combined strategy uses both relative thresholds and score cliff detection to filter out low-relevance results, providing higher quality context for RAG applications.
LLM synthesis options
| Option | Type | Description |
|--------|------|-------------|
| model | string | LLM for synthesis (e.g., "openai:gpt-4o-mini", "nvidia:meta/llama3-8b-instruct") |
| modelApiKey | string | API key for the LLM provider |
| contextOnly | boolean | Skip synthesis, return context only |
| returnSources | boolean | Include source metadata in response |
Timeline queries
const entries = await mv.timeline({
limit: 50,
since: 1704067200, // Unix timestamp
reverse: true,
});Statistics
const stats = await mv.stats();
// { frame_count, size_bytes, has_lex_index, has_vec_index, ... }File operations
// Commit pending changes
await mv.seal();
// Verify file integrity
import { verifyMemvid } from "@memvid/sdk";
const report = await verifyMemvid("notes.mv2", { deep: true });
// Repair indexes
import { doctorMemvid } from "@memvid/sdk";
await doctorMemvid("notes.mv2", { rebuildLexIndex: true, dryRun: true });
// Verify the single-file guarantee (no sidecar files like -wal/-shm)
import { verifySingleFile } from "@memvid/sdk";
await verifySingleFile("notes.mv2");
// Lock visibility helpers
import { lockWho, lockNudge } from "@memvid/sdk";
console.log(await lockWho("notes.mv2")); // { locked: false } or { locked: true, owner: ... }
await lockNudge("notes.mv2"); // sends SIGUSR1 to active writer (Unix)Framework adapters
Adapters expose framework-native tools when the corresponding dependency is installed.
LangChain
import { use } from "@memvid/sdk";
const mv = await use("langchain", "notes.mv2");
const tools = mv.tools; // StructuredTool instances for put/find/askLlamaIndex
const mv = await use("llamaindex", "notes.mv2");
const queryEngine = mv.asQueryEngine();OpenAI function calling
const mv = await use("openai", "notes.mv2");
const functions = mv.functions; // JSON schemas for tool_callsVercel AI SDK
const mv = await use("vercel-ai", "notes.mv2");Table extraction
Extract structured tables from PDFs:
const result = await mv.putPdfTables("report.pdf", true);
console.log(`Extracted ${result.tables_count} tables`);
const tables = await mv.listTables();
const data = await mv.getTable(tables[0].table_id, "json");Error handling
Errors include a code prefix for programmatic handling:
| Code | Description |
|------|-------------|
| MV001 | Storage capacity exceeded |
| MV002 | Invalid ticket signature |
| MV003 | Ticket replay detected |
| MV004 | Lexical index not enabled |
| MV005 | Time index missing |
| MV006 | Verification failed |
| MV007 | File locked by another process |
| MV008 | Memvid API key required |
| MV009 | Memory already bound |
| MV010 | Frame not found |
| MV011 | Vector index not enabled |
| MV012 | Corrupt file detected |
| MV013 | I/O error (e.g., file not found) |
| MV014 | Vector dimension mismatch |
| MV015 | Embedding runtime unavailable/failed |
| MV999 | Unknown/internal error |
import { MemvidError } from "@memvid/sdk";
try {
await mv.put({ ... });
} catch (err) {
if (err instanceof MemvidError) {
if (err.code === "MV001") {
console.error("Out of storage capacity");
} else {
console.error(`Error ${err.code}: ${err.message}`);
}
}
}Environment variables
| Variable | Description |
|----------|-------------|
| MEMVID_API_KEY | API key for capacity beyond free tier |
| MEMVID_API_URL | Control plane URL (for enterprise deployments) |
| MEMVID_MODELS_DIR | Path to embedding model cache |
| MEMVID_OFFLINE | Set to 1 to disable network-backed embeddings and avoid model downloads |
| OPENAI_API_KEY | API key for OpenAI embeddings / LLMs (when used) |
| OPENAI_BASE_URL | Override OpenAI API base URL (defaults to https://api.openai.com) |
Building from source
# Install dependencies
pnpm install
# Build TypeScript
pnpm run build
# Build native module (requires Rust toolchain)
cargo build --release
cp target/release/libmemvid_node.dylib index.node # macOSRequirements
- Node.js 18+
- macOS (Apple Silicon or Intel), Linux (x64), or Windows (x64)
License
Apache-2.0. See LICENSE.
